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	r, err := Init(memory.NewStorage(), memfs.New())
30	c.Assert(err, IsNil)
31
32	w, err := r.Worktree()
33	c.Assert(err, IsNil)
34
35	hash, err := w.Commit("foo", &CommitOptions{})
36	c.Assert(err, IsNil)
37	c.Assert(hash.IsZero(), Equals, false)
38
39	commit, err := r.CommitObject(hash)
40	c.Assert(err, IsNil)
41	c.Assert(commit.Author.Name, Not(Equals), "")
42}
43
44func (s *WorktreeSuite) TestCommitInitial(c *C) {
45	expected := plumbing.NewHash("98c4ac7c29c913f7461eae06e024dc18e80d23a4")
46
47	fs := memfs.New()
48	storage := memory.NewStorage()
49
50	r, err := Init(storage, fs)
51	c.Assert(err, IsNil)
52
53	w, err := r.Worktree()
54	c.Assert(err, IsNil)
55
56	util.WriteFile(fs, "foo", []byte("foo"), 0644)
57
58	_, err = w.Add("foo")
59	c.Assert(err, IsNil)
60
61	hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature()})
62	c.Assert(hash, Equals, expected)
63	c.Assert(err, IsNil)
64
65	assertStorageStatus(c, r, 1, 1, 1, expected)
66}
67
68func (s *WorktreeSuite) TestCommitParent(c *C) {
69	expected := plumbing.NewHash("ef3ca05477530b37f48564be33ddd48063fc7a22")
70
71	fs := memfs.New()
72	w := &Worktree{
73		r:          s.Repository,
74		Filesystem: fs,
75	}
76
77	err := w.Checkout(&CheckoutOptions{})
78	c.Assert(err, IsNil)
79
80	util.WriteFile(fs, "foo", []byte("foo"), 0644)
81
82	_, err = w.Add("foo")
83	c.Assert(err, IsNil)
84
85	hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature()})
86	c.Assert(hash, Equals, expected)
87	c.Assert(err, IsNil)
88
89	assertStorageStatus(c, s.Repository, 13, 11, 10, expected)
90}
91
92func (s *WorktreeSuite) TestCommitAll(c *C) {
93	expected := plumbing.NewHash("aede6f8c9c1c7ec9ca8d287c64b8ed151276fa28")
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, "LICENSE", []byte("foo"), 0644)
105	util.WriteFile(fs, "foo", []byte("foo"), 0644)
106
107	hash, err := w.Commit("foo\n", &CommitOptions{
108		All:    true,
109		Author: defaultSignature(),
110	})
111
112	c.Assert(hash, Equals, expected)
113	c.Assert(err, IsNil)
114
115	assertStorageStatus(c, s.Repository, 13, 11, 10, expected)
116}
117
118func (s *WorktreeSuite) TestRemoveAndCommitAll(c *C) {
119	expected := plumbing.NewHash("907cd576c6ced2ecd3dab34a72bf9cf65944b9a9")
120
121	fs := memfs.New()
122	w := &Worktree{
123		r:          s.Repository,
124		Filesystem: fs,
125	}
126
127	err := w.Checkout(&CheckoutOptions{})
128	c.Assert(err, IsNil)
129
130	util.WriteFile(fs, "foo", []byte("foo"), 0644)
131	_, err = w.Add("foo")
132	c.Assert(err, IsNil)
133
134	_, errFirst := w.Commit("Add in Repo\n", &CommitOptions{
135		Author: defaultSignature(),
136	})
137	c.Assert(errFirst, IsNil)
138
139	errRemove := fs.Remove("foo")
140	c.Assert(errRemove, IsNil)
141
142	hash, errSecond := w.Commit("Remove foo\n", &CommitOptions{
143		All:    true,
144		Author: defaultSignature(),
145	})
146	c.Assert(errSecond, IsNil)
147
148	c.Assert(hash, Equals, expected)
149	c.Assert(err, IsNil)
150
151	assertStorageStatus(c, s.Repository, 13, 11, 11, expected)
152}
153
154func (s *WorktreeSuite) TestCommitSign(c *C) {
155	fs := memfs.New()
156	storage := memory.NewStorage()
157
158	r, err := Init(storage, fs)
159	c.Assert(err, IsNil)
160
161	w, err := r.Worktree()
162	c.Assert(err, IsNil)
163
164	util.WriteFile(fs, "foo", []byte("foo"), 0644)
165
166	_, err = w.Add("foo")
167	c.Assert(err, IsNil)
168
169	key := commitSignKey(c, true)
170	hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature(), SignKey: key})
171	c.Assert(err, IsNil)
172
173	// Verify the commit.
174	pks := new(bytes.Buffer)
175	pkw, err := armor.Encode(pks, openpgp.PublicKeyType, nil)
176	c.Assert(err, IsNil)
177
178	err = key.Serialize(pkw)
179	c.Assert(err, IsNil)
180	err = pkw.Close()
181	c.Assert(err, IsNil)
182
183	expectedCommit, err := r.CommitObject(hash)
184	c.Assert(err, IsNil)
185	actual, err := expectedCommit.Verify(pks.String())
186	c.Assert(err, IsNil)
187	c.Assert(actual.PrimaryKey, DeepEquals, key.PrimaryKey)
188}
189
190func (s *WorktreeSuite) TestCommitSignBadKey(c *C) {
191	fs := memfs.New()
192	storage := memory.NewStorage()
193
194	r, err := Init(storage, fs)
195	c.Assert(err, IsNil)
196
197	w, err := r.Worktree()
198	c.Assert(err, IsNil)
199
200	util.WriteFile(fs, "foo", []byte("foo"), 0644)
201
202	_, err = w.Add("foo")
203	c.Assert(err, IsNil)
204
205	key := commitSignKey(c, false)
206	_, err = w.Commit("foo\n", &CommitOptions{Author: defaultSignature(), SignKey: key})
207	c.Assert(err, Equals, errors.InvalidArgumentError("signing key is encrypted"))
208}
209
210func (s *WorktreeSuite) TestCommitTreeSort(c *C) {
211	fs, clean := s.TemporalFilesystem()
212	defer clean()
213
214	st := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
215	r, err := Init(st, nil)
216	c.Assert(err, IsNil)
217
218	r, _ = Clone(memory.NewStorage(), memfs.New(), &CloneOptions{
219		URL: fs.Root(),
220	})
221
222	w, err := r.Worktree()
223	c.Assert(err, IsNil)
224
225	mfs := w.Filesystem
226
227	err = mfs.MkdirAll("delta", 0755)
228	c.Assert(err, IsNil)
229
230	for _, p := range []string{"delta_last", "Gamma", "delta/middle", "Beta", "delta-first", "alpha"} {
231		util.WriteFile(mfs, p, []byte("foo"), 0644)
232		_, err = w.Add(p)
233		c.Assert(err, IsNil)
234	}
235
236	_, err = w.Commit("foo\n", &CommitOptions{
237		All:    true,
238		Author: defaultSignature(),
239	})
240	c.Assert(err, IsNil)
241
242	err = r.Push(&PushOptions{})
243	c.Assert(err, IsNil)
244
245	cmd := exec.Command("git", "fsck")
246	cmd.Dir = fs.Root()
247	cmd.Env = os.Environ()
248	buf := &bytes.Buffer{}
249	cmd.Stderr = buf
250	cmd.Stdout = buf
251
252	err = cmd.Run()
253
254	c.Assert(err, IsNil, Commentf("%s", buf.Bytes()))
255}
256
257// https://github.com/go-git/go-git/pull/224
258func (s *WorktreeSuite) TestJustStoreObjectsNotAlreadyStored(c *C) {
259	fs, clean := s.TemporalFilesystem()
260	defer clean()
261
262	fsDotgit, err := fs.Chroot(".git") // real fs to get modified timestamps
263	c.Assert(err, IsNil)
264	storage := filesystem.NewStorage(fsDotgit, cache.NewObjectLRUDefault())
265
266	r, err := Init(storage, fs)
267	c.Assert(err, IsNil)
268
269	w, err := r.Worktree()
270	c.Assert(err, IsNil)
271
272	// Step 1: Write LICENSE
273	util.WriteFile(fs, "LICENSE", []byte("license"), 0644)
274	hLicense, err := w.Add("LICENSE")
275	c.Assert(err, IsNil)
276	c.Assert(hLicense, Equals, plumbing.NewHash("0484eba0d41636ba71fa612c78559cd6c3006cde"))
277
278	hash, err := w.Commit("commit 1\n", &CommitOptions{
279		All:    true,
280		Author: defaultSignature(),
281	})
282	c.Assert(err, IsNil)
283	c.Assert(hash, Equals, plumbing.NewHash("7a7faee4630d2664a6869677cc8ab614f3fd4a18"))
284
285	infoLicense, err := fsDotgit.Stat(filepath.Join("objects", "04", "84eba0d41636ba71fa612c78559cd6c3006cde"))
286	c.Assert(err, IsNil) // checking objects file exists
287
288	// Step 2: Write foo.
289	time.Sleep(5 * time.Millisecond) // uncool, but we need to get different timestamps...
290	util.WriteFile(fs, "foo", []byte("foo"), 0644)
291	hFoo, err := w.Add("foo")
292	c.Assert(err, IsNil)
293	c.Assert(hFoo, Equals, plumbing.NewHash("19102815663d23f8b75a47e7a01965dcdc96468c"))
294
295	hash, err = w.Commit("commit 2\n", &CommitOptions{
296		All:    true,
297		Author: defaultSignature(),
298	})
299	c.Assert(hash, Equals, plumbing.NewHash("97c0c5177e6ac57d10e8ea0017f2d39b91e2b364"))
300
301	// Step 3: Check
302	// There is no need to overwrite the object of LICENSE, because its content
303	// was not changed. Just a write on the object of foo is required. This behaviour
304	// is fixed by #224 and tested by comparing the timestamps of the stored objects.
305	infoFoo, err := fsDotgit.Stat(filepath.Join("objects", "19", "102815663d23f8b75a47e7a01965dcdc96468c"))
306	c.Assert(err, IsNil)                                                    // checking objects file exists
307	c.Assert(infoLicense.ModTime().Before(infoFoo.ModTime()), Equals, true) // object of foo has another/greaterThan timestamp than LICENSE
308
309	infoLicenseSecond, err := fsDotgit.Stat(filepath.Join("objects", "04", "84eba0d41636ba71fa612c78559cd6c3006cde"))
310	c.Assert(err, IsNil)
311
312	log.Printf("comparing mod time: %v == %v on %v (%v)", infoLicenseSecond.ModTime(), infoLicense.ModTime(), runtime.GOOS, runtime.GOARCH)
313	c.Assert(infoLicenseSecond.ModTime(), Equals, infoLicense.ModTime()) // object of LICENSE should have the same timestamp because no additional write operation was performed
314}
315
316func assertStorageStatus(
317	c *C, r *Repository,
318	treesCount, blobCount, commitCount int, head plumbing.Hash,
319) {
320	trees, err := r.Storer.IterEncodedObjects(plumbing.TreeObject)
321	c.Assert(err, IsNil)
322	blobs, err := r.Storer.IterEncodedObjects(plumbing.BlobObject)
323	c.Assert(err, IsNil)
324	commits, err := r.Storer.IterEncodedObjects(plumbing.CommitObject)
325	c.Assert(err, IsNil)
326
327	c.Assert(lenIterEncodedObjects(trees), Equals, treesCount)
328	c.Assert(lenIterEncodedObjects(blobs), Equals, blobCount)
329	c.Assert(lenIterEncodedObjects(commits), Equals, commitCount)
330
331	ref, err := r.Head()
332	c.Assert(err, IsNil)
333	c.Assert(ref.Hash(), Equals, head)
334}
335
336func lenIterEncodedObjects(iter storer.EncodedObjectIter) int {
337	count := 0
338	iter.ForEach(func(plumbing.EncodedObject) error {
339		count++
340		return nil
341	})
342
343	return count
344}
345
346func defaultSignature() *object.Signature {
347	when, _ := time.Parse(object.DateFormat, "Thu May 04 00:03:43 2017 +0200")
348	return &object.Signature{
349		Name:  "foo",
350		Email: "foo@foo.foo",
351		When:  when,
352	}
353}
354
355func commitSignKey(c *C, decrypt bool) *openpgp.Entity {
356	s := strings.NewReader(armoredKeyRing)
357	es, err := openpgp.ReadArmoredKeyRing(s)
358	c.Assert(err, IsNil)
359
360	c.Assert(es, HasLen, 1)
361	c.Assert(es[0].Identities, HasLen, 1)
362	_, ok := es[0].Identities["foo bar <foo@foo.foo>"]
363	c.Assert(ok, Equals, true)
364
365	key := es[0]
366	if decrypt {
367		err = key.PrivateKey.Decrypt([]byte(keyPassphrase))
368		c.Assert(err, IsNil)
369	}
370
371	return key
372}
373
374const armoredKeyRing = `
375-----BEGIN PGP PRIVATE KEY BLOCK-----
376
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
429=gIM9
430-----END PGP PRIVATE KEY BLOCK-----
431`
432
433const keyPassphrase = "abcdef0123456789"
434