fork of go-git with some jj specific features
1package object
2
3import (
4 "context"
5 "sort"
6 "testing"
7
8 fixtures "github.com/go-git/go-git-fixtures/v4"
9 "github.com/go-git/go-git/v5/plumbing"
10 "github.com/go-git/go-git/v5/plumbing/cache"
11 "github.com/go-git/go-git/v5/plumbing/filemode"
12 "github.com/go-git/go-git/v5/plumbing/format/diff"
13 "github.com/go-git/go-git/v5/plumbing/storer"
14 "github.com/go-git/go-git/v5/storage/filesystem"
15 "github.com/go-git/go-git/v5/utils/merkletrie"
16 "github.com/stretchr/testify/suite"
17)
18
19type ChangeFixtureSuite struct {
20 fixtures.Suite
21}
22
23type ChangeSuite struct {
24 suite.Suite
25 ChangeFixtureSuite
26 Storer storer.EncodedObjectStorer
27 Fixture *fixtures.Fixture
28}
29
30func (s *ChangeSuite) SetupSuite() {
31 s.Fixture = fixtures.ByURL("https://github.com/src-d/go-git.git").
32 ByTag(".git").One()
33 sto := filesystem.NewStorage(s.Fixture.DotGit(), cache.NewObjectLRUDefault())
34 s.Storer = sto
35}
36
37func (s *ChangeSuite) tree(h plumbing.Hash) *Tree {
38 t, err := GetTree(s.Storer, h)
39 s.NoError(err)
40 return t
41}
42
43func TestChangeSuite(t *testing.T) {
44 suite.Run(t, new(ChangeSuite))
45}
46
47func (s *ChangeSuite) TestInsert() {
48 // Commit a5078b19f08f63e7948abd0a5e2fb7d319d3a565 of the go-git
49 // fixture inserted "examples/clone/main.go".
50 //
51 // On that commit, the "examples/clone" tree is
52 // 6efca3ff41cab651332f9ebc0c96bb26be809615
53 //
54 // and the "examples/colone/main.go" is
55 // f95dc8f7923add1a8b9f72ecb1e8db1402de601a
56
57 path := "examples/clone/main.go"
58 name := "main.go"
59 mode := filemode.Regular
60 blob := plumbing.NewHash("f95dc8f7923add1a8b9f72ecb1e8db1402de601a")
61 tree := plumbing.NewHash("6efca3ff41cab651332f9ebc0c96bb26be809615")
62
63 change := &Change{
64 From: empty,
65 To: ChangeEntry{
66 Name: path,
67 Tree: s.tree(tree),
68 TreeEntry: TreeEntry{
69 Name: name,
70 Mode: mode,
71 Hash: blob,
72 },
73 },
74 }
75
76 action, err := change.Action()
77 s.NoError(err)
78 s.Equal(merkletrie.Insert, action)
79
80 from, to, err := change.Files()
81 s.NoError(err)
82 s.Nil(from)
83 s.Equal(name, to.Name)
84 s.Equal(blob, to.Blob.Hash)
85
86 p, err := change.Patch()
87 s.NoError(err)
88 s.Equal(1, len(p.FilePatches()))
89 s.Equal(1, len(p.FilePatches()[0].Chunks()))
90 s.Equal(diff.Add, p.FilePatches()[0].Chunks()[0].Type())
91
92 p, err = change.PatchContext(context.Background())
93 s.NoError(err)
94 s.Equal(1, len(p.FilePatches()))
95 s.Equal(1, len(p.FilePatches()[0].Chunks()))
96 s.Equal(diff.Add, p.FilePatches()[0].Chunks()[0].Type())
97
98 str := change.String()
99 s.Equal("<Action: Insert, Path: examples/clone/main.go>", str)
100}
101
102func (s *ChangeSuite) TestDelete() {
103 // Commit f6011d65d57c2a866e231fc21a39cb618f86f9ea of the go-git
104 // fixture deleted "utils/difftree/difftree.go".
105 //
106 // The parent of that commit is
107 // 9b4a386db3d98a4362516a00ef3d04d4698c9bcd.
108 //
109 // On that parent commit, the "utils/difftree" tree is
110 // f3d11566401ce4b0808aab9dd6fad3d5abf1481a.
111 //
112 // and the "utils/difftree/difftree.go" is
113 // e2cb9a5719daf634d45a063112b4044ee81da13ea.
114
115 path := "utils/difftree/difftree.go"
116 name := "difftree.go"
117 mode := filemode.Regular
118 blob := plumbing.NewHash("e2cb9a5719daf634d45a063112b4044ee81da13e")
119 tree := plumbing.NewHash("f3d11566401ce4b0808aab9dd6fad3d5abf1481a")
120
121 change := &Change{
122 From: ChangeEntry{
123 Name: path,
124 Tree: s.tree(tree),
125 TreeEntry: TreeEntry{
126 Name: name,
127 Mode: mode,
128 Hash: blob,
129 },
130 },
131 To: empty,
132 }
133
134 action, err := change.Action()
135 s.NoError(err)
136 s.Equal(merkletrie.Delete, action)
137
138 from, to, err := change.Files()
139 s.NoError(err)
140 s.Nil(to)
141 s.Equal(name, from.Name)
142 s.Equal(blob, from.Blob.Hash)
143
144 p, err := change.Patch()
145 s.NoError(err)
146 s.Equal(1, len(p.FilePatches()))
147 s.Equal(1, len(p.FilePatches()[0].Chunks()))
148 s.Equal(diff.Delete, p.FilePatches()[0].Chunks()[0].Type())
149
150 p, err = change.PatchContext(context.Background())
151 s.NoError(err)
152 s.Equal(1, len(p.FilePatches()))
153 s.Equal(1, len(p.FilePatches()[0].Chunks()))
154 s.Equal(diff.Delete, p.FilePatches()[0].Chunks()[0].Type())
155
156 str := change.String()
157 s.Equal("<Action: Delete, Path: utils/difftree/difftree.go>", str)
158}
159
160func (s *ChangeSuite) TestModify() {
161 // Commit 7beaad711378a4daafccc2c04bc46d36df2a0fd1 of the go-git
162 // fixture modified "examples/latest/latest.go".
163 // the "examples/latest" tree is
164 // b1f01b730b855c82431918cb338ad47ed558999b.
165 // and "examples/latest/latest.go" is blob
166 // 05f583ace3a9a078d8150905a53a4d82567f125f.
167 //
168 // The parent of that commit is
169 // 337148ef6d751477796922ac127b416b8478fcc4.
170 // the "examples/latest" tree is
171 // 8b0af31d2544acb5c4f3816a602f11418cbd126e.
172 // and "examples/latest/latest.go" is blob
173 // de927fad935d172929aacf20e71f3bf0b91dd6f9.
174
175 path := "utils/difftree/difftree.go"
176 name := "difftree.go"
177 mode := filemode.Regular
178 fromBlob := plumbing.NewHash("05f583ace3a9a078d8150905a53a4d82567f125f")
179 fromTree := plumbing.NewHash("b1f01b730b855c82431918cb338ad47ed558999b")
180 toBlob := plumbing.NewHash("de927fad935d172929aacf20e71f3bf0b91dd6f9")
181 toTree := plumbing.NewHash("8b0af31d2544acb5c4f3816a602f11418cbd126e")
182
183 change := &Change{
184 From: ChangeEntry{
185 Name: path,
186 Tree: s.tree(fromTree),
187 TreeEntry: TreeEntry{
188 Name: name,
189 Mode: mode,
190 Hash: fromBlob,
191 },
192 },
193 To: ChangeEntry{
194 Name: path,
195 Tree: s.tree(toTree),
196 TreeEntry: TreeEntry{
197 Name: name,
198 Mode: mode,
199 Hash: toBlob,
200 },
201 },
202 }
203
204 action, err := change.Action()
205 s.NoError(err)
206 s.Equal(merkletrie.Modify, action)
207
208 from, to, err := change.Files()
209 s.NoError(err)
210
211 s.Equal(name, from.Name)
212 s.Equal(fromBlob, from.Blob.Hash)
213 s.Equal(name, to.Name)
214 s.Equal(toBlob, to.Blob.Hash)
215
216 p, err := change.Patch()
217 s.NoError(err)
218 s.Equal(1, len(p.FilePatches()))
219 s.Equal(7, len(p.FilePatches()[0].Chunks()))
220 s.Equal(diff.Equal, p.FilePatches()[0].Chunks()[0].Type())
221 s.Equal(diff.Delete, p.FilePatches()[0].Chunks()[1].Type())
222 s.Equal(diff.Add, p.FilePatches()[0].Chunks()[2].Type())
223 s.Equal(diff.Equal, p.FilePatches()[0].Chunks()[3].Type())
224 s.Equal(diff.Delete, p.FilePatches()[0].Chunks()[4].Type())
225 s.Equal(diff.Add, p.FilePatches()[0].Chunks()[5].Type())
226 s.Equal(diff.Equal, p.FilePatches()[0].Chunks()[6].Type())
227
228 p, err = change.PatchContext(context.Background())
229 s.NoError(err)
230 s.Equal(1, len(p.FilePatches()))
231 s.Equal(7, len(p.FilePatches()[0].Chunks()))
232 s.Equal(diff.Equal, p.FilePatches()[0].Chunks()[0].Type())
233 s.Equal(diff.Delete, p.FilePatches()[0].Chunks()[1].Type())
234 s.Equal(diff.Add, p.FilePatches()[0].Chunks()[2].Type())
235 s.Equal(diff.Equal, p.FilePatches()[0].Chunks()[3].Type())
236 s.Equal(diff.Delete, p.FilePatches()[0].Chunks()[4].Type())
237 s.Equal(diff.Add, p.FilePatches()[0].Chunks()[5].Type())
238 s.Equal(diff.Equal, p.FilePatches()[0].Chunks()[6].Type())
239
240 str := change.String()
241 s.Equal("<Action: Modify, Path: utils/difftree/difftree.go>", str)
242}
243
244func (s *ChangeSuite) TestEmptyChangeFails() {
245 change := &Change{}
246
247 _, err := change.Action()
248 s.ErrorContains(err, "malformed")
249
250 _, _, err = change.Files()
251 s.ErrorContains(err, "malformed")
252
253 str := change.String()
254 s.Equal("malformed change", str)
255}
256
257// test reproducing bug #317
258func (s *ChangeSuite) TestNoFileFilemodes() {
259 f := fixtures.ByURL("https://github.com/git-fixtures/submodule.git").One()
260
261 sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault())
262
263 iter, err := sto.IterEncodedObjects(plumbing.AnyObject)
264 s.NoError(err)
265 var commits []*Commit
266 iter.ForEach(func(o plumbing.EncodedObject) error {
267 if o.Type() == plumbing.CommitObject {
268 commit, err := GetCommit(sto, o.Hash())
269 s.NoError(err)
270 commits = append(commits, commit)
271
272 }
273
274 return nil
275 })
276
277 s.NotEqual(0, len(commits))
278
279 var prev *Commit
280 for _, commit := range commits {
281 if prev == nil {
282 prev = commit
283 continue
284 }
285 tree, err := commit.Tree()
286 s.NoError(err)
287 prevTree, err := prev.Tree()
288 s.NoError(err)
289 changes, err := DiffTree(tree, prevTree)
290 s.NoError(err)
291 for _, change := range changes {
292 _, _, err := change.Files()
293 s.NoError(err)
294 }
295
296 prev = commit
297 }
298}
299
300func (s *ChangeSuite) TestErrorsFindingChildsAreDetected() {
301 // Commit 7beaad711378a4daafccc2c04bc46d36df2a0fd1 of the go-git
302 // fixture modified "examples/latest/latest.go".
303 // the "examples/latest" tree is
304 // b1f01b730b855c82431918cb338ad47ed558999b.
305 // and "examples/latest/latest.go" is blob
306 // 05f583ace3a9a078d8150905a53a4d82567f125f.
307 //
308 // The parent of that commit is
309 // 337148ef6d751477796922ac127b416b8478fcc4.
310 // the "examples/latest" tree is
311 // 8b0af31d2544acb5c4f3816a602f11418cbd126e.
312 // and "examples/latest/latest.go" is blob
313 // de927fad935d172929aacf20e71f3bf0b91dd6f9.
314
315 path := "utils/difftree/difftree.go"
316 name := "difftree.go"
317 mode := filemode.Regular
318 fromBlob := plumbing.NewHash("aaaa") // does not exists
319 fromTree := plumbing.NewHash("b1f01b730b855c82431918cb338ad47ed558999b")
320 toBlob := plumbing.NewHash("bbbb") // does not exists
321 toTree := plumbing.NewHash("8b0af31d2544acb5c4f3816a602f11418cbd126e")
322
323 change := &Change{
324 From: ChangeEntry{
325 Name: path,
326 Tree: s.tree(fromTree),
327 TreeEntry: TreeEntry{
328 Name: name,
329 Mode: mode,
330 Hash: fromBlob,
331 },
332 },
333 To: ChangeEntry{},
334 }
335
336 _, _, err := change.Files()
337 s.ErrorContains(err, "object not found")
338
339 change = &Change{
340 From: empty,
341 To: ChangeEntry{
342 Name: path,
343 Tree: s.tree(toTree),
344 TreeEntry: TreeEntry{
345 Name: name,
346 Mode: mode,
347 Hash: toBlob,
348 },
349 },
350 }
351
352 _, _, err = change.Files()
353 s.ErrorContains(err, "object not found")
354}
355
356func (s *ChangeSuite) TestChangesString() {
357 expected := "[]"
358 changes := Changes{}
359 obtained := changes.String()
360 s.Equal(expected, obtained)
361
362 expected = "[<Action: Modify, Path: bla>]"
363 changes = make([]*Change, 1)
364 changes[0] = &Change{}
365 changes[0].From.Name = "bla"
366 changes[0].To.Name = "bla"
367
368 obtained = changes.String()
369 s.Equal(expected, obtained)
370
371 expected = "[<Action: Modify, Path: bla>, <Action: Delete, Path: foo/bar>]"
372 changes = make([]*Change, 2)
373 changes[0] = &Change{}
374 changes[0].From.Name = "bla"
375 changes[0].To.Name = "bla"
376 changes[1] = &Change{}
377 changes[1].From.Name = "foo/bar"
378 obtained = changes.String()
379 s.Equal(expected, obtained)
380}
381
382func (s *ChangeSuite) TestChangesSort() {
383 changes := make(Changes, 3)
384 changes[0] = &Change{}
385 changes[0].From.Name = "z"
386 changes[0].To.Name = "z"
387 changes[1] = &Change{}
388 changes[1].From.Name = "b/b"
389 changes[2] = &Change{}
390 changes[2].To.Name = "b/a"
391
392 expected := "[<Action: Insert, Path: b/a>, " +
393 "<Action: Delete, Path: b/b>, " +
394 "<Action: Modify, Path: z>]"
395
396 sort.Sort(changes)
397 s.Equal(expected, changes.String())
398}
399
400func (s *ChangeSuite) TestCancel() {
401 // Commit a5078b19f08f63e7948abd0a5e2fb7d319d3a565 of the go-git
402 // fixture inserted "examples/clone/main.go".
403 //
404 // On that commit, the "examples/clone" tree is
405 // 6efca3ff41cab651332f9ebc0c96bb26be809615
406 //
407 // and the "examples/clone/main.go" is
408 // f95dc8f7923add1a8b9f72ecb1e8db1402de601a
409
410 path := "examples/clone/main.go"
411 name := "main.go"
412 mode := filemode.Regular
413 blob := plumbing.NewHash("f95dc8f7923add1a8b9f72ecb1e8db1402de601a")
414 tree := plumbing.NewHash("6efca3ff41cab651332f9ebc0c96bb26be809615")
415
416 change := &Change{
417 From: empty,
418 To: ChangeEntry{
419 Name: path,
420 Tree: s.tree(tree),
421 TreeEntry: TreeEntry{
422 Name: name,
423 Mode: mode,
424 Hash: blob,
425 },
426 },
427 }
428
429 ctx, cancel := context.WithCancel(context.Background())
430 cancel()
431 p, err := change.PatchContext(ctx)
432 s.Nil(p)
433 s.ErrorContains(err, "operation canceled")
434}