@@ -39,6 +39,11 @@ type PatchHeader struct {
3939 // patch. Empty if no message is included in the header.
4040 Title string
4141 Body string
42+
43+ // If the preamble looks like an email, ParsePatchHeader will
44+ // remove prefixes such as `Re: ` and `[PATCH v3 5/17]` from the
45+ // Title and place them here.
46+ SubjectPrefix string
4247}
4348
4449// Message returns the commit message for the header. The message consists of
@@ -160,10 +165,14 @@ func ParsePatchDate(s string) (time.Time, error) {
160165// formats used by git diff, git log, and git show and the UNIX mailbox format
161166// used by git format-patch.
162167//
163- // ParsePatchHeader makes no assumptions about the format of the patch title or
164- // message other than trimming whitespace and condensing blank lines. In
165- // particular, it does not remove the extra content that git format-patch adds
166- // to make emailed patches friendlier, like subject prefixes or commit stats.
168+ // If ParsePatchHeader detect that it is handling an email, it will
169+ // remove extra content at the beginning of the title line, such as
170+ // `[PATCH]` or `Re:` in the same way that `git mailinfo` does.
171+ // SubjectPrefix will be set to the value of this removed string.
172+ // (`git mailinfo` is the core part of `git am` that pulls information
173+ // out of an individual mail.) Unline `git mailinfo`,
174+ // ParsePatchHeader does not at the moment remove commit states or
175+ // other extraneous matter after a `---` line.
167176func ParsePatchHeader (s string ) (* PatchHeader , error ) {
168177 r := bufio .NewReader (strings .NewReader (s ))
169178
@@ -359,7 +368,8 @@ func parseHeaderMail(mailLine string, r io.Reader) (*PatchHeader, error) {
359368 h .AuthorDate = d
360369 }
361370
362- h .Title = msg .Header .Get ("Subject" )
371+ subject := msg .Header .Get ("Subject" )
372+ h .SubjectPrefix , h .Title = parseSubject (subject )
363373
364374 s := bufio .NewScanner (msg .Body )
365375 h .Body = scanMessageBody (s , "" )
@@ -369,3 +379,51 @@ func parseHeaderMail(mailLine string, r io.Reader) (*PatchHeader, error) {
369379
370380 return h , nil
371381}
382+
383+ // Takes an email subject and returns the patch prefix and commit
384+ // title. i.e., `[PATCH v3 3/5] Implement foo` would return `[PATCH
385+ // v3 3/5] ` and `Implement foo`
386+ func parseSubject (s string ) (string , string ) {
387+ // This is meant to be compatible with
388+ // https://github.com/git/git/blob/master/mailinfo.c:cleanup_subject().
389+ // If compatibility with `git am` drifts, go there to see if there
390+ // are any updates.
391+
392+ at := 0
393+ for at < len (s ) {
394+ switch s [at ] {
395+ case 'r' , 'R' :
396+ // Detect re:, Re:, rE: and RE:
397+ if at + 2 < len (s ) &&
398+ (s [at + 1 ] == 'e' || s [at + 1 ] == 'E' ) &&
399+ s [at + 2 ] == ':' {
400+ at += 3
401+ continue
402+ }
403+
404+ case ' ' , '\t' , ':' :
405+ // Delete whitespace and duplicate ':' characters
406+ at ++
407+ continue
408+
409+ case '[' :
410+ // Look for closing parenthesis
411+ j := at + 1
412+ for ; j < len (s ); j ++ {
413+ if s [j ] == ']' {
414+ break
415+ }
416+ }
417+
418+ if j < len (s ) {
419+ at = j + 1
420+ continue
421+ }
422+ }
423+
424+ // Only loop if we actually removed something
425+ break
426+ }
427+
428+ return s [:at ], s [at :]
429+ }
0 commit comments