Skip to content

Commit 74682f1

Browse files
author
George Dunlap
committed
ParsePatchHeader: Copy functionality of git mailinfo's cleanup_subject
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. Inspired by https://github.com/git/git/blob/master/mailinfo.c:cleanup_subject() Signed-off-by: George Dunlap <[email protected]>
1 parent d3116e7 commit 74682f1

File tree

2 files changed

+108
-6
lines changed

2 files changed

+108
-6
lines changed

gitdiff/patch_header.go

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,13 @@ func ParsePatchDate(s string) (time.Time, error) {
160160
// formats used by git diff, git log, and git show and the UNIX mailbox format
161161
// used by git format-patch.
162162
//
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.
163+
// If ParsePatchHeader detect that it is handling an email, it will
164+
// remove extra content at the beginning of the title line, such as
165+
// `[PATCH]` or `Re:` in the same way that `git mailinfo` does. (`git
166+
// mailinfo` is the core part of `git am` that pulls information out
167+
// of an individual mail.) Unline `git mailinfo`, it does not at the
168+
// moment remove commit states or other extraneous matter after a
169+
// `---` line.
167170
func ParsePatchHeader(s string) (*PatchHeader, error) {
168171
r := bufio.NewReader(strings.NewReader(s))
169172

@@ -359,7 +362,8 @@ func parseHeaderMail(mailLine string, r io.Reader) (*PatchHeader, error) {
359362
h.AuthorDate = d
360363
}
361364

362-
h.Title = msg.Header.Get("Subject")
365+
subject := msg.Header.Get("Subject")
366+
h.Title = cleanupSubject(subject)
363367

364368
s := bufio.NewScanner(msg.Body)
365369
h.Body = scanMessageBody(s, "")
@@ -369,3 +373,61 @@ func parseHeaderMail(mailLine string, r io.Reader) (*PatchHeader, error) {
369373

370374
return h, nil
371375
}
376+
377+
func cleanupSubject(s string) string {
378+
r := []rune(s)
379+
380+
// This is meant to be compatible with
381+
// https://github.com/git/git/blob/master/mailinfo.c:cleanup_subject().
382+
// If compatibility with `git am` drifts, go there to see if there
383+
// are any updates.
384+
385+
var at int
386+
for at = 0; at < len(r); {
387+
switch r[at] {
388+
case 'r', 'R':
389+
// Detect re:, Re:, rE: and RE:
390+
if at+3 > len(r) {
391+
break
392+
}
393+
if (r[at+1] == 'e' || r[at+1] == 'E') &&
394+
r[at+2] == ':' {
395+
at += 3
396+
continue
397+
}
398+
399+
case ' ', '\t', ':':
400+
// Delete whitespace and duplicate ':' characters
401+
at++
402+
continue
403+
404+
case '[':
405+
// Look for closing parenthesis
406+
var closepos int
407+
for j := at + 1; j < len(r); j++ {
408+
if r[j] == ']' {
409+
closepos = j
410+
break
411+
}
412+
}
413+
414+
if closepos == 0 {
415+
break
416+
}
417+
418+
at = closepos + 1
419+
continue
420+
}
421+
422+
// Only loop if we actually removed something
423+
break
424+
}
425+
426+
if at > 0 {
427+
for i := 0; i < len(r)-at; i++ {
428+
r[i] = r[at+i]
429+
}
430+
}
431+
432+
return string(r[:len(r)-at])
433+
}

gitdiff/patch_header_test.go

Lines changed: 41 additions & 1 deletion
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,43 @@ 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": "A sample commit to test header parsing",
356+
"patch": "[PATCH] A sample commit to test header parsing",
357+
"patchv5": "[PATCH v5] A sample commit to test header parsing",
358+
"patchrfc": "[PATCH RFC] A sample commit to test header parsing",
359+
"patchnospace": "[PATCH]A sample commit to test header parsing",
360+
"space": " A sample commit to test header parsing",
361+
"re": "re: A sample commit to test header parsing",
362+
"Re": "Re: A sample commit to test header parsing",
363+
"RE": "rE: A sample commit to test header parsing",
364+
"rere": "re: re: A sample commit to test header parsing",
365+
}
366+
367+
for name, test := range tests {
368+
act := cleanupSubject(test)
369+
if act != exp {
370+
t.Errorf("%s: Incorrect cleanup of %s: got %s, wanted %s",
371+
name, test, act, exp)
372+
}
373+
}
374+
375+
moretests := map[string]struct {
376+
in, exp string
377+
}{
378+
"Reimplement": {"Reimplement something", "Reimplement something"},
379+
"patch-reimplement": {"[PATCH v5] Reimplement something", "Reimplement something"},
380+
"Openbracket": {"[Just to annoy people", "[Just to annoy people"},
381+
}
382+
383+
for name, test := range moretests {
384+
act := cleanupSubject(test.in)
385+
if act != test.exp {
386+
t.Errorf("%s: Incorrect cleanup of %s: got %s, wanted %s",
387+
name, test.in, act, test.exp)
388+
}
389+
}
390+
}

0 commit comments

Comments
 (0)