@@ -67,11 +67,12 @@ formatted-commit -t refactor -s "web/git-bug" -m "fancy shmancy" \
}
if len(trailers) > 0 {
- commitMsg.WriteString("\n\n")
- for _, trailer := range trailers {
- commitMsg.WriteString(trailer)
- commitMsg.WriteString("\n")
+ trailersBlock, err := buildTrailersBlock(trailers)
+ if err != nil {
+ return fmt.Errorf("failed to build trailers: %w", err)
}
+ commitMsg.WriteString("\n\n")
+ commitMsg.WriteString(trailersBlock)
}
fmt.Print(commitMsg.String())
@@ -82,7 +83,7 @@ formatted-commit -t refactor -s "web/git-bug" -m "fancy shmancy" \
func init() {
rootCmd.Flags().StringVarP(&commitType, "type", "t", "", "commit type (required)")
rootCmd.Flags().StringVarP(&message, "message", "m", "", "commit message (required)")
- rootCmd.Flags().StringSliceVarP(&trailers, "trailer", "T", []string{}, "trailer in 'Sentence-case-key: value' format (optional, repeatable)")
+ rootCmd.Flags().StringArrayVarP(&trailers, "trailer", "T", []string{}, "trailer in 'Sentence-case-key: value' format (optional, repeatable)")
rootCmd.Flags().StringVarP(&body, "body", "b", "", "commit body (optional)")
rootCmd.Flags().StringVarP(&scope, "scope", "s", "", "commit scope (optional)")
rootCmd.Flags().BoolVarP(&breakingChange, "breaking", "B", false, "mark as breaking change (optional)")
@@ -0,0 +1,68 @@
+// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+package main
+
+import (
+ "fmt"
+ "strings"
+ "unicode"
+)
+
+func validateTrailer(trailer string) error {
+ if trailer == "" {
+ return fmt.Errorf("trailer cannot be empty")
+ }
+
+ lines := strings.Split(trailer, "\n")
+ firstLine := lines[0]
+
+ if len(firstLine) > 0 && unicode.IsSpace(rune(firstLine[0])) {
+ return fmt.Errorf("trailer key cannot start with whitespace: %q", trailer)
+ }
+
+ colonIdx := strings.Index(firstLine, ":")
+ if colonIdx == -1 {
+ return fmt.Errorf("trailer must contain ':' separator: %q", trailer)
+ }
+
+ key := firstLine[:colonIdx]
+
+ if strings.TrimSpace(key) != key {
+ return fmt.Errorf("trailer key cannot have leading or trailing whitespace: %q", key)
+ }
+
+ if strings.ContainsAny(key, " \t") {
+ return fmt.Errorf("trailer key cannot contain whitespace: %q", key)
+ }
+
+ if key == "" {
+ return fmt.Errorf("trailer key cannot be empty: %q", trailer)
+ }
+
+ for i, line := range lines[1:] {
+ if line == "" {
+ continue
+ }
+ if len(line) > 0 && !unicode.IsSpace(rune(line[0])) {
+ return fmt.Errorf("trailer continuation line %d must start with whitespace: %q", i+2, line)
+ }
+ }
+
+ return nil
+}
+
+func buildTrailersBlock(trailers []string) (string, error) {
+ if len(trailers) == 0 {
+ return "", nil
+ }
+
+ for _, trailer := range trailers {
+ if err := validateTrailer(trailer); err != nil {
+ return "", err
+ }
+ }
+
+ return strings.Join(trailers, "\n"), nil
+}