cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.79.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/git/changes_test.go

942lines · modecode

1package git_test
2
3import (
4 "fmt"
5 "log/slog"
6 "os"
7 "strings"
8 "testing"
9
10 "github.com/neilotoole/slogt"
11 "github.com/stretchr/testify/require"
12
13 "github.com/cloudflare/pint/internal/git"
14)
15
16func debugGitRun(t *testing.T) git.CommandRunner {
17 return func(args ...string) ([]byte, error) {
18 out, err := git.RunGit(args...)
19 if err == nil {
20 if len(out) == 0 {
21 t.Logf("%s ~> no stdout", strings.Join(args, " "))
22 } else {
23 t.Logf("%s\n---\n%s---", strings.Join(args, " "), string(out))
24 }
25 }
26 return out, err
27 }
28}
29
30func mustRun(t *testing.T, args ...string) {
31 _, err := debugGitRun(t)(args...)
32 require.NoError(t, err, strings.Join(args, " "))
33}
34
35func gitCommit(t *testing.T, message string) {
36 t.Setenv("GIT_AUTHOR_NAME", "pint")
37 t.Setenv("GIT_AUTHOR_EMAIL", "pint@example.com")
38 t.Setenv("GIT_COMMITTER_NAME", "pint")
39 t.Setenv("GIT_COMMITTER_EMAIL", "pint")
40 mustRun(t, "commit", "-am", "commit "+message)
41}
42
43func TestChanges(t *testing.T) {
44 type testCaseT struct {
45 setup func(t *testing.T) git.CommandRunner
46 title string
47 err string
48 changes []*git.FileChange
49 }
50
51 testCases := []testCaseT{
52 {
53 title: "git log error",
54 setup: func(_ *testing.T) git.CommandRunner {
55 cmd := func(args ...string) ([]byte, error) {
56 return nil, fmt.Errorf("mock git error: %v", args)
57 }
58 return cmd
59 },
60 changes: nil,
61 err: "failed to get the list of modified files from git: mock git error: [log --reverse --no-merges --first-parent --format=%H --name-status main..HEAD]",
62 },
63 {
64 title: "chmod",
65 setup: func(t *testing.T) git.CommandRunner {
66 mustRun(t, "init", "--initial-branch=main", ".")
67 require.NoError(t, os.WriteFile("index.txt", []byte("foo"), 0o644))
68 mustRun(t, "add", "index.txt")
69 gitCommit(t, "init")
70
71 mustRun(t, "checkout", "-b", "v2")
72 require.NoError(t, os.Chmod("index.txt", 0o755))
73 mustRun(t, "add", "index.txt")
74 gitCommit(t, "chmod")
75
76 return debugGitRun(t)
77 },
78 changes: []*git.FileChange{
79 {
80 Commits: []string{"1"},
81 Path: git.PathDiff{
82 Before: git.Path{
83 Name: "index.txt",
84 Type: git.File,
85 },
86 After: git.Path{
87 Name: "index.txt",
88 Type: git.File,
89 },
90 },
91 Body: git.BodyDiff{
92 Before: []byte("foo"),
93 After: []byte("foo"),
94 ModifiedLines: []int{},
95 },
96 },
97 },
98 err: "",
99 },
100 {
101 title: "dir -> file",
102 setup: func(t *testing.T) git.CommandRunner {
103 mustRun(t, "init", "--initial-branch=main", ".")
104 require.NoError(t, os.Mkdir("index.txt", 0o755))
105 require.NoError(t, os.WriteFile("index.txt/.keep", []byte("keep"), 0o644))
106 mustRun(t, "add", "index.txt")
107 gitCommit(t, "init")
108
109 mustRun(t, "checkout", "-b", "v2")
110 require.NoError(t, os.RemoveAll("index.txt"))
111 require.NoError(t, os.WriteFile("index.txt", []byte("foo"), 0o644))
112 mustRun(t, "add", "index.txt")
113 gitCommit(t, "chmod")
114
115 return debugGitRun(t)
116 },
117 changes: []*git.FileChange{
118 {
119 Commits: []string{"1"},
120 Path: git.PathDiff{
121 Before: git.Path{
122 Name: "index.txt",
123 Type: git.Dir,
124 },
125 After: git.Path{
126 Name: "index.txt",
127 Type: git.File,
128 },
129 },
130 Body: git.BodyDiff{
131 Before: nil,
132 After: []byte("foo"),
133 ModifiedLines: []int{1},
134 },
135 },
136 {
137 Commits: []string{"1"},
138 Path: git.PathDiff{
139 Before: git.Path{
140 Name: "index.txt/.keep",
141 Type: git.File,
142 },
143 After: git.Path{
144 Name: "index.txt/.keep",
145 Type: git.Missing,
146 },
147 },
148 Body: git.BodyDiff{
149 Before: []byte("keep"),
150 After: nil,
151 ModifiedLines: []int{1},
152 },
153 },
154 },
155 err: "",
156 },
157 {
158 title: "delete and re-add",
159 setup: func(t *testing.T) git.CommandRunner {
160 mustRun(t, "init", "--initial-branch=main", ".")
161 require.NoError(t, os.WriteFile("index.txt", []byte("foo"), 0o644))
162 mustRun(t, "add", "index.txt")
163 gitCommit(t, "init")
164
165 mustRun(t, "checkout", "-b", "v2")
166 require.NoError(t, os.Remove("index.txt"))
167 mustRun(t, "add", "index.txt")
168 gitCommit(t, "rm")
169 require.NoError(t, os.WriteFile("index.txt", []byte("foo"), 0o644))
170 mustRun(t, "add", "index.txt")
171 gitCommit(t, "add")
172
173 return debugGitRun(t)
174 },
175 changes: []*git.FileChange{
176 {
177 Commits: []string{"1", "2"},
178 Path: git.PathDiff{
179 Before: git.Path{
180 Name: "index.txt",
181 Type: git.File,
182 },
183 After: git.Path{
184 Name: "index.txt",
185 Type: git.File,
186 },
187 },
188 Body: git.BodyDiff{
189 Before: []byte("foo"),
190 After: []byte("foo"),
191 ModifiedLines: []int{},
192 },
193 },
194 },
195 err: "",
196 },
197 {
198 title: "file -> symlink",
199 setup: func(t *testing.T) git.CommandRunner {
200 mustRun(t, "init", "--initial-branch=main", ".")
201 require.NoError(t, os.WriteFile("index.txt", []byte("foo\n1\n"), 0o644))
202 mustRun(t, "add", "index.txt")
203 require.NoError(t, os.WriteFile("second file.txt", []byte("bar\n1\n"), 0o644))
204 mustRun(t, "add", "second file.txt")
205 gitCommit(t, "init")
206
207 mustRun(t, "checkout", "-b", "v2")
208 require.NoError(t, os.Remove("second file.txt"))
209 require.NoError(t, os.Symlink("index.txt", "second file.txt"))
210 mustRun(t, "add", "second file.txt")
211 gitCommit(t, "symlink")
212
213 return debugGitRun(t)
214 },
215 changes: []*git.FileChange{
216 {
217 Commits: []string{"1"},
218 Path: git.PathDiff{
219 Before: git.Path{
220 Name: "second file.txt",
221 Type: git.File,
222 },
223 After: git.Path{
224 Name: "second file.txt",
225 Type: git.Symlink,
226 SymlinkTarget: "index.txt",
227 },
228 },
229 Body: git.BodyDiff{
230 Before: []byte("bar\n1\n"),
231 After: []byte("foo\n1\n"),
232 ModifiedLines: []int{1, 2},
233 },
234 },
235 },
236 err: "",
237 },
238 {
239 title: "rename partial",
240 setup: func(t *testing.T) git.CommandRunner {
241 mustRun(t, "init", "--initial-branch=main", ".")
242 require.NoError(t, os.WriteFile("index.txt", []byte("1\n2\n3\n4\n5\n6\n7\n8\n9\n"), 0o644))
243 mustRun(t, "add", "index.txt")
244 gitCommit(t, "init")
245
246 mustRun(t, "checkout", "-b", "v2")
247 mustRun(t, "mv", "index.txt", "second.txt")
248 require.NoError(t, os.WriteFile("second.txt", []byte("1\n2\n3\n4\n5\nX\nX\nX\nX\n"), 0o644))
249 mustRun(t, "add", "second.txt")
250 gitCommit(t, "mv")
251
252 return debugGitRun(t)
253 },
254 changes: []*git.FileChange{
255 {
256 Commits: []string{"1"},
257 Path: git.PathDiff{
258 Before: git.Path{
259 Name: "index.txt",
260 Type: git.File,
261 },
262 After: git.Path{
263 Name: "second.txt",
264 Type: git.File,
265 },
266 },
267 Body: git.BodyDiff{
268 Before: []byte("1\n2\n3\n4\n5\n6\n7\n8\n9\n"),
269 After: []byte("1\n2\n3\n4\n5\nX\nX\nX\nX\n"),
270 ModifiedLines: []int{6, 7, 8, 9},
271 },
272 },
273 },
274 err: "",
275 },
276 {
277 title: "rename 100% and edit",
278 setup: func(t *testing.T) git.CommandRunner {
279 mustRun(t, "init", "--initial-branch=main", ".")
280 require.NoError(t, os.WriteFile("index.txt", []byte("1\n2\n3\n4\n5\n6\n7\n8\n9\n"), 0o644))
281 mustRun(t, "add", "index.txt")
282 gitCommit(t, "init")
283
284 mustRun(t, "checkout", "-b", "v2")
285 mustRun(t, "mv", "index.txt", "second.txt")
286 gitCommit(t, "mv")
287 require.NoError(t, os.WriteFile("second.txt", []byte("1\n2\n3\n4\n5\nX\n7\n8\n9\n"), 0o644))
288 mustRun(t, "add", "second.txt")
289 gitCommit(t, "edit")
290
291 return debugGitRun(t)
292 },
293 changes: []*git.FileChange{
294 {
295 Commits: []string{"1", "2"},
296 Path: git.PathDiff{
297 Before: git.Path{
298 Name: "index.txt",
299 Type: git.File,
300 },
301 After: git.Path{
302 Name: "second.txt",
303 Type: git.File,
304 },
305 },
306 Body: git.BodyDiff{
307 Before: []byte("1\n2\n3\n4\n5\n6\n7\n8\n9\n"),
308 After: []byte("1\n2\n3\n4\n5\nX\n7\n8\n9\n"),
309 ModifiedLines: []int{6},
310 },
311 },
312 },
313 err: "",
314 },
315 {
316 title: "add file, add another",
317 setup: func(t *testing.T) git.CommandRunner {
318 mustRun(t, "init", "--initial-branch=main", ".")
319 require.NoError(t, os.WriteFile("index.txt", []byte("foo"), 0o644))
320 mustRun(t, "add", "index.txt")
321 gitCommit(t, "init")
322
323 mustRun(t, "checkout", "-b", "v2")
324 require.NoError(t, os.WriteFile("second.txt", []byte("second"), 0o644))
325 mustRun(t, "add", "second.txt")
326 require.NoError(t, os.WriteFile("third.txt", []byte("third"), 0o644))
327 mustRun(t, "add", "third.txt")
328 gitCommit(t, "add two more")
329
330 return debugGitRun(t)
331 },
332 changes: []*git.FileChange{
333 {
334 Commits: []string{"1"},
335 Path: git.PathDiff{
336 Before: git.Path{
337 Name: "",
338 Type: git.Missing,
339 },
340 After: git.Path{
341 Name: "second.txt",
342 Type: git.File,
343 },
344 },
345 Body: git.BodyDiff{
346 After: []byte("second"),
347 ModifiedLines: []int{1},
348 },
349 },
350 {
351 Commits: []string{"1"},
352 Path: git.PathDiff{
353 Before: git.Path{
354 Name: "",
355 Type: git.Missing,
356 },
357 After: git.Path{
358 Name: "third.txt",
359 Type: git.File,
360 },
361 },
362 Body: git.BodyDiff{
363 After: []byte("third"),
364 ModifiedLines: []int{1},
365 },
366 },
367 },
368 err: "",
369 },
370 {
371 title: "delete file",
372 setup: func(t *testing.T) git.CommandRunner {
373 mustRun(t, "init", "--initial-branch=main", ".")
374 require.NoError(t, os.WriteFile("index.txt", []byte("foo"), 0o644))
375 mustRun(t, "add", "index.txt")
376 require.NoError(t, os.WriteFile("second.txt", []byte("second"), 0o644))
377 mustRun(t, "add", "second.txt")
378 gitCommit(t, "init")
379
380 mustRun(t, "checkout", "-b", "v2")
381 require.NoError(t, os.Remove("second.txt"))
382 mustRun(t, "add", "second.txt")
383 gitCommit(t, "rm second")
384
385 return debugGitRun(t)
386 },
387 changes: []*git.FileChange{
388 {
389 Commits: []string{"1"},
390 Path: git.PathDiff{
391 Before: git.Path{
392 Name: "second.txt",
393 Type: git.File,
394 },
395 After: git.Path{
396 Name: "second.txt",
397 Type: git.Missing,
398 },
399 },
400 Body: git.BodyDiff{
401 Before: []byte("second"),
402 ModifiedLines: []int{1},
403 },
404 },
405 },
406 err: "",
407 },
408 {
409 title: "delete symlink",
410 setup: func(t *testing.T) git.CommandRunner {
411 mustRun(t, "init", "--initial-branch=main", ".")
412 require.NoError(t, os.WriteFile("index.txt", []byte("foo"), 0o644))
413 mustRun(t, "add", "index.txt")
414 require.NoError(t, os.Symlink("index.txt", "second.txt"))
415 mustRun(t, "add", "second.txt")
416 gitCommit(t, "init")
417
418 mustRun(t, "checkout", "-b", "v2")
419 require.NoError(t, os.Remove("second.txt"))
420 mustRun(t, "add", "second.txt")
421 gitCommit(t, "rm second")
422
423 return debugGitRun(t)
424 },
425 changes: []*git.FileChange{
426 {
427 Commits: []string{"1"},
428 Path: git.PathDiff{
429 Before: git.Path{
430 Name: "second.txt",
431 Type: git.Symlink,
432 SymlinkTarget: "index.txt",
433 },
434 After: git.Path{
435 Name: "second.txt",
436 Type: git.Missing,
437 },
438 },
439 Body: git.BodyDiff{
440 Before: []byte("foo"),
441 ModifiedLines: []int{1},
442 },
443 },
444 },
445 err: "",
446 },
447 {
448 title: "delete directory with symlinks",
449 setup: func(t *testing.T) git.CommandRunner {
450 mustRun(t, "init", "--initial-branch=main", ".")
451 require.NoError(t, os.WriteFile("index.txt", []byte("foo"), 0o644))
452 mustRun(t, "add", "index.txt")
453 require.NoError(t, os.Mkdir("dir", 0o755))
454 require.NoError(t, os.Symlink("../index.txt", "dir/first.txt"))
455 require.NoError(t, os.Symlink("../index.txt", "dir/second.txt"))
456 mustRun(t, "add", "dir")
457 gitCommit(t, "init")
458
459 mustRun(t, "checkout", "-b", "v2")
460 require.NoError(t, os.RemoveAll("dir"))
461 mustRun(t, "add", "dir")
462 gitCommit(t, "rm dir")
463
464 return debugGitRun(t)
465 },
466 changes: []*git.FileChange{
467 {
468 Commits: []string{"1"},
469 Path: git.PathDiff{
470 Before: git.Path{
471 Name: "dir/first.txt",
472 Type: git.Symlink,
473 SymlinkTarget: "index.txt",
474 },
475 After: git.Path{
476 Name: "dir/first.txt",
477 Type: git.Missing,
478 },
479 },
480 Body: git.BodyDiff{
481 Before: []byte("foo"),
482 ModifiedLines: []int{1},
483 },
484 },
485 {
486 Commits: []string{"1"},
487 Path: git.PathDiff{
488 Before: git.Path{
489 Name: "dir/second.txt",
490 Type: git.Symlink,
491 SymlinkTarget: "index.txt",
492 },
493 After: git.Path{
494 Name: "dir/second.txt",
495 Type: git.Missing,
496 },
497 },
498 Body: git.BodyDiff{
499 Before: []byte("foo"),
500 ModifiedLines: []int{1},
501 },
502 },
503 },
504 err: "",
505 },
506 {
507 title: "symlink target changed",
508 setup: func(t *testing.T) git.CommandRunner {
509 mustRun(t, "init", "--initial-branch=main", ".")
510 require.NoError(t, os.WriteFile("index.txt", []byte("foo\n1\n"), 0o644))
511 mustRun(t, "add", "index.txt")
512 require.NoError(t, os.WriteFile("second file.txt", []byte("bar\n1\n"), 0o644))
513 mustRun(t, "add", "second file.txt")
514 require.NoError(t, os.Mkdir("dir", 0o755))
515 require.NoError(t, os.Symlink("../index.txt", "dir/first.txt"))
516 require.NoError(t, os.Symlink("../second file.txt", "dir/second.txt"))
517 mustRun(t, "add", "dir")
518 gitCommit(t, "init")
519
520 mustRun(t, "checkout", "-b", "v2")
521 require.NoError(t, os.Remove("dir/second.txt"))
522 require.NoError(t, os.Symlink("first.txt", "dir/second.txt"))
523 mustRun(t, "add", "dir")
524 gitCommit(t, "symlink change")
525
526 return debugGitRun(t)
527 },
528 changes: []*git.FileChange{
529 {
530 Commits: []string{"1"},
531 Path: git.PathDiff{
532 Before: git.Path{
533 Name: "dir/second.txt",
534 Type: git.Symlink,
535 SymlinkTarget: "second file.txt",
536 },
537 After: git.Path{
538 Name: "dir/second.txt",
539 Type: git.Symlink,
540 SymlinkTarget: "index.txt",
541 },
542 },
543 Body: git.BodyDiff{
544 Before: []byte("bar\n1\n"),
545 After: []byte("foo\n1\n"),
546 ModifiedLines: []int{1, 2},
547 },
548 },
549 },
550 err: "",
551 },
552 {
553 title: "rule modified then file renamed",
554 setup: func(t *testing.T) git.CommandRunner {
555 mustRun(t, "init", "--initial-branch=main", ".")
556 require.NoError(t, os.WriteFile("main.txt", []byte("l1\nl2\nl3\n"), 0o644))
557 mustRun(t, "add", "main.txt")
558 gitCommit(t, "init")
559
560 mustRun(t, "checkout", "-b", "v2")
561 require.NoError(t, os.WriteFile("main.txt", []byte("l1\nl3\n"), 0o644))
562 mustRun(t, "add", "main.txt")
563 gitCommit(t, "edit")
564
565 mustRun(t, "mv", "main.txt", "pr.txt")
566 gitCommit(t, "rename")
567
568 return debugGitRun(t)
569 },
570 changes: []*git.FileChange{
571 {
572 Commits: []string{"1", "2"},
573 Path: git.PathDiff{
574 Before: git.Path{
575 Name: "main.txt",
576 Type: git.File,
577 },
578 After: git.Path{
579 Name: "pr.txt",
580 Type: git.File,
581 },
582 },
583 Body: git.BodyDiff{
584 Before: []byte("l1\nl2\nl3\n"),
585 After: []byte("l1\nl3\n"),
586 ModifiedLines: []int{1, 2},
587 },
588 },
589 },
590 err: "",
591 },
592 {
593 title: "directory_renamed",
594 setup: func(t *testing.T) git.CommandRunner {
595 mustRun(t, "init", "--initial-branch=main", ".")
596 require.NoError(t, os.Mkdir("dir1", 0o755))
597 require.NoError(t, os.Mkdir("dir1/rules", 0o755))
598 require.NoError(t, os.WriteFile("dir1/rules/file1.txt", []byte("a1\na2\na3\n"), 0o644))
599 require.NoError(t, os.WriteFile("dir1/rules/file2.txt", []byte("b1\nb2"), 0o644))
600 mustRun(t, "add", "dir1")
601 gitCommit(t, "init")
602
603 mustRun(t, "checkout", "-b", "v1")
604 mustRun(t, "mv", "dir1", "dir2")
605 gitCommit(t, "rename")
606
607 return debugGitRun(t)
608 },
609 changes: []*git.FileChange{
610 {
611 Commits: []string{"1"},
612 Path: git.PathDiff{
613 Before: git.Path{
614 Name: "dir1/rules/file1.txt",
615 Type: git.File,
616 },
617 After: git.Path{
618 Name: "dir2/rules/file1.txt",
619 Type: git.File,
620 },
621 },
622 Body: git.BodyDiff{
623 Before: []byte("a1\na2\na3\n"),
624 After: []byte("a1\na2\na3\n"),
625 ModifiedLines: []int{1, 2, 3},
626 },
627 },
628 {
629 Commits: []string{"1"},
630 Path: git.PathDiff{
631 Before: git.Path{
632 Name: "dir1/rules/file2.txt",
633 Type: git.File,
634 },
635 After: git.Path{
636 Name: "dir2/rules/file2.txt",
637 Type: git.File,
638 },
639 },
640 Body: git.BodyDiff{
641 Before: []byte("b1\nb2"),
642 After: []byte("b1\nb2"),
643 ModifiedLines: []int{1, 2},
644 },
645 },
646 },
647 err: "",
648 },
649 {
650 title: "rule partially replaced",
651 setup: func(t *testing.T) git.CommandRunner {
652 mustRun(t, "init", "--initial-branch=main", ".")
653 require.NoError(t, os.WriteFile("main.txt", []byte("l1\nl2\nl3\n"), 0o644))
654 mustRun(t, "add", "main.txt")
655 gitCommit(t, "init")
656
657 mustRun(t, "checkout", "-b", "v2")
658 require.NoError(t, os.WriteFile("main.txt", []byte("l1\nl3\n"), 0o644))
659 mustRun(t, "add", "main.txt")
660 gitCommit(t, "edit")
661
662 mustRun(t, "mv", "main.txt", "pr.txt")
663 gitCommit(t, "rename")
664
665 return debugGitRun(t)
666 },
667 changes: []*git.FileChange{
668 {
669 Commits: []string{"1", "2"},
670 Path: git.PathDiff{
671 Before: git.Path{
672 Name: "main.txt",
673 Type: git.File,
674 },
675 After: git.Path{
676 Name: "pr.txt",
677 Type: git.File,
678 },
679 },
680 Body: git.BodyDiff{
681 Before: []byte("l1\nl2\nl3\n"),
682 After: []byte("l1\nl3\n"),
683 ModifiedLines: []int{1, 2},
684 },
685 },
686 },
687 err: "",
688 },
689 }
690
691 for _, tc := range testCases {
692 t.Run(tc.title, func(t *testing.T) {
693 slog.SetDefault(slogt.New(t))
694
695 dir := t.TempDir()
696 t.Chdir(dir)
697
698 cmd := tc.setup(t)
699 changes, err := git.Changes(cmd, "main", git.NewPathFilter(nil, nil, nil))
700 if tc.err != "" {
701 require.EqualError(t, err, tc.err)
702 require.Nil(t, changes)
703 } else {
704 require.NoError(t, err)
705 require.Len(t, changes, len(tc.changes))
706 for i := range tc.changes {
707 require.Len(t, changes[i].Commits, len(tc.changes[i].Commits), "changes[%d].Commits", i)
708 require.Equal(t, tc.changes[i].Path, changes[i].Path, "changes[%d].Path", i)
709 require.Equal(t, tc.changes[i].Body, changes[i].Body, "changes[%d].Body", i)
710 }
711 require.Len(t, changes, len(tc.changes))
712 }
713 })
714 }
715}
716
717func TestChangesMocked(t *testing.T) {
718 type testCaseT struct {
719 title string
720 err string
721 mock git.CommandRunner
722 changes []*git.FileChange
723 }
724
725 testCases := []testCaseT{
726 {
727 title: "empty line in git log output",
728 mock: func(args ...string) ([]byte, error) {
729 if args[0] == "log" {
730 // Empty line should trigger len(parts) == 0
731 return []byte("abc123\n\nM\tfile.txt\n"), nil
732 }
733 if args[0] == "ls-tree" {
734 return []byte("100644 blob abc123def456\tfile.txt\n"), nil
735 }
736 if args[0] == "cat-file" {
737 return []byte("content"), nil
738 }
739 if args[0] == "blame" {
740 return []byte("abc123 1 1 1\nfilename file.txt\n\tcontent\n"), nil
741 }
742 return nil, nil
743 },
744 changes: []*git.FileChange{
745 {
746 Commits: []string{"1"},
747 Path: git.PathDiff{
748 Before: git.Path{
749 Name: "file.txt",
750 Type: git.File,
751 },
752 After: git.Path{
753 Name: "file.txt",
754 Type: git.File,
755 },
756 },
757 Body: git.BodyDiff{
758 Before: []byte("content"),
759 After: []byte("content"),
760 ModifiedLines: []int{},
761 },
762 },
763 },
764 },
765 {
766 title: "ls-tree malformed line - less than 3 space-separated parts",
767 mock: func(args ...string) ([]byte, error) {
768 if args[0] == "log" {
769 return []byte("abc123\nA\tnewfile.txt\n"), nil
770 }
771 if args[0] == "ls-tree" {
772 // Only 2 parts instead of 3
773 return []byte("100644 blob\n"), nil
774 }
775 if args[0] == "cat-file" {
776 return []byte("content"), nil
777 }
778 if args[0] == "blame" {
779 return []byte("abc123 1 1 1\nfilename newfile.txt\n\tcontent\n"), nil
780 }
781 return nil, nil
782 },
783 changes: []*git.FileChange{
784 {
785 Commits: []string{"1"},
786 Path: git.PathDiff{
787 Before: git.Path{
788 Name: "",
789 Type: git.Missing,
790 },
791 After: git.Path{
792 Name: "newfile.txt",
793 Type: git.Missing,
794 },
795 },
796 Body: git.BodyDiff{
797 Before: nil,
798 After: []byte("content"),
799 ModifiedLines: []int{},
800 },
801 },
802 },
803 },
804 {
805 title: "ls-tree line missing tab separator",
806 mock: func(args ...string) ([]byte, error) {
807 if args[0] == "log" {
808 return []byte("abc123\nA\tnewfile.txt\n"), nil
809 }
810 if args[0] == "ls-tree" {
811 // Has 3 space parts but no tab in third part
812 return []byte("100644 blob abc123def456\n"), nil
813 }
814 if args[0] == "cat-file" {
815 return []byte("content"), nil
816 }
817 if args[0] == "blame" {
818 return []byte("abc123 1 1 1\nfilename newfile.txt\n\tcontent\n"), nil
819 }
820 return nil, nil
821 },
822 changes: []*git.FileChange{
823 {
824 Commits: []string{"1"},
825 Path: git.PathDiff{
826 Before: git.Path{
827 Name: "",
828 Type: git.Missing,
829 },
830 After: git.Path{
831 Name: "newfile.txt",
832 Type: git.Missing,
833 },
834 },
835 Body: git.BodyDiff{
836 Before: nil,
837 After: []byte("content"),
838 ModifiedLines: []int{},
839 },
840 },
841 },
842 },
843 {
844 title: "ls-tree returns different path",
845 mock: func(args ...string) ([]byte, error) {
846 if args[0] == "log" {
847 return []byte("abc123\nA\tnewfile.txt\n"), nil
848 }
849 if args[0] == "ls-tree" {
850 // Returns a different file path
851 return []byte("100644 blob abc123def456\totherfile.txt\n"), nil
852 }
853 if args[0] == "cat-file" {
854 return []byte("content"), nil
855 }
856 if args[0] == "blame" {
857 return []byte("abc123 1 1 1\nfilename newfile.txt\n\tcontent\n"), nil
858 }
859 return nil, nil
860 },
861 changes: []*git.FileChange{
862 {
863 Commits: []string{"1"},
864 Path: git.PathDiff{
865 Before: git.Path{
866 Name: "",
867 Type: git.Missing,
868 },
869 After: git.Path{
870 Name: "newfile.txt",
871 Type: git.Missing,
872 },
873 },
874 Body: git.BodyDiff{
875 Before: nil,
876 After: []byte("content"),
877 ModifiedLines: []int{},
878 },
879 },
880 },
881 },
882 {
883 title: "ls-tree returns tag object type",
884 mock: func(args ...string) ([]byte, error) {
885 if args[0] == "log" {
886 return []byte("abc123\nA\tnewfile.txt\n"), nil
887 }
888 if args[0] == "ls-tree" {
889 // Object type is "tag" not "blob" or "tree"
890 return []byte("100644 tag abc123def456\tnewfile.txt\n"), nil
891 }
892 if args[0] == "cat-file" {
893 return []byte("content"), nil
894 }
895 if args[0] == "blame" {
896 return []byte("abc123 1 1 1\nfilename newfile.txt\n\tcontent\n"), nil
897 }
898 return nil, nil
899 },
900 changes: []*git.FileChange{
901 {
902 Commits: []string{"1"},
903 Path: git.PathDiff{
904 Before: git.Path{
905 Name: "",
906 Type: git.Missing,
907 },
908 After: git.Path{
909 Name: "newfile.txt",
910 Type: git.Missing,
911 },
912 },
913 Body: git.BodyDiff{
914 Before: nil,
915 After: []byte("content"),
916 ModifiedLines: []int{},
917 },
918 },
919 },
920 },
921 }
922
923 for _, tc := range testCases {
924 t.Run(tc.title, func(t *testing.T) {
925 slog.SetDefault(slogt.New(t))
926
927 changes, err := git.Changes(tc.mock, "main", git.NewPathFilter(nil, nil, nil))
928 if tc.err != "" {
929 require.EqualError(t, err, tc.err)
930 require.Nil(t, changes)
931 } else {
932 require.NoError(t, err)
933 require.Len(t, changes, len(tc.changes))
934 for i := range tc.changes {
935 require.Len(t, changes[i].Commits, len(tc.changes[i].Commits), "changes[%d].Commits", i)
936 require.Equal(t, tc.changes[i].Path, changes[i].Path, "changes[%d].Path", i)
937 require.Equal(t, tc.changes[i].Body, changes[i].Body, "changes[%d].Body", i)
938 }
939 }
940 })
941 }
942}
943