Skip to content

Commit 379b893

Browse files
gwdGeorge Dunlap
and
George Dunlap
authored
Remove email decorations from patch titles (#17)
Primarily to get rid of [PATCH] at the front, but while we're here just be generally compatible with `git am`: * Remove `re` and variations * Remove whitespace * Remove anything in brackets But only at the very beginning of the subject. Store anything removed in this way in PatchHeader.SubjectPrefix. Inspired by https://github.com/git/git/blob/master/mailinfo.c:cleanup_subject() Signed-off-by: George Dunlap <[email protected]> Co-authored-by: George Dunlap <[email protected]>
1 parent 864dd3f commit 379b893

File tree

2 files changed

+112
-6
lines changed

2 files changed

+112
-6
lines changed

gitdiff/patch_header.go

+63-5
Original file line numberDiff line numberDiff line change
@@ -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.
167176
func 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+
}

gitdiff/patch_header_test.go

+49-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ Another body line.
236236
SHA: expectedSHA,
237237
Author: expectedIdentity,
238238
AuthorDate: expectedDate,
239-
Title: "[PATCH] " + expectedTitle,
239+
Title: expectedTitle,
240240
Body: expectedBody,
241241
},
242242
},
@@ -348,3 +348,51 @@ func assertPatchIdentity(t *testing.T, kind string, exp, act *PatchIdentity) {
348348
t.Errorf("incorrect parsed %s, expected %+v, bot got %+v", kind, exp, act)
349349
}
350350
}
351+
352+
func TestCleanupSubject(t *testing.T) {
353+
exp := "A sample commit to test header parsing"
354+
tests := map[string]string{
355+
"plain": "",
356+
"patch": "[PATCH] ",
357+
"patchv5": "[PATCH v5] ",
358+
"patchrfc": "[PATCH RFC] ",
359+
"patchnospace": "[PATCH]",
360+
"space": " ",
361+
"re": "re: ",
362+
"Re": "Re: ",
363+
"RE": "rE: ",
364+
"rere": "re: re: ",
365+
}
366+
367+
for name, prefix := range tests {
368+
gotprefix, gottitle := parseSubject(prefix + exp)
369+
if gottitle != exp {
370+
t.Errorf("%s: Incorrect parsing of prefix %s: got title %s, wanted %s",
371+
name, prefix, gottitle, exp)
372+
}
373+
if gotprefix != prefix {
374+
t.Errorf("%s: Incorrect parsing of prefix %s: got prefix %s",
375+
name, prefix, gotprefix)
376+
}
377+
}
378+
379+
moretests := map[string]struct {
380+
in, eprefix, etitle string
381+
}{
382+
"Reimplement": {"Reimplement something", "", "Reimplement something"},
383+
"patch-reimplement": {"[PATCH v5] Reimplement something", "[PATCH v5] ", "Reimplement something"},
384+
"Openbracket": {"[Just to annoy people", "", "[Just to annoy people"},
385+
}
386+
387+
for name, test := range moretests {
388+
prefix, title := parseSubject(test.in)
389+
if title != test.etitle {
390+
t.Errorf("%s: Incorrect parsing of %s: got title %s, wanted %s",
391+
name, test.in, title, test.etitle)
392+
}
393+
if prefix != test.eprefix {
394+
t.Errorf("%s: Incorrect parsing of %s: got prefix %s, wanted %s",
395+
name, test.in, title, test.etitle)
396+
}
397+
}
398+
}

0 commit comments

Comments
 (0)