repos / git-pr

a self-hosted git collaboration server
git clone https://github.com/picosh/git-pr.git

Eric Bower  ·  2025-12-13

range_diff_test.go

  1package git
  2
  3import (
  4	"fmt"
  5	"strings"
  6	"testing"
  7
  8	"github.com/picosh/git-pr/fixtures"
  9)
 10
 11func bail(err error) {
 12	if err != nil {
 13		panic(bail)
 14	}
 15}
 16
 17func cmp(afile, bfile string) string {
 18	a, err := fixtures.Fixtures.Open(afile)
 19	bail(err)
 20	b, err := fixtures.Fixtures.Open(bfile)
 21	bail(err)
 22	aPatches, err := ParsePatchset(a)
 23	bail(err)
 24	bPatches, err := ParsePatchset(b)
 25	bail(err)
 26	actual := RangeDiff(aPatches, bPatches)
 27	return RangeDiffToStr(actual)
 28}
 29
 30func fail(expected, actual string) string {
 31	return fmt.Sprintf("expected:[\n%s] actual:[\n%s]", expected, actual)
 32}
 33
 34// https://git.kernel.org/tree/t/t3206-range-diff.sh?id=d19b6cd2dd72dc811f19df4b32c7ed223256c3ee
 35
 36// simple A..B A..C (unmodified)
 37/*
 38	1:  $(test_oid t1) = 1:  $(test_oid u1) s/5/A/
 39	2:  $(test_oid t2) = 2:  $(test_oid u2) s/4/A/
 40	3:  $(test_oid t3) = 3:  $(test_oid u3) s/11/B/
 41	4:  $(test_oid t4) = 4:  $(test_oid u4) s/12/B/
 42*/
 43func TestRangeDiffUnmodified(t *testing.T) {
 44	actual := cmp("a_b.patch", "a_c.patch")
 45	expected := "1:  33c682a = 1:  1668484 chore: add torch and create random tensor\n"
 46	if expected != actual {
 47		t.Fatal(fail(expected, actual))
 48	}
 49}
 50
 51// trivial reordering
 52/*
 53	1:  $(test_oid t1) = 1:  $(test_oid r1) s/5/A/
 54	3:  $(test_oid t3) = 2:  $(test_oid r2) s/11/B/
 55	4:  $(test_oid t4) = 3:  $(test_oid r3) s/12/B/
 56	2:  $(test_oid t2) = 4:  $(test_oid r4) s/4/A/
 57*/
 58func TestRangeDiffTrivialReordering(t *testing.T) {
 59	actual := cmp("a_b_reorder.patch", "a_c_reorder.patch")
 60	expected := `2:  22dde12 = 1:  7dbb94c docs: readme
 611:  33c682a = 2:  ad17587 chore: add torch and create random tensor
 62`
 63	if expected != actual {
 64		t.Fatal(fail(expected, actual))
 65	}
 66}
 67
 68// removed commit
 69/*
 70	1:  $(test_oid t1) = 1:  $(test_oid d1) s/5/A/
 71	2:  $(test_oid t2) < -:  $(test_oid __) s/4/A/
 72	3:  $(test_oid t3) = 2:  $(test_oid d2) s/11/B/
 73	4:  $(test_oid t4) = 3:  $(test_oid d3) s/12/B/
 74*/
 75func TestRangeDiffRemovedCommit(t *testing.T) {
 76	actual := cmp("a_b_reorder.patch", "a_c_rm_commit.patch")
 77	if !strings.Contains(actual, "1:  33c682a < -:  ------- chore: add torch and create random tensor") {
 78		t.Fatal("expected removed commit header not found")
 79	}
 80	if !strings.Contains(actual, "2:  22dde12 = 1:  7dbb94c docs: readme") {
 81		t.Fatal("expected equal commit header not found")
 82	}
 83	if !strings.Contains(actual, "requirements.txt") {
 84		t.Fatal("expected file diff for removed commit")
 85	}
 86}
 87
 88// added commit
 89/*
 90	1:  $(test_oid t1) = 1:  $(test_oid a1) s/5/A/
 91	2:  $(test_oid t2) = 2:  $(test_oid a2) s/4/A/
 92	-:  $(test_oid __) > 3:  $(test_oid a3) s/6/A/
 93	3:  $(test_oid t3) = 4:  $(test_oid a4) s/11/B/
 94	4:  $(test_oid t4) = 5:  $(test_oid a5) s/12/B/
 95*/
 96func TestRangeDiffAddedCommit(t *testing.T) {
 97	actual := cmp("a_b_reorder.patch", "a_c_added_commit.patch")
 98	if !strings.Contains(actual, "1:  33c682a = 1:  33c682a chore: add torch and create random tensor") {
 99		t.Fatal("expected first equal commit header not found")
100	}
101	if !strings.Contains(actual, "2:  22dde12 = 2:  22dde12 docs: readme") {
102		t.Fatal("expected second equal commit header not found")
103	}
104	if !strings.Contains(actual, "-:  ------- > 3:  b248060 chore: make tensor 6x6") {
105		t.Fatal("expected added commit header not found")
106	}
107	if !strings.Contains(actual, "train.py") {
108		t.Fatal("expected file diff for added commit")
109	}
110}
111
112// changed commit
113/*
114	1:  $(test_oid t1) = 1:  $(test_oid c1) s/5/A/
115	2:  $(test_oid t2) = 2:  $(test_oid c2) s/4/A/
116	3:  $(test_oid t3) ! 3:  $(test_oid c3) s/11/B/
117	    @@ file: A
118	      9
119	      10
120	     -11
121	    -+B
122	    ++BB
123	      12
124	      13
125	      14
126	4:  $(test_oid t4) ! 4:  $(test_oid c4) s/12/B/
127	    @@ file
128	     @@ file: A
129	      9
130	      10
131	    - B
132	    + BB
133	     -12
134	     +B
135	      13
136*/
137func TestRangeDiffChangedCommit(t *testing.T) {
138	actual := cmp("a_b_reorder.patch", "a_c_changed_commit.patch")
139	// os.WriteFile("fixtures/expected_commit_changed.txt", []byte(actual), 0644)
140	fp, err := fixtures.Fixtures.ReadFile("expected_commit_changed.txt")
141	if err != nil {
142		t.Fatal("file not found")
143	}
144	expected := string(fp)
145	if strings.TrimSpace(expected) != strings.TrimSpace(actual) {
146		t.Fatal(fail(expected, actual))
147	}
148}
149
150// renamed file
151/*
152	1:  $(test_oid t1) = 1:  $(test_oid n1) s/5/A/
153	2:  $(test_oid t2) ! 2:  $(test_oid n2) s/4/A/
154	    @@ Metadata
155	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
156	    Z
157	    Z ## Commit message ##
158	    -    s/4/A/
159	    +    s/4/A/ + rename file
160	    Z
161	    - ## file ##
162	    + ## file => renamed-file ##
163	    Z@@
164	    Z 1
165	    Z 2
166	3:  $(test_oid t3) ! 3:  $(test_oid n3) s/11/B/
167	    @@ Metadata
168	    Z ## Commit message ##
169	    Z    s/11/B/
170	    Z
171	    - ## file ##
172	    -@@ file: A
173	    + ## renamed-file ##
174	    +@@ renamed-file: A
175	    Z 8
176	    Z 9
177	    Z 10
178	4:  $(test_oid t4) ! 4:  $(test_oid n4) s/12/B/
179	    @@ Metadata
180	    Z ## Commit message ##
181	    Z    s/12/B/
182	    Z
183	    - ## file ##
184	    -@@ file: A
185	    + ## renamed-file ##
186	    +@@ renamed-file: A
187	    Z 9
188	    Z 10
189	    Z B
190*/
191func TestRangeDiffRenamedFile(t *testing.T) {
192	actual := cmp("a_b_reorder.patch", "a_c_renamed_file.patch")
193	if !strings.Contains(actual, "1:  33c682a = 1:  33c682a") {
194		t.Fatal("expected first commit to be equal")
195	}
196	if !strings.Contains(actual, "2:  22dde12 ! 2:  aabbcc1") {
197		t.Fatal("expected second commit to show diff marker")
198	}
199	if !strings.Contains(actual, "DOCS.md") {
200		t.Fatal("expected renamed file DOCS.md in output")
201	}
202}
203
204// file with mode only change
205/*
206	1:  $(test_oid t2) ! 1:  $(test_oid o1) s/4/A/
207	    @@ Metadata
208	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
209	    Z
210	    Z ## Commit message ##
211	    -    s/4/A/
212	    +    s/4/A/ + add other-file
213	    Z
214	    Z ## file ##
215	    Z@@
216	    @@ file
217	    Z A
218	    Z 6
219	    Z 7
220	    +
221	    + ## other-file (new) ##
222	2:  $(test_oid t3) ! 2:  $(test_oid o2) s/11/B/
223	    @@ Metadata
224	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
225	    Z
226	    Z ## Commit message ##
227	    -    s/11/B/
228	    +    s/11/B/ + mode change other-file
229	    Z
230	    Z ## file ##
231	    Z@@ file: A
232	    @@ file: A
233	    Z 12
234	    Z 13
235	    Z 14
236	    +
237	    + ## other-file (mode change 100644 => 100755) ##
238	3:  $(test_oid t4) = 3:  $(test_oid o3) s/12/B/
239*/
240func TestRangeDiffFileWithModeOnlyChange(t *testing.T) {
241	actual := cmp("a_b_reorder.patch", "a_c_mode_change.patch")
242	if !strings.Contains(actual, "1:  33c682a ! 1:  33c682a") {
243		t.Fatal("expected first commit to show diff marker due to new file")
244	}
245	if !strings.Contains(actual, "run.sh") {
246		t.Fatal("expected run.sh script in output")
247	}
248}
249
250// file added and later removed
251/*
252	1:  $(test_oid t1) = 1:  $(test_oid s1) s/5/A/
253	2:  $(test_oid t2) ! 2:  $(test_oid s2) s/4/A/
254	    @@ Metadata
255	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
256	    Z
257	    Z ## Commit message ##
258	    -    s/4/A/
259	    +    s/4/A/ + new-file
260	    Z
261	    Z ## file ##
262	    Z@@
263	    @@ file
264	    Z A
265	    Z 6
266	    Z 7
267	    +
268	    + ## new-file (new) ##
269	3:  $(test_oid t3) ! 3:  $(test_oid s3) s/11/B/
270	    @@ Metadata
271	    ZAuthor: Thomas Rast <trast@inf.ethz.ch>
272	    Z
273	    Z ## Commit message ##
274	    -    s/11/B/
275	    +    s/11/B/ + remove file
276	    Z
277	    Z ## file ##
278	    Z@@ file: A
279	    @@ file: A
280	    Z 12
281	    Z 13
282	    Z 14
283	    +
284	    + ## new-file (deleted) ##
285	4:  $(test_oid t4) = 4:  $(test_oid s4) s/12/B/
286*/
287func TestRangeDiffFileAddedThenRemoved(t *testing.T) {
288	actual := cmp("a_b_reorder.patch", "a_c_file_added_removed.patch")
289	if !strings.Contains(actual, "1:  33c682a = 1:  33c682a") {
290		t.Fatal("expected first commit to be equal")
291	}
292	if !strings.Contains(actual, "temp.txt") {
293		t.Fatal("expected temp.txt in output")
294	}
295	if !strings.Contains(actual, "-:  ------- > 3:  ccddee1") {
296		t.Fatal("expected third commit to be added")
297	}
298}
299
300// changed message
301/*
302	1:  $(test_oid t1) = 1:  $(test_oid m1) s/5/A/
303	2:  $(test_oid t2) ! 2:  $(test_oid m2) s/4/A/
304	    @@ Metadata
305	    Z ## Commit message ##
306	    Z    s/4/A/
307	    Z
308	    +    Also a silly comment here!
309	    +
310	    Z ## file ##
311	    Z@@
312	    Z 1
313	3:  $(test_oid t3) = 3:  $(test_oid m3) s/11/B/
314	4:  $(test_oid t4) = 4:  $(test_oid m4) s/12/B/
315*/
316func TestRangeDiffChangedMessage(t *testing.T) {
317	actual := cmp("a_b_reorder.patch", "a_c_changed_message.patch")
318	if !strings.Contains(actual, "1:  33c682a = 1:  33c682a") {
319		t.Fatal("expected first commit to be equal")
320	}
321	if !strings.Contains(actual, "2:  22dde12 ! 2:  ddeeff1") {
322		t.Fatal("expected second commit to show diff marker due to message change")
323	}
324}
325
326func TestRangeDiffEmptyPatchset(t *testing.T) {
327	a, err := fixtures.Fixtures.Open("a_b_reorder.patch")
328	bail(err)
329	aPatches, err := ParsePatchset(a)
330	bail(err)
331	bPatches := []*Patch{}
332
333	actual := RangeDiff(aPatches, bPatches)
334	result := RangeDiffToStr(actual)
335
336	if len(aPatches) != 2 {
337		t.Fatalf("expected 2 patches in a, got %d", len(aPatches))
338	}
339	if !strings.Contains(result, "< -:") {
340		t.Fatal("expected removed commit markers when comparing to empty patchset")
341	}
342	if !strings.Contains(result, "1:  33c682a < -:") {
343		t.Fatal("expected first commit to show as removed")
344	}
345	if !strings.Contains(result, "2:  22dde12 < -:") {
346		t.Fatal("expected second commit to show as removed")
347	}
348}
349
350func TestRangeDiffEmptyToNonEmpty(t *testing.T) {
351	b, err := fixtures.Fixtures.Open("a_b_reorder.patch")
352	bail(err)
353	bPatches, err := ParsePatchset(b)
354	bail(err)
355	aPatches := []*Patch{}
356
357	actual := RangeDiff(aPatches, bPatches)
358	result := RangeDiffToStr(actual)
359
360	if len(bPatches) != 2 {
361		t.Fatalf("expected 2 patches in b, got %d", len(bPatches))
362	}
363	if !strings.Contains(result, "> 1:") {
364		t.Fatal("expected added commit markers when comparing from empty patchset")
365	}
366	if !strings.Contains(result, "-:  ------- > 1:  33c682a") {
367		t.Fatal("expected first commit to show as added")
368	}
369}
370
371func TestRangeDiffSquashedCommits(t *testing.T) {
372	actual := cmp("a_b_reorder.patch", "a_c_squashed.patch")
373
374	if !strings.Contains(actual, "< -:") {
375		t.Fatal("expected at least one commit to show as removed (squashed away)")
376	}
377	if !strings.Contains(actual, "aabbccd") {
378		t.Fatal("expected squashed commit sha in output")
379	}
380}
381
382func TestRangeDiffSplitCommits(t *testing.T) {
383	actual := cmp("a_b_reorder.patch", "a_c_split.patch")
384
385	if !strings.Contains(actual, "-:  ------- >") {
386		t.Fatal("expected added commit marker for split commit")
387	}
388	if !strings.Contains(actual, "aabb112") {
389		t.Fatal("expected new split commit sha (aabb112) in output")
390	}
391	if !strings.Contains(actual, "33c682a") {
392		t.Fatal("expected original commit sha in output")
393	}
394}
395
396func TestRangeDiffDifferentAuthor(t *testing.T) {
397	actual := cmp("a_b_reorder.patch", "a_c_different_author.patch")
398
399	if !strings.Contains(actual, "!") {
400		t.Fatal("expected diff marker (!) for commits with different author")
401	}
402	if !strings.Contains(actual, "33c682a") && !strings.Contains(actual, "22dde12") {
403		t.Fatal("expected commit shas in output")
404	}
405}
406
407func TestRangeDiffMultipleFilesInCommit(t *testing.T) {
408	actual := cmp("a_b_reorder.patch", "a_c_multi_file_change.patch")
409
410	if !strings.Contains(actual, "1:  33c682a = 1:  33c682a") {
411		t.Fatal("expected first commit to be equal")
412	}
413	if !strings.Contains(actual, "2:  22dde12 ! 2:  bbccdd1") {
414		t.Fatal("expected second commit to show diff marker")
415	}
416	if !strings.Contains(actual, "CONTRIBUTING.md") {
417		t.Fatal("expected CONTRIBUTING.md in diff output")
418	}
419	if !strings.Contains(actual, "LICENSE.md") {
420		t.Fatal("expected LICENSE.md in diff output")
421	}
422}
423
424func TestRangeDiffIgnoresContextLines(t *testing.T) {
425	actual := cmp("context_lines_v1.patch", "context_lines_v2.patch")
426
427	if !strings.Contains(actual, "=") {
428		t.Fatal("expected equal marker (=) since +/- lines are identical")
429	}
430	if strings.Contains(actual, "!") {
431		t.Fatal("should not show diff marker (!) when only context lines differ")
432	}
433	if strings.Contains(actual, "old_value") || strings.Contains(actual, "new_value") {
434		t.Fatal("should not have file diff output when changes are equal")
435	}
436}
437
438func TestRangeDiffNormalizesHunkHeaders(t *testing.T) {
439	actual := cmp("hunk_header_v1.patch", "hunk_header_v2.patch")
440
441	if !strings.Contains(actual, "=") {
442		t.Fatal("expected equal marker (=) since changes are identical despite different hunk headers")
443	}
444	if strings.Contains(actual, "!") {
445		t.Fatal("should not show diff marker (!) when only hunk header line numbers differ")
446	}
447	if strings.Contains(actual, "@@ server.go") {
448		t.Fatal("should not have file diff output when changes are equal")
449	}
450}