1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package gitcli_test
4
5import (
6 "context"
7 "testing"
8
9 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/require"
11
12 "bnema.dev/ermite/internal/domain"
13)
14
15func TestChangedPaths(t *testing.T) {
16 g, data := newGit(t)
17 ctx := context.Background()
18 m := newMergeFixture(t, data)
19 t.Cleanup(func() { _ = g.Close(ctx) })
20
21 // b..d changed a.txt and added c.txt.
22 got, truncated, err := g.ChangedPaths(ctx, m.f.Name, m.b, m.d, 300)
23 require.NoError(t, err)
24 assert.False(t, truncated)
25 assert.ElementsMatch(t, []string{"a.txt", "c.txt"}, got)
26
27 // A zero base lists every file of the commit, as for a new repository.
28 got, _, err = g.ChangedPaths(ctx, m.f.Name, domain.ObjectID{}, m.b, 300)
29 require.NoError(t, err)
30 assert.ElementsMatch(t, []string{"a.txt", "b.txt"}, got)
31
32 // No change is an empty list, not an error.
33 got, truncated, err = g.ChangedPaths(ctx, m.f.Name, m.d, m.d, 300)
34 require.NoError(t, err)
35 assert.Empty(t, got)
36 assert.False(t, truncated)
37
38 // A bound cuts the list and says so.
39 got, truncated, err = g.ChangedPaths(ctx, m.f.Name, domain.ObjectID{}, m.d, 2)
40 require.NoError(t, err)
41 assert.Len(t, got, 2)
42 assert.True(t, truncated)
43 got, truncated, err = g.ChangedPaths(ctx, m.f.Name, domain.ObjectID{}, m.d, 3)
44 require.NoError(t, err)
45 assert.Len(t, got, 3)
46 assert.False(t, truncated, "a list of exactly the bound is complete")
47
48 _, _, err = g.ChangedPaths(ctx, m.f.Name, m.b, m.d, 0)
49 assert.ErrorIs(t, err, domain.ErrInvalid)
50 _, _, err = g.ChangedPaths(ctx, name(t, "missing"), m.b, m.d, 5)
51 assert.ErrorIs(t, err, domain.ErrNotFound)
52 _, _, err = g.ChangedPaths(ctx, m.f.Name, m.b, oidOf(t, "1111111111111111111111111111111111111111"), 5)
53 assert.Error(t, err, "an unknown commit is an error")
54}
55
56func TestChangedPathsKeepsAWholeRenameAndOddNames(t *testing.T) {
57 g, data := newGit(t)
58 ctx := context.Background()
59 m := newMergeFixture(t, data)
60 t.Cleanup(func() { _ = g.Close(ctx) })
61
62 m.f.Git("checkout", "-q", "feat")
63 m.f.Write("dir with space/é.txt", "see\n")
64 m.f.Git("rm", "-q", "c.txt")
65 d2 := oidOf(t, m.f.Commit("rename"))
66 got, _, err := g.ChangedPaths(ctx, m.f.Name, m.featTip, d2, 10)
67 require.NoError(t, err)
68 assert.ElementsMatch(t, []string{"c.txt", "dir with space/é.txt"}, got, "both sides of a rename, verbatim")
69}
70
71func TestCommitsOffBranches(t *testing.T) {
72 g, data := newGit(t)
73 ctx := context.Background()
74 m := newMergeFixture(t, data)
75 t.Cleanup(func() { _ = g.Close(ctx) })
76
77 msgs := func(cs []domain.Commit) []string {
78 var out []string
79 for _, c := range cs {
80 out = append(out, c.Summary)
81 }
82 return out
83 }
84
85 // Every commit of the fixture is on main or feat.
86 got, more, err := g.CommitsOffBranches(ctx, m.f.Name, m.d, 10)
87 require.NoError(t, err)
88 assert.Empty(t, got, "feat has its tip")
89 assert.False(t, more)
90
91 // A commit no branch has, on top of feat: listed alone, then with its
92 // parent when that has no branch either (a branch deleted, a detached tip).
93 m.f.Git("checkout", "--quiet", "--detach", m.d.String())
94 m.f.Write("t1.txt", "one\n")
95 t1 := oidOf(t, m.f.Commit("tagged one"))
96 m.f.Write("t2.txt", "two\n")
97 t2 := oidOf(t, m.f.Commit("tagged two"))
98 m.f.Git("update-ref", "refs/tags/v1", t2.String())
99
100 got, more, err = g.CommitsOffBranches(ctx, m.f.Name, t2, 10)
101 require.NoError(t, err)
102 assert.Equal(t, []string{"tagged two", "tagged one"}, msgs(got), "newest first, down to the commits a branch has")
103 assert.False(t, more)
104 got, _, err = g.CommitsOffBranches(ctx, m.f.Name, t1, 10)
105 require.NoError(t, err)
106 assert.Equal(t, []string{"tagged one"}, msgs(got))
107
108 // A bound cuts the list and says so.
109 got, more, err = g.CommitsOffBranches(ctx, m.f.Name, t2, 1)
110 require.NoError(t, err)
111 assert.Equal(t, []string{"tagged two"}, msgs(got))
112 assert.True(t, more)
113
114 // Another tag is no branch: t2 stays listed although v1 points to it, and
115 // moving a branch onto t1 takes t1 out of the list.
116 m.f.Git("update-ref", "refs/heads/rel", t1.String())
117 got, _, err = g.CommitsOffBranches(ctx, m.f.Name, t2, 10)
118 require.NoError(t, err)
119 assert.Equal(t, []string{"tagged two"}, msgs(got))
120
121 _, _, err = g.CommitsOffBranches(ctx, m.f.Name, domain.ObjectID{}, 10)
122 assert.ErrorIs(t, err, domain.ErrInvalid)
123 _, _, err = g.CommitsOffBranches(ctx, m.f.Name, t2, 0)
124 assert.ErrorIs(t, err, domain.ErrInvalidPageSize)
125 _, _, err = g.CommitsOffBranches(ctx, name(t, "missing"), t2, 5)
126 assert.ErrorIs(t, err, domain.ErrNotFound)
127}