main
range_diff_test.go
Eric Bower
·
2026-02-25
1package patchbin
2
3import (
4 "fmt"
5 "strings"
6 "testing"
7
8 "github.com/picosh/patchbin/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 removed file 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 if !strings.Contains(actual, "1: 33c682a = 1: 33c682a chore: add torch and create random tensor") {
140 t.Fatal("expected first commit to be equal")
141 }
142 if !strings.Contains(actual, "2: 22dde12 ! 2: dce20e7 docs: readme") {
143 t.Fatal("expected second commit to show changed marker")
144 }
145 if !strings.Contains(actual, "~ README.md") {
146 t.Fatal("expected changed file README.md")
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, "-: ------- >") {
296 t.Fatal("expected added commit marker")
297 }
298 if !strings.Contains(actual, "ccddee1") {
299 t.Fatal("expected commit ccddee1 in output")
300 }
301}
302
303// changed message
304/*
305 1: $(test_oid t1) = 1: $(test_oid m1) s/5/A/
306 2: $(test_oid t2) ! 2: $(test_oid m2) s/4/A/
307 @@ Metadata
308 Z ## Commit message ##
309 Z s/4/A/
310 Z
311 + Also a silly comment here!
312 +
313 Z ## file ##
314 Z@@
315 Z 1
316 3: $(test_oid t3) = 3: $(test_oid m3) s/11/B/
317 4: $(test_oid t4) = 4: $(test_oid m4) s/12/B/
318*/
319func TestRangeDiffChangedMessage(t *testing.T) {
320 actual := cmp("a_b_reorder.patch", "a_c_changed_message.patch")
321 if !strings.Contains(actual, "1: 33c682a = 1: 33c682a") {
322 t.Fatal("expected first commit to be equal")
323 }
324 if !strings.Contains(actual, "2: 22dde12 ! 2: ddeeff1") {
325 t.Fatal("expected second commit to show diff marker due to message change")
326 }
327}
328
329func TestRangeDiffEmptyPatchset(t *testing.T) {
330 a, err := fixtures.Fixtures.Open("a_b_reorder.patch")
331 bail(err)
332 aPatches, err := ParsePatchset(a)
333 bail(err)
334 bPatches := []*Patch{}
335
336 actual := RangeDiff(aPatches, bPatches)
337 result := RangeDiffToStr(actual)
338
339 if len(aPatches) != 2 {
340 t.Fatalf("expected 2 patches in a, got %d", len(aPatches))
341 }
342 if !strings.Contains(result, "< -:") {
343 t.Fatal("expected removed commit markers when comparing to empty patchset")
344 }
345 if !strings.Contains(result, "1: 33c682a < -:") {
346 t.Fatal("expected first commit to show as removed")
347 }
348 if !strings.Contains(result, "2: 22dde12 < -:") {
349 t.Fatal("expected second commit to show as removed")
350 }
351}
352
353func TestRangeDiffEmptyToNonEmpty(t *testing.T) {
354 b, err := fixtures.Fixtures.Open("a_b_reorder.patch")
355 bail(err)
356 bPatches, err := ParsePatchset(b)
357 bail(err)
358 aPatches := []*Patch{}
359
360 actual := RangeDiff(aPatches, bPatches)
361 result := RangeDiffToStr(actual)
362
363 if len(bPatches) != 2 {
364 t.Fatalf("expected 2 patches in b, got %d", len(bPatches))
365 }
366 if !strings.Contains(result, "> 1:") {
367 t.Fatal("expected added commit markers when comparing from empty patchset")
368 }
369 if !strings.Contains(result, "-: ------- > 1: 33c682a") {
370 t.Fatal("expected first commit to show as added")
371 }
372}
373
374func TestRangeDiffSquashedCommits(t *testing.T) {
375 actual := cmp("a_b_reorder.patch", "a_c_squashed.patch")
376
377 if !strings.Contains(actual, "< -:") {
378 t.Fatal("expected at least one commit to show as removed (squashed away)")
379 }
380 if !strings.Contains(actual, "aabbccd") {
381 t.Fatal("expected squashed commit sha in output")
382 }
383}
384
385func TestRangeDiffSplitCommits(t *testing.T) {
386 actual := cmp("a_b_reorder.patch", "a_c_split.patch")
387
388 if !strings.Contains(actual, "-: ------- >") {
389 t.Fatal("expected added commit marker for split commit")
390 }
391 if !strings.Contains(actual, "aabb112") {
392 t.Fatal("expected new split commit sha (aabb112) in output")
393 }
394 if !strings.Contains(actual, "33c682a") {
395 t.Fatal("expected original commit sha in output")
396 }
397}
398
399func TestRangeDiffDifferentAuthor(t *testing.T) {
400 actual := cmp("a_b_reorder.patch", "a_c_different_author.patch")
401
402 if !strings.Contains(actual, "!") {
403 t.Fatal("expected diff marker (!) for commits with different author")
404 }
405 if !strings.Contains(actual, "33c682a") && !strings.Contains(actual, "22dde12") {
406 t.Fatal("expected commit shas in output")
407 }
408}
409
410func TestRangeDiffMultipleFilesInCommit(t *testing.T) {
411 actual := cmp("a_b_reorder.patch", "a_c_multi_file_change.patch")
412
413 if !strings.Contains(actual, "1: 33c682a = 1: 33c682a") {
414 t.Fatal("expected first commit to be equal")
415 }
416 if !strings.Contains(actual, "2: 22dde12 ! 2: bbccdd1") {
417 t.Fatal("expected second commit to show diff marker")
418 }
419 if !strings.Contains(actual, "CONTRIBUTING.md") {
420 t.Fatal("expected CONTRIBUTING.md in diff output")
421 }
422 if !strings.Contains(actual, "LICENSE.md") {
423 t.Fatal("expected LICENSE.md in diff output")
424 }
425}
426
427func TestRangeDiffIgnoresContextLines(t *testing.T) {
428 actual := cmp("context_lines_v1.patch", "context_lines_v2.patch")
429
430 if !strings.Contains(actual, "=") {
431 t.Fatal("expected equal marker (=) since +/- lines are identical")
432 }
433 if strings.Contains(actual, "!") {
434 t.Fatal("should not show diff marker (!) when only context lines differ")
435 }
436 if strings.Contains(actual, "old_value") || strings.Contains(actual, "new_value") {
437 t.Fatal("should not have file diff output when changes are equal")
438 }
439}
440
441func TestRangeDiffNormalizesHunkHeaders(t *testing.T) {
442 actual := cmp("hunk_header_v1.patch", "hunk_header_v2.patch")
443
444 if !strings.Contains(actual, "=") {
445 t.Fatal("expected equal marker (=) since changes are identical despite different hunk headers")
446 }
447 if strings.Contains(actual, "!") {
448 t.Fatal("should not show diff marker (!) when only hunk header line numbers differ")
449 }
450 if strings.Contains(actual, "@@ server.go") {
451 t.Fatal("should not have file diff output when changes are equal")
452 }
453}