fork of go-gitdiff with jj support
1package gitdiff
2
3import (
4 "testing"
5 "time"
6)
7
8func TestParsePatchDate(t *testing.T) {
9 expected := time.Date(2020, 4, 9, 8, 7, 6, 0, time.UTC)
10
11 tests := map[string]struct {
12 Input string
13 Output time.Time
14 Err interface{}
15 }{
16 "default": {
17 Input: "Thu Apr 9 01:07:06 2020 -0700",
18 Output: expected,
19 },
20 "defaultLocal": {
21 Input: "Thu Apr 9 01:07:06 2020",
22 Output: time.Date(2020, 4, 9, 1, 7, 6, 0, time.Local),
23 },
24 "iso": {
25 Input: "2020-04-09 01:07:06 -0700",
26 Output: expected,
27 },
28 "isoStrict": {
29 Input: "2020-04-09T01:07:06-07:00",
30 Output: expected,
31 },
32 "rfc": {
33 Input: "Thu, 9 Apr 2020 01:07:06 -0700",
34 Output: expected,
35 },
36 "short": {
37 Input: "2020-04-09",
38 Output: time.Date(2020, 4, 9, 0, 0, 0, 0, time.Local),
39 },
40 "raw": {
41 Input: "1586419626 -0700",
42 Output: expected,
43 },
44 "unix": {
45 Input: "1586419626",
46 Output: expected,
47 },
48 "unknownFormat": {
49 Input: "4/9/2020 01:07:06 PDT",
50 Err: "unknown date format",
51 },
52 "empty": {
53 Input: "",
54 },
55 }
56
57 for name, test := range tests {
58 t.Run(name, func(t *testing.T) {
59 d, err := ParsePatchDate(test.Input)
60 if test.Err != nil {
61 assertError(t, test.Err, err, "parsing date")
62 return
63 }
64 if err != nil {
65 t.Fatalf("unexpected error parsing date: %v", err)
66 }
67 if !test.Output.Equal(d) {
68 t.Errorf("incorrect parsed date: expected %v, actual %v", test.Output, d)
69 }
70 })
71 }
72}
73
74func TestParsePatchHeader(t *testing.T) {
75 expectedSHA := "61f5cd90bed4d204ee3feb3aa41ee91d4734855b"
76 expectedIdentity := &PatchIdentity{
77 Name: "Morton Haypenny",
78 Email: "mhaypenny@example.com",
79 }
80 expectedDate := time.Date(2020, 04, 11, 15, 21, 23, 0, time.FixedZone("PDT", -7*60*60))
81 expectedTitle := "A sample commit to test header parsing"
82 expectedEmojiOneLineTitle := "🤖 Enabling auto-merging"
83 expectedEmojiMultiLineTitle := "[IA64] Put ia64 config files on the Uwe Kleine-König diet"
84 expectedBody := "The medium format shows the body, which\nmay wrap on to multiple lines.\n\nAnother body line."
85 expectedBodyAppendix := "CC: Joe Smith <joe.smith@company.com>"
86
87 tests := map[string]struct {
88 Input string
89 Options []PatchHeaderOption
90 Header PatchHeader
91 Err interface{}
92 }{
93 "prettyShort": {
94 Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
95Author: Morton Haypenny <mhaypenny@example.com>
96
97 A sample commit to test header parsing
98`,
99 Header: PatchHeader{
100 SHA: expectedSHA,
101 Author: expectedIdentity,
102 Title: expectedTitle,
103 },
104 },
105 "prettyMedium": {
106 Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
107Author: Morton Haypenny <mhaypenny@example.com>
108Date: Sat Apr 11 15:21:23 2020 -0700
109
110 A sample commit to test header parsing
111
112 The medium format shows the body, which
113 may wrap on to multiple lines.
114
115 Another body line.
116`,
117 Header: PatchHeader{
118 SHA: expectedSHA,
119 Author: expectedIdentity,
120 AuthorDate: expectedDate,
121 Title: expectedTitle,
122 Body: expectedBody,
123 },
124 },
125 "prettyFull": {
126 Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
127Author: Morton Haypenny <mhaypenny@example.com>
128Commit: Morton Haypenny <mhaypenny@example.com>
129
130 A sample commit to test header parsing
131
132 The medium format shows the body, which
133 may wrap on to multiple lines.
134
135 Another body line.
136`,
137 Header: PatchHeader{
138 SHA: expectedSHA,
139 Author: expectedIdentity,
140 Committer: expectedIdentity,
141 Title: expectedTitle,
142 Body: expectedBody,
143 },
144 },
145 "prettyFuller": {
146 Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
147Author: Morton Haypenny <mhaypenny@example.com>
148AuthorDate: Sat Apr 11 15:21:23 2020 -0700
149Commit: Morton Haypenny <mhaypenny@example.com>
150CommitDate: Sat Apr 11 15:21:23 2020 -0700
151
152 A sample commit to test header parsing
153
154 The medium format shows the body, which
155 may wrap on to multiple lines.
156
157 Another body line.
158`,
159 Header: PatchHeader{
160 SHA: expectedSHA,
161 Author: expectedIdentity,
162 AuthorDate: expectedDate,
163 Committer: expectedIdentity,
164 CommitterDate: expectedDate,
165 Title: expectedTitle,
166 Body: expectedBody,
167 },
168 },
169 "prettyAppendix": {
170 Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
171Author: Morton Haypenny <mhaypenny@example.com>
172AuthorDate: Sat Apr 11 15:21:23 2020 -0700
173Commit: Morton Haypenny <mhaypenny@example.com>
174CommitDate: Sat Apr 11 15:21:23 2020 -0700
175
176 A sample commit to test header parsing
177
178 The medium format shows the body, which
179 may wrap on to multiple lines.
180
181 Another body line.
182 ---
183 CC: Joe Smith <joe.smith@company.com>
184`,
185 Header: PatchHeader{
186 SHA: expectedSHA,
187 Author: expectedIdentity,
188 AuthorDate: expectedDate,
189 Committer: expectedIdentity,
190 CommitterDate: expectedDate,
191 Title: expectedTitle,
192 Body: expectedBody + "\n---\n" + expectedBodyAppendix,
193 },
194 },
195 "mailbox": {
196 Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
197From: Morton Haypenny <mhaypenny@example.com>
198Date: Sat, 11 Apr 2020 15:21:23 -0700
199Subject: [PATCH] A sample commit to test header parsing
200
201The medium format shows the body, which
202may wrap on to multiple lines.
203
204Another body line.
205`,
206 Header: PatchHeader{
207 SHA: expectedSHA,
208 Author: expectedIdentity,
209 AuthorDate: expectedDate,
210 Title: expectedTitle,
211 Body: expectedBody,
212 },
213 },
214 "mailboxPatchOnly": {
215 Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
216From: Morton Haypenny <mhaypenny@example.com>
217Date: Sat, 11 Apr 2020 15:21:23 -0700
218Subject: [PATCH] [BUG-123] A sample commit to test header parsing
219
220The medium format shows the body, which
221may wrap on to multiple lines.
222
223Another body line.
224`,
225 Options: []PatchHeaderOption{
226 WithSubjectCleanMode(SubjectCleanPatchOnly),
227 },
228 Header: PatchHeader{
229 SHA: expectedSHA,
230 Author: expectedIdentity,
231 AuthorDate: expectedDate,
232 Title: "[BUG-123] " + expectedTitle,
233 Body: expectedBody,
234 },
235 },
236 "mailboxEmojiOneLine": {
237 Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
238From: Morton Haypenny <mhaypenny@example.com>
239Date: Sat, 11 Apr 2020 15:21:23 -0700
240Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Enabling=20auto-merging?=
241
242The medium format shows the body, which
243may wrap on to multiple lines.
244
245Another body line.
246`,
247 Header: PatchHeader{
248 SHA: expectedSHA,
249 Author: expectedIdentity,
250 AuthorDate: expectedDate,
251 Title: expectedEmojiOneLineTitle,
252 Body: expectedBody,
253 },
254 },
255 "mailboxEmojiMultiLine": {
256 Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
257From: Morton Haypenny <mhaypenny@example.com>
258Date: Sat, 11 Apr 2020 15:21:23 -0700
259Subject: [PATCH] =?UTF-8?q?[IA64]=20Put=20ia64=20config=20files=20on=20the=20?=
260 =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20diet?=
261
262The medium format shows the body, which
263may wrap on to multiple lines.
264
265Another body line.
266`,
267 Header: PatchHeader{
268 SHA: expectedSHA,
269 Author: expectedIdentity,
270 AuthorDate: expectedDate,
271 Title: expectedEmojiMultiLineTitle,
272 Body: expectedBody,
273 },
274 },
275 "mailboxRFC5322SpecialCharacters": {
276 Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
277From: "dependabot[bot]" <12345+dependabot[bot]@users.noreply.github.com>
278Date: Sat, 11 Apr 2020 15:21:23 -0700
279Subject: [PATCH] A sample commit to test header parsing
280
281The medium format shows the body, which
282may wrap on to multiple lines.
283
284Another body line.
285`,
286 Header: PatchHeader{
287 SHA: expectedSHA,
288 Author: &PatchIdentity{
289 Name: "dependabot[bot]",
290 Email: "12345+dependabot[bot]@users.noreply.github.com",
291 },
292 AuthorDate: expectedDate,
293 Title: expectedTitle,
294 Body: expectedBody,
295 },
296 },
297 "mailboxAppendix": {
298 Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
299From: Morton Haypenny <mhaypenny@example.com>
300Date: Sat, 11 Apr 2020 15:21:23 -0700
301Subject: [PATCH] A sample commit to test header parsing
302
303The medium format shows the body, which
304may wrap on to multiple lines.
305
306Another body line.
307---
308CC: Joe Smith <joe.smith@company.com>
309`,
310 Header: PatchHeader{
311 SHA: expectedSHA,
312 Author: expectedIdentity,
313 AuthorDate: expectedDate,
314 Title: expectedTitle,
315 Body: expectedBody,
316 BodyAppendix: expectedBodyAppendix,
317 },
318 },
319 "mailboxMinimalNoName": {
320 Input: `From: <mhaypenny@example.com>
321Subject: [PATCH] A sample commit to test header parsing
322
323The medium format shows the body, which
324may wrap on to multiple lines.
325
326Another body line.
327`,
328 Header: PatchHeader{
329 Author: &PatchIdentity{expectedIdentity.Email, expectedIdentity.Email},
330 Title: expectedTitle,
331 Body: expectedBody,
332 },
333 },
334 "mailboxMinimal": {
335 Input: `From: Morton Haypenny <mhaypenny@example.com>
336Subject: [PATCH] A sample commit to test header parsing
337
338The medium format shows the body, which
339may wrap on to multiple lines.
340
341Another body line.
342`,
343 Header: PatchHeader{
344 Author: expectedIdentity,
345 Title: expectedTitle,
346 Body: expectedBody,
347 },
348 },
349 "unwrapTitle": {
350 Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
351Author: Morton Haypenny <mhaypenny@example.com>
352Date: Sat Apr 11 15:21:23 2020 -0700
353
354 A sample commit to test header parsing with a long
355 title that is wrapped.
356`,
357 Header: PatchHeader{
358 SHA: expectedSHA,
359 Author: expectedIdentity,
360 AuthorDate: expectedDate,
361 Title: expectedTitle + " with a long title that is wrapped.",
362 },
363 },
364 "normalizeBodySpace": {
365 Input: `commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
366Author: Morton Haypenny <mhaypenny@example.com>
367Date: Sat Apr 11 15:21:23 2020 -0700
368
369 A sample commit to test header parsing
370
371
372 The medium format shows the body, which
373 may wrap on to multiple lines.
374
375
376 Another body line.
377
378
379`,
380 Header: PatchHeader{
381 SHA: expectedSHA,
382 Author: expectedIdentity,
383 AuthorDate: expectedDate,
384 Title: expectedTitle,
385 Body: expectedBody,
386 },
387 },
388 "ignoreLeadingBlankLines": {
389 Input: `
390
391` + " " + `
392commit 61f5cd90bed4d204ee3feb3aa41ee91d4734855b
393Author: Morton Haypenny <mhaypenny@example.com>
394
395 A sample commit to test header parsing
396`,
397 Header: PatchHeader{
398 SHA: expectedSHA,
399 Author: expectedIdentity,
400 Title: expectedTitle,
401 },
402 },
403 "emptyHeader": {
404 Input: "",
405 Header: PatchHeader{},
406 },
407 }
408
409 for name, test := range tests {
410 t.Run(name, func(t *testing.T) {
411 h, err := ParsePatchHeader(test.Input, test.Options...)
412 if test.Err != nil {
413 assertError(t, test.Err, err, "parsing patch header")
414 return
415 }
416 if err != nil {
417 t.Fatalf("unexpected error parsing patch header: %v", err)
418 }
419 if h == nil {
420 t.Fatalf("expected non-nil header, but got nil")
421 }
422
423 exp := test.Header
424 act := *h
425
426 if exp.SHA != act.SHA {
427 t.Errorf("incorrect parsed SHA: expected %q, actual %q", exp.SHA, act.SHA)
428 }
429
430 assertPatchIdentity(t, "author", exp.Author, act.Author)
431 if !exp.AuthorDate.Equal(act.AuthorDate) {
432 t.Errorf("incorrect parsed author date: expected %v, but got %v", exp.AuthorDate, act.AuthorDate)
433 }
434
435 assertPatchIdentity(t, "committer", exp.Committer, act.Committer)
436 if !exp.CommitterDate.Equal(act.CommitterDate) {
437 t.Errorf("incorrect parsed committer date: expected %v, but got %v", exp.CommitterDate, act.CommitterDate)
438 }
439
440 if exp.Title != act.Title {
441 t.Errorf("incorrect parsed title:\n expected: %q\n actual: %q", exp.Title, act.Title)
442 }
443 if exp.Body != act.Body {
444 t.Errorf("incorrect parsed body:\n expected: %q\n actual: %q", exp.Body, act.Body)
445 }
446 if exp.BodyAppendix != act.BodyAppendix {
447 t.Errorf("incorrect parsed body appendix:\n expected: %q\n actual: %q",
448 exp.BodyAppendix, act.BodyAppendix)
449 }
450 })
451 }
452}
453
454func assertPatchIdentity(t *testing.T, kind string, exp, act *PatchIdentity) {
455 switch {
456 case exp == nil && act == nil:
457 case exp == nil && act != nil:
458 t.Errorf("incorrect parsed %s: expected nil, but got %+v", kind, act)
459 case exp != nil && act == nil:
460 t.Errorf("incorrect parsed %s: expected %+v, but got nil", kind, exp)
461 case exp.Name != act.Name || exp.Email != act.Email:
462 t.Errorf("incorrect parsed %s, expected %+v, bot got %+v", kind, exp, act)
463 }
464}
465
466func TestCleanSubject(t *testing.T) {
467 expectedSubject := "A sample commit to test header parsing"
468
469 tests := map[string]struct {
470 Input string
471 Mode SubjectCleanMode
472 Prefix string
473 Subject string
474 }{
475 "CleanAll/noPrefix": {
476 Input: expectedSubject,
477 Mode: SubjectCleanAll,
478 Subject: expectedSubject,
479 },
480 "CleanAll/patchPrefix": {
481 Input: "[PATCH] " + expectedSubject,
482 Mode: SubjectCleanAll,
483 Prefix: "[PATCH] ",
484 Subject: expectedSubject,
485 },
486 "CleanAll/patchPrefixNoSpace": {
487 Input: "[PATCH]" + expectedSubject,
488 Mode: SubjectCleanAll,
489 Prefix: "[PATCH]",
490 Subject: expectedSubject,
491 },
492 "CleanAll/patchPrefixContent": {
493 Input: "[PATCH 3/7] " + expectedSubject,
494 Mode: SubjectCleanAll,
495 Prefix: "[PATCH 3/7] ",
496 Subject: expectedSubject,
497 },
498 "CleanAll/spacePrefix": {
499 Input: " " + expectedSubject,
500 Mode: SubjectCleanAll,
501 Subject: expectedSubject,
502 },
503 "CleanAll/replyLowerPrefix": {
504 Input: "re: " + expectedSubject,
505 Mode: SubjectCleanAll,
506 Prefix: "re: ",
507 Subject: expectedSubject,
508 },
509 "CleanAll/replyMixedPrefix": {
510 Input: "Re: " + expectedSubject,
511 Mode: SubjectCleanAll,
512 Prefix: "Re: ",
513 Subject: expectedSubject,
514 },
515 "CleanAll/replyCapsPrefix": {
516 Input: "RE: " + expectedSubject,
517 Mode: SubjectCleanAll,
518 Prefix: "RE: ",
519 Subject: expectedSubject,
520 },
521 "CleanAll/replyDoublePrefix": {
522 Input: "Re: re: " + expectedSubject,
523 Mode: SubjectCleanAll,
524 Prefix: "Re: re: ",
525 Subject: expectedSubject,
526 },
527 "CleanAll/noPrefixSubjectHasRe": {
528 Input: "Reimplement parsing",
529 Mode: SubjectCleanAll,
530 Subject: "Reimplement parsing",
531 },
532 "CleanAll/patchPrefixSubjectHasRe": {
533 Input: "[PATCH 1/2] Reimplement parsing",
534 Mode: SubjectCleanAll,
535 Prefix: "[PATCH 1/2] ",
536 Subject: "Reimplement parsing",
537 },
538 "CleanAll/unclosedPrefix": {
539 Input: "[Just to annoy people",
540 Mode: SubjectCleanAll,
541 Subject: "[Just to annoy people",
542 },
543 "CleanAll/multiplePrefix": {
544 Input: " Re:Re: [PATCH 1/2][DRAFT] " + expectedSubject + " ",
545 Mode: SubjectCleanAll,
546 Prefix: "Re:Re: [PATCH 1/2][DRAFT] ",
547 Subject: expectedSubject,
548 },
549 "CleanPatchOnly/patchPrefix": {
550 Input: "[PATCH] " + expectedSubject,
551 Mode: SubjectCleanPatchOnly,
552 Prefix: "[PATCH] ",
553 Subject: expectedSubject,
554 },
555 "CleanPatchOnly/mixedPrefix": {
556 Input: "[PATCH] [TICKET-123] " + expectedSubject,
557 Mode: SubjectCleanPatchOnly,
558 Prefix: "[PATCH] ",
559 Subject: "[TICKET-123] " + expectedSubject,
560 },
561 "CleanPatchOnly/multiplePrefix": {
562 Input: "Re:Re: [PATCH 1/2][DRAFT] " + expectedSubject,
563 Mode: SubjectCleanPatchOnly,
564 Prefix: "Re:Re: [PATCH 1/2]",
565 Subject: "[DRAFT] " + expectedSubject,
566 },
567 "CleanWhitespace/leadingSpace": {
568 Input: " [PATCH] " + expectedSubject,
569 Mode: SubjectCleanWhitespace,
570 Subject: "[PATCH] " + expectedSubject,
571 },
572 "CleanWhitespace/trailingSpace": {
573 Input: "[PATCH] " + expectedSubject + " ",
574 Mode: SubjectCleanWhitespace,
575 Subject: "[PATCH] " + expectedSubject,
576 },
577 }
578
579 for name, test := range tests {
580 t.Run(name, func(t *testing.T) {
581 prefix, subject := cleanSubject(test.Input, test.Mode)
582 if prefix != test.Prefix {
583 t.Errorf("incorrect prefix: expected %q, actual %q", test.Prefix, prefix)
584 }
585 if subject != test.Subject {
586 t.Errorf("incorrect subject: expected %q, actual %q", test.Subject, subject)
587 }
588 })
589 }
590}