55 "errors"
66 "fmt"
77 "regexp"
8+ "slices"
89 "strings"
910)
1011
2021 errIssueIDNotFound = errors .New ("could not find issue id using configured regex" )
2122 errInvalidIssueRegex = errors .New ("could not compile issue regex" )
2223 errInvalidHeaderRegex = errors .New ("invalid regex on header-selector" )
24+
25+ footerColonRe = regexp .MustCompile (`^[a-zA-Z-]+: ` )
26+ footerHashRe = regexp .MustCompile (`^[a-zA-Z-]+ #` )
2327)
2428
2529// CommitMessage is a message using conventional commits.
@@ -306,7 +310,14 @@ func (p MessageProcessorImpl) Parse(subject, body string) (CommitMessage, error)
306310 if mdCfg .Key != "" {
307311 prefixes := append ([]string {mdCfg .Key }, mdCfg .KeySynonyms ... )
308312 for _ , prefix := range prefixes {
309- if tagValue := extractFooterMetadata (prefix , m .Body , mdCfg .UseHash ); tagValue != "" {
313+ var prefixPattern string
314+ if mdCfg .UseHash {
315+ prefixPattern = prefix + " #"
316+ } else {
317+ prefixPattern = prefix + ": "
318+ }
319+
320+ if tagValue := extractFooterMetadata (prefixPattern , m .Body ); tagValue != "" {
310321 m .Metadata [key ] = tagValue
311322
312323 break
@@ -319,7 +330,7 @@ func (p MessageProcessorImpl) Parse(subject, body string) (CommitMessage, error)
319330 m .Metadata [BreakingChangeMetadataKey ] = m .Description
320331 }
321332
322- if tagValue := extractFooterMetadata (BreakingChangeFooterKey , m .Body , false ); tagValue != "" {
333+ if tagValue := extractFooterMetadata (BreakingChangeFooterKey + ": " , m .Body ); tagValue != "" {
323334 m .IsBreakingChange = true
324335 m .Metadata [BreakingChangeMetadataKey ] = tagValue
325336 }
@@ -367,19 +378,51 @@ func parseSubjectMessage(message string) (string, string, string, bool) {
367378 return result [1 ], result [3 ], strings .TrimSpace (result [5 ]), result [4 ] == "!"
368379}
369380
370- func extractFooterMetadata (key , text string , useHash bool ) string {
371- regex := regexp . MustCompile ( key + ": (.*)" )
381+ func extractFooterMetadata (key , text string ) string {
382+ var value string
372383
373- if useHash {
374- regex = regexp .MustCompile (key + " (#.*)" )
375- }
384+ scanner := bufio .NewScanner (strings .NewReader (text ))
385+
386+ inFooterValue := false
387+
388+ for scanner .Scan () {
389+ line := scanner .Text ()
390+
391+ if after , ok := strings .CutPrefix (line , key ); ok {
392+ if strings .HasSuffix (key , " #" ) {
393+ value = "#" + after
394+ } else {
395+ value = after
396+ }
397+
398+ inFooterValue = true
399+
400+ continue
401+ }
376402
377- result := regex .FindStringSubmatch (text )
378- if len (result ) < 2 { //nolint:mnd
379- return ""
403+ if inFooterValue {
404+ // Check if this line is another footer
405+ // Standard footer pattern: "Key: value"
406+ if footerColonRe .MatchString (line ) {
407+ break
408+ }
409+
410+ // Hash footer pattern: "Key #value" - e.g., "Refs #123"
411+ if footerHashRe .MatchString (line ) {
412+ break
413+ }
414+
415+ // Check for BREAKING CHANGE footer which has space in key name
416+ if strings .HasPrefix (line , BreakingChangeFooterKey + ": " ) {
417+ break
418+ }
419+
420+ // Continuation of previous footer value
421+ value += "\n " + line
422+ }
380423 }
381424
382- return result [ 1 ]
425+ return value
383426}
384427
385428func hasFooter (message string ) bool {
@@ -411,13 +454,7 @@ func hasIssueID(message string, issueConfig CommitMessageFooterConfig) bool {
411454}
412455
413456func contains (value string , content []string ) bool {
414- for _ , v := range content {
415- if value == v {
416- return true
417- }
418- }
419-
420- return false
457+ return slices .Contains (content , value )
421458}
422459
423460func splitCommitMessageContent (content string ) (string , string ) {
0 commit comments