1package git
2
3import (
4 "bytes"
5 "log"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "runtime"
10 "strings"
11 "time"
12
13 "github.com/go-git/go-git/v5/plumbing"
14 "github.com/go-git/go-git/v5/plumbing/cache"
15 "github.com/go-git/go-git/v5/plumbing/object"
16 "github.com/go-git/go-git/v5/plumbing/storer"
17 "github.com/go-git/go-git/v5/storage/filesystem"
18 "github.com/go-git/go-git/v5/storage/memory"
19
20 "github.com/ProtonMail/go-crypto/openpgp"
21 "github.com/ProtonMail/go-crypto/openpgp/armor"
22 "github.com/ProtonMail/go-crypto/openpgp/errors"
23 "github.com/go-git/go-billy/v5/memfs"
24 "github.com/go-git/go-billy/v5/util"
25 . "gopkg.in/check.v1"
26)
27
28func (s *WorktreeSuite) TestCommitEmptyOptions(c *C) {
29 fs := memfs.New()
30 r, err := Init(memory.NewStorage(), fs)
31 c.Assert(err, IsNil)
32
33 w, err := r.Worktree()
34 c.Assert(err, IsNil)
35
36 util.WriteFile(fs, "foo", []byte("foo"), 0644)
37
38 _, err = w.Add("foo")
39 c.Assert(err, IsNil)
40
41 hash, err := w.Commit("foo", &CommitOptions{})
42 c.Assert(err, IsNil)
43 c.Assert(hash.IsZero(), Equals, false)
44
45 commit, err := r.CommitObject(hash)
46 c.Assert(err, IsNil)
47 c.Assert(commit.Author.Name, Not(Equals), "")
48}
49
50func (s *WorktreeSuite) TestCommitInitial(c *C) {
51 expected := plumbing.NewHash("98c4ac7c29c913f7461eae06e024dc18e80d23a4")
52
53 fs := memfs.New()
54 storage := memory.NewStorage()
55
56 r, err := Init(storage, fs)
57 c.Assert(err, IsNil)
58
59 w, err := r.Worktree()
60 c.Assert(err, IsNil)
61
62 util.WriteFile(fs, "foo", []byte("foo"), 0644)
63
64 _, err = w.Add("foo")
65 c.Assert(err, IsNil)
66
67 hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature()})
68 c.Assert(hash, Equals, expected)
69 c.Assert(err, IsNil)
70
71 assertStorageStatus(c, r, 1, 1, 1, expected)
72}
73
74func (s *WorktreeSuite) TestNothingToCommit(c *C) {
75 expected := plumbing.NewHash("838ea833ce893e8555907e5ef224aa076f5e274a")
76
77 r, err := Init(memory.NewStorage(), memfs.New())
78 c.Assert(err, IsNil)
79
80 w, err := r.Worktree()
81 c.Assert(err, IsNil)
82
83 hash, err := w.Commit("failed empty commit\n", &CommitOptions{Author: defaultSignature()})
84 c.Assert(hash, Equals, plumbing.ZeroHash)
85 c.Assert(err, Equals, ErrEmptyCommit)
86
87 hash, err = w.Commit("enable empty commits\n", &CommitOptions{Author: defaultSignature(), AllowEmptyCommits: true})
88 c.Assert(hash, Equals, expected)
89 c.Assert(err, IsNil)
90}
91
92func (s *WorktreeSuite) TestCommitParent(c *C) {
93 expected := plumbing.NewHash("ef3ca05477530b37f48564be33ddd48063fc7a22")
94
95 fs := memfs.New()
96 w := &Worktree{
97 r: s.Repository,
98 Filesystem: fs,
99 }
100
101 err := w.Checkout(&CheckoutOptions{})
102 c.Assert(err, IsNil)
103
104 util.WriteFile(fs, "foo", []byte("foo"), 0644)
105
106 _, err = w.Add("foo")
107 c.Assert(err, IsNil)
108
109 hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature()})
110 c.Assert(hash, Equals, expected)
111 c.Assert(err, IsNil)
112
113 assertStorageStatus(c, s.Repository, 13, 11, 10, expected)
114}
115
116func (s *WorktreeSuite) TestCommitAmend(c *C) {
117 fs := memfs.New()
118 w := &Worktree{
119 r: s.Repository,
120 Filesystem: fs,
121 }
122
123 err := w.Checkout(&CheckoutOptions{})
124 c.Assert(err, IsNil)
125
126 util.WriteFile(fs, "foo", []byte("foo"), 0644)
127
128 _, err = w.Add("foo")
129 c.Assert(err, IsNil)
130
131 _, err = w.Commit("foo\n", &CommitOptions{Author: defaultSignature()})
132 c.Assert(err, IsNil)
133
134 util.WriteFile(fs, "bar", []byte("bar"), 0644)
135
136 _, err = w.Add("bar")
137 c.Assert(err, IsNil)
138
139 amendedHash, err := w.Commit("bar\n", &CommitOptions{Amend: true})
140 c.Assert(err, IsNil)
141
142 headRef, err := w.r.Head()
143 c.Assert(err, IsNil)
144
145 c.Assert(amendedHash, Equals, headRef.Hash())
146
147 commit, err := w.r.CommitObject(headRef.Hash())
148 c.Assert(err, IsNil)
149 c.Assert(commit.Message, Equals, "bar\n")
150 c.Assert(commit.NumParents(), Equals, 1)
151
152 stats, err := commit.Stats()
153 c.Assert(err, IsNil)
154 c.Assert(stats, HasLen, 2)
155 c.Assert(stats[0], Equals, object.FileStat{
156 Name: "bar",
157 Addition: 1,
158 })
159 c.Assert(stats[1], Equals, object.FileStat{
160 Name: "foo",
161 Addition: 1,
162 })
163
164 assertStorageStatus(c, s.Repository, 14, 12, 11, amendedHash)
165}
166
167func (s *WorktreeSuite) TestAddAndCommitWithSkipStatus(c *C) {
168 expected := plumbing.NewHash("375a3808ffde7f129cdd3c8c252fd0fe37cfd13b")
169
170 fs := memfs.New()
171 w := &Worktree{
172 r: s.Repository,
173 Filesystem: fs,
174 }
175
176 err := w.Checkout(&CheckoutOptions{})
177 c.Assert(err, IsNil)
178
179 util.WriteFile(fs, "LICENSE", []byte("foo"), 0644)
180 util.WriteFile(fs, "foo", []byte("foo"), 0644)
181
182 err = w.AddWithOptions(&AddOptions{
183 Path: "foo",
184 SkipStatus: true,
185 })
186 c.Assert(err, IsNil)
187
188 hash, err := w.Commit("commit foo only\n", &CommitOptions{
189 Author: defaultSignature(),
190 })
191
192 c.Assert(hash, Equals, expected)
193 c.Assert(err, IsNil)
194
195 assertStorageStatus(c, s.Repository, 13, 11, 10, expected)
196}
197
198func (s *WorktreeSuite) TestAddAndCommitWithSkipStatusPathNotModified(c *C) {
199 expected := plumbing.NewHash("375a3808ffde7f129cdd3c8c252fd0fe37cfd13b")
200 expected2 := plumbing.NewHash("8691273baf8f6ee2cccfc05e910552c04d02d472")
201
202 fs := memfs.New()
203 w := &Worktree{
204 r: s.Repository,
205 Filesystem: fs,
206 }
207
208 err := w.Checkout(&CheckoutOptions{})
209 c.Assert(err, IsNil)
210
211 util.WriteFile(fs, "foo", []byte("foo"), 0644)
212
213 status, err := w.Status()
214 c.Assert(err, IsNil)
215 foo := status.File("foo")
216 c.Assert(foo.Staging, Equals, Untracked)
217 c.Assert(foo.Worktree, Equals, Untracked)
218
219 err = w.AddWithOptions(&AddOptions{
220 Path: "foo",
221 SkipStatus: true,
222 })
223 c.Assert(err, IsNil)
224
225 status, err = w.Status()
226 c.Assert(err, IsNil)
227 foo = status.File("foo")
228 c.Assert(foo.Staging, Equals, Added)
229 c.Assert(foo.Worktree, Equals, Unmodified)
230
231 hash, err := w.Commit("commit foo only\n", &CommitOptions{All: true,
232 Author: defaultSignature(),
233 })
234 c.Assert(hash, Equals, expected)
235 c.Assert(err, IsNil)
236 commit1, err := w.r.CommitObject(hash)
237
238 status, err = w.Status()
239 c.Assert(err, IsNil)
240 foo = status.File("foo")
241 c.Assert(foo.Staging, Equals, Untracked)
242 c.Assert(foo.Worktree, Equals, Untracked)
243
244 assertStorageStatus(c, s.Repository, 13, 11, 10, expected)
245
246 err = w.AddWithOptions(&AddOptions{
247 Path: "foo",
248 SkipStatus: true,
249 })
250 c.Assert(err, IsNil)
251
252 status, err = w.Status()
253 c.Assert(err, IsNil)
254 foo = status.File("foo")
255 c.Assert(foo.Staging, Equals, Untracked)
256 c.Assert(foo.Worktree, Equals, Untracked)
257
258 hash, err = w.Commit("commit with no changes\n", &CommitOptions{
259 Author: defaultSignature(),
260 })
261 c.Assert(hash, Equals, expected2)
262 c.Assert(err, IsNil)
263 commit2, err := w.r.CommitObject(hash)
264
265 status, err = w.Status()
266 c.Assert(err, IsNil)
267 foo = status.File("foo")
268 c.Assert(foo.Staging, Equals, Untracked)
269 c.Assert(foo.Worktree, Equals, Untracked)
270
271 patch, err := commit2.Patch(commit1)
272 c.Assert(err, IsNil)
273 files := patch.FilePatches()
274 c.Assert(files, IsNil)
275
276 assertStorageStatus(c, s.Repository, 13, 11, 11, expected2)
277}
278
279func (s *WorktreeSuite) TestCommitAll(c *C) {
280 expected := plumbing.NewHash("aede6f8c9c1c7ec9ca8d287c64b8ed151276fa28")
281
282 fs := memfs.New()
283 w := &Worktree{
284 r: s.Repository,
285 Filesystem: fs,
286 }
287
288 err := w.Checkout(&CheckoutOptions{})
289 c.Assert(err, IsNil)
290
291 util.WriteFile(fs, "LICENSE", []byte("foo"), 0644)
292 util.WriteFile(fs, "foo", []byte("foo"), 0644)
293
294 hash, err := w.Commit("foo\n", &CommitOptions{
295 All: true,
296 Author: defaultSignature(),
297 })
298
299 c.Assert(hash, Equals, expected)
300 c.Assert(err, IsNil)
301
302 assertStorageStatus(c, s.Repository, 13, 11, 10, expected)
303}
304
305func (s *WorktreeSuite) TestRemoveAndCommitAll(c *C) {
306 expected := plumbing.NewHash("907cd576c6ced2ecd3dab34a72bf9cf65944b9a9")
307
308 fs := memfs.New()
309 w := &Worktree{
310 r: s.Repository,
311 Filesystem: fs,
312 }
313
314 err := w.Checkout(&CheckoutOptions{})
315 c.Assert(err, IsNil)
316
317 util.WriteFile(fs, "foo", []byte("foo"), 0644)
318 _, err = w.Add("foo")
319 c.Assert(err, IsNil)
320
321 _, errFirst := w.Commit("Add in Repo\n", &CommitOptions{
322 Author: defaultSignature(),
323 })
324 c.Assert(errFirst, IsNil)
325
326 errRemove := fs.Remove("foo")
327 c.Assert(errRemove, IsNil)
328
329 hash, errSecond := w.Commit("Remove foo\n", &CommitOptions{
330 All: true,
331 Author: defaultSignature(),
332 })
333 c.Assert(errSecond, IsNil)
334
335 c.Assert(hash, Equals, expected)
336 c.Assert(err, IsNil)
337
338 assertStorageStatus(c, s.Repository, 13, 11, 11, expected)
339}
340
341func (s *WorktreeSuite) TestCommitSign(c *C) {
342 fs := memfs.New()
343 storage := memory.NewStorage()
344
345 r, err := Init(storage, fs)
346 c.Assert(err, IsNil)
347
348 w, err := r.Worktree()
349 c.Assert(err, IsNil)
350
351 util.WriteFile(fs, "foo", []byte("foo"), 0644)
352
353 _, err = w.Add("foo")
354 c.Assert(err, IsNil)
355
356 key := commitSignKey(c, true)
357 hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature(), SignKey: key})
358 c.Assert(err, IsNil)
359
360 // Verify the commit.
361 pks := new(bytes.Buffer)
362 pkw, err := armor.Encode(pks, openpgp.PublicKeyType, nil)
363 c.Assert(err, IsNil)
364
365 err = key.Serialize(pkw)
366 c.Assert(err, IsNil)
367 err = pkw.Close()
368 c.Assert(err, IsNil)
369
370 expectedCommit, err := r.CommitObject(hash)
371 c.Assert(err, IsNil)
372 actual, err := expectedCommit.Verify(pks.String())
373 c.Assert(err, IsNil)
374 c.Assert(actual.PrimaryKey, DeepEquals, key.PrimaryKey)
375}
376
377func (s *WorktreeSuite) TestCommitSignBadKey(c *C) {
378 fs := memfs.New()
379 storage := memory.NewStorage()
380
381 r, err := Init(storage, fs)
382 c.Assert(err, IsNil)
383
384 w, err := r.Worktree()
385 c.Assert(err, IsNil)
386
387 util.WriteFile(fs, "foo", []byte("foo"), 0644)
388
389 _, err = w.Add("foo")
390 c.Assert(err, IsNil)
391
392 key := commitSignKey(c, false)
393 _, err = w.Commit("foo\n", &CommitOptions{Author: defaultSignature(), SignKey: key})
394 c.Assert(err, Equals, errors.InvalidArgumentError("signing key is encrypted"))
395}
396
397func (s *WorktreeSuite) TestCommitTreeSort(c *C) {
398 fs, clean := s.TemporalFilesystem()
399 defer clean()
400
401 st := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
402 _, err := Init(st, nil)
403 c.Assert(err, IsNil)
404
405 r, _ := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{
406 URL: fs.Root(),
407 })
408
409 w, err := r.Worktree()
410 c.Assert(err, IsNil)
411
412 mfs := w.Filesystem
413
414 err = mfs.MkdirAll("delta", 0755)
415 c.Assert(err, IsNil)
416
417 for _, p := range []string{"delta_last", "Gamma", "delta/middle", "Beta", "delta-first", "alpha"} {
418 util.WriteFile(mfs, p, []byte("foo"), 0644)
419 _, err = w.Add(p)
420 c.Assert(err, IsNil)
421 }
422
423 _, err = w.Commit("foo\n", &CommitOptions{
424 All: true,
425 Author: defaultSignature(),
426 })
427 c.Assert(err, IsNil)
428
429 err = r.Push(&PushOptions{})
430 c.Assert(err, IsNil)
431
432 cmd := exec.Command("git", "fsck")
433 cmd.Dir = fs.Root()
434 cmd.Env = os.Environ()
435 buf := &bytes.Buffer{}
436 cmd.Stderr = buf
437 cmd.Stdout = buf
438
439 err = cmd.Run()
440
441 c.Assert(err, IsNil, Commentf("%s", buf.Bytes()))
442}
443
444// https://github.com/go-git/go-git/pull/224
445func (s *WorktreeSuite) TestJustStoreObjectsNotAlreadyStored(c *C) {
446 fs, clean := s.TemporalFilesystem()
447 defer clean()
448
449 fsDotgit, err := fs.Chroot(".git") // real fs to get modified timestamps
450 c.Assert(err, IsNil)
451 storage := filesystem.NewStorage(fsDotgit, cache.NewObjectLRUDefault())
452
453 r, err := Init(storage, fs)
454 c.Assert(err, IsNil)
455
456 w, err := r.Worktree()
457 c.Assert(err, IsNil)
458
459 // Step 1: Write LICENSE
460 util.WriteFile(fs, "LICENSE", []byte("license"), 0644)
461 hLicense, err := w.Add("LICENSE")
462 c.Assert(err, IsNil)
463 c.Assert(hLicense, Equals, plumbing.NewHash("0484eba0d41636ba71fa612c78559cd6c3006cde"))
464
465 hash, err := w.Commit("commit 1\n", &CommitOptions{
466 All: true,
467 Author: defaultSignature(),
468 })
469 c.Assert(err, IsNil)
470 c.Assert(hash, Equals, plumbing.NewHash("7a7faee4630d2664a6869677cc8ab614f3fd4a18"))
471
472 infoLicense, err := fsDotgit.Stat(filepath.Join("objects", "04", "84eba0d41636ba71fa612c78559cd6c3006cde"))
473 c.Assert(err, IsNil) // checking objects file exists
474
475 // Step 2: Write foo.
476 time.Sleep(5 * time.Millisecond) // uncool, but we need to get different timestamps...
477 util.WriteFile(fs, "foo", []byte("foo"), 0644)
478 hFoo, err := w.Add("foo")
479 c.Assert(err, IsNil)
480 c.Assert(hFoo, Equals, plumbing.NewHash("19102815663d23f8b75a47e7a01965dcdc96468c"))
481
482 hash, err = w.Commit("commit 2\n", &CommitOptions{
483 All: true,
484 Author: defaultSignature(),
485 })
486 c.Assert(err, IsNil)
487 c.Assert(hash, Equals, plumbing.NewHash("97c0c5177e6ac57d10e8ea0017f2d39b91e2b364"))
488
489 // Step 3: Check
490 // There is no need to overwrite the object of LICENSE, because its content
491 // was not changed. Just a write on the object of foo is required. This behaviour
492 // is fixed by #224 and tested by comparing the timestamps of the stored objects.
493 infoFoo, err := fsDotgit.Stat(filepath.Join("objects", "19", "102815663d23f8b75a47e7a01965dcdc96468c"))
494 c.Assert(err, IsNil) // checking objects file exists
495 c.Assert(infoLicense.ModTime().Before(infoFoo.ModTime()), Equals, true) // object of foo has another/greaterThan timestamp than LICENSE
496
497 infoLicenseSecond, err := fsDotgit.Stat(filepath.Join("objects", "04", "84eba0d41636ba71fa612c78559cd6c3006cde"))
498 c.Assert(err, IsNil)
499
500 log.Printf("comparing mod time: %v == %v on %v (%v)", infoLicenseSecond.ModTime(), infoLicense.ModTime(), runtime.GOOS, runtime.GOARCH)
501 c.Assert(infoLicenseSecond.ModTime(), Equals, infoLicense.ModTime()) // object of LICENSE should have the same timestamp because no additional write operation was performed
502}
503
504func assertStorageStatus(
505 c *C, r *Repository,
506 treesCount, blobCount, commitCount int, head plumbing.Hash,
507) {
508 trees, err := r.Storer.IterEncodedObjects(plumbing.TreeObject)
509 c.Assert(err, IsNil)
510 blobs, err := r.Storer.IterEncodedObjects(plumbing.BlobObject)
511 c.Assert(err, IsNil)
512 commits, err := r.Storer.IterEncodedObjects(plumbing.CommitObject)
513 c.Assert(err, IsNil)
514
515 c.Assert(lenIterEncodedObjects(trees), Equals, treesCount)
516 c.Assert(lenIterEncodedObjects(blobs), Equals, blobCount)
517 c.Assert(lenIterEncodedObjects(commits), Equals, commitCount)
518
519 ref, err := r.Head()
520 c.Assert(err, IsNil)
521 c.Assert(ref.Hash(), Equals, head)
522}
523
524func lenIterEncodedObjects(iter storer.EncodedObjectIter) int {
525 count := 0
526 iter.ForEach(func(plumbing.EncodedObject) error {
527 count++
528 return nil
529 })
530
531 return count
532}
533
534func defaultSignature() *object.Signature {
535 when, _ := time.Parse(object.DateFormat, "Thu May 04 00:03:43 2017 +0200")
536 return &object.Signature{
537 Name: "foo",
538 Email: "foo@foo.foo",
539 When: when,
540 }
541}
542
543func commitSignKey(c *C, decrypt bool) *openpgp.Entity {
544 s := strings.NewReader(armoredKeyRing)
545 es, err := openpgp.ReadArmoredKeyRing(s)
546 c.Assert(err, IsNil)
547
548 c.Assert(es, HasLen, 1)
549 c.Assert(es[0].Identities, HasLen, 1)
550 _, ok := es[0].Identities["foo bar <foo@foo.foo>"]
551 c.Assert(ok, Equals, true)
552
553 key := es[0]
554 if decrypt {
555 err = key.PrivateKey.Decrypt([]byte(keyPassphrase))
556 c.Assert(err, IsNil)
557 }
558
559 return key
560}
561
562const armoredKeyRing = `
563-----BEGIN PGP PRIVATE KEY BLOCK-----
564
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
617=gIM9
618-----END PGP PRIVATE KEY BLOCK-----
619`
620
621const keyPassphrase = "abcdef0123456789"