1package git
2
3import (
4	"bytes"
5	"io/ioutil"
6	"os"
7	"os/exec"
8	"strings"
9	"time"
10
11	"github.com/go-git/go-git/v5/plumbing"
12	"github.com/go-git/go-git/v5/plumbing/cache"
13	"github.com/go-git/go-git/v5/plumbing/object"
14	"github.com/go-git/go-git/v5/plumbing/storer"
15	"github.com/go-git/go-git/v5/storage/filesystem"
16	"github.com/go-git/go-git/v5/storage/memory"
17
18	"github.com/go-git/go-billy/v5/memfs"
19	"github.com/go-git/go-billy/v5/osfs"
20	"github.com/go-git/go-billy/v5/util"
21	"golang.org/x/crypto/openpgp"
22	"golang.org/x/crypto/openpgp/armor"
23	"golang.org/x/crypto/openpgp/errors"
24	. "gopkg.in/check.v1"
25)
26
27func (s *WorktreeSuite) TestCommitEmptyOptions(c *C) {
28	r, err := Init(memory.NewStorage(), memfs.New())
29	c.Assert(err, IsNil)
30
31	w, err := r.Worktree()
32	c.Assert(err, IsNil)
33
34	hash, err := w.Commit("foo", &CommitOptions{})
35	c.Assert(err, IsNil)
36	c.Assert(hash.IsZero(), Equals, false)
37
38	commit, err := r.CommitObject(hash)
39	c.Assert(err, IsNil)
40	c.Assert(commit.Author.Name, Not(Equals), "")
41}
42
43func (s *WorktreeSuite) TestCommitInitial(c *C) {
44	expected := plumbing.NewHash("98c4ac7c29c913f7461eae06e024dc18e80d23a4")
45
46	fs := memfs.New()
47	storage := memory.NewStorage()
48
49	r, err := Init(storage, fs)
50	c.Assert(err, IsNil)
51
52	w, err := r.Worktree()
53	c.Assert(err, IsNil)
54
55	util.WriteFile(fs, "foo", []byte("foo"), 0644)
56
57	_, err = w.Add("foo")
58	c.Assert(err, IsNil)
59
60	hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature()})
61	c.Assert(hash, Equals, expected)
62	c.Assert(err, IsNil)
63
64	assertStorageStatus(c, r, 1, 1, 1, expected)
65}
66
67func (s *WorktreeSuite) TestCommitParent(c *C) {
68	expected := plumbing.NewHash("ef3ca05477530b37f48564be33ddd48063fc7a22")
69
70	fs := memfs.New()
71	w := &Worktree{
72		r:          s.Repository,
73		Filesystem: fs,
74	}
75
76	err := w.Checkout(&CheckoutOptions{})
77	c.Assert(err, IsNil)
78
79	util.WriteFile(fs, "foo", []byte("foo"), 0644)
80
81	_, err = w.Add("foo")
82	c.Assert(err, IsNil)
83
84	hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature()})
85	c.Assert(hash, Equals, expected)
86	c.Assert(err, IsNil)
87
88	assertStorageStatus(c, s.Repository, 13, 11, 10, expected)
89}
90
91func (s *WorktreeSuite) TestCommitAll(c *C) {
92	expected := plumbing.NewHash("aede6f8c9c1c7ec9ca8d287c64b8ed151276fa28")
93
94	fs := memfs.New()
95	w := &Worktree{
96		r:          s.Repository,
97		Filesystem: fs,
98	}
99
100	err := w.Checkout(&CheckoutOptions{})
101	c.Assert(err, IsNil)
102
103	util.WriteFile(fs, "LICENSE", []byte("foo"), 0644)
104	util.WriteFile(fs, "foo", []byte("foo"), 0644)
105
106	hash, err := w.Commit("foo\n", &CommitOptions{
107		All:    true,
108		Author: defaultSignature(),
109	})
110
111	c.Assert(hash, Equals, expected)
112	c.Assert(err, IsNil)
113
114	assertStorageStatus(c, s.Repository, 13, 11, 10, expected)
115}
116
117func (s *WorktreeSuite) TestRemoveAndCommitAll(c *C) {
118	expected := plumbing.NewHash("907cd576c6ced2ecd3dab34a72bf9cf65944b9a9")
119
120	fs := memfs.New()
121	w := &Worktree{
122		r:          s.Repository,
123		Filesystem: fs,
124	}
125
126	err := w.Checkout(&CheckoutOptions{})
127	c.Assert(err, IsNil)
128
129	util.WriteFile(fs, "foo", []byte("foo"), 0644)
130	_, err = w.Add("foo")
131	c.Assert(err, IsNil)
132
133	_, errFirst := w.Commit("Add in Repo\n", &CommitOptions{
134		Author: defaultSignature(),
135	})
136	c.Assert(errFirst, IsNil)
137
138	errRemove := fs.Remove("foo")
139	c.Assert(errRemove, IsNil)
140
141	hash, errSecond := w.Commit("Remove foo\n", &CommitOptions{
142		All:    true,
143		Author: defaultSignature(),
144	})
145	c.Assert(errSecond, IsNil)
146
147	c.Assert(hash, Equals, expected)
148	c.Assert(err, IsNil)
149
150	assertStorageStatus(c, s.Repository, 13, 11, 11, expected)
151}
152
153func (s *WorktreeSuite) TestCommitSign(c *C) {
154	fs := memfs.New()
155	storage := memory.NewStorage()
156
157	r, err := Init(storage, fs)
158	c.Assert(err, IsNil)
159
160	w, err := r.Worktree()
161	c.Assert(err, IsNil)
162
163	util.WriteFile(fs, "foo", []byte("foo"), 0644)
164
165	_, err = w.Add("foo")
166	c.Assert(err, IsNil)
167
168	key := commitSignKey(c, true)
169	hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature(), SignKey: key})
170	c.Assert(err, IsNil)
171
172	// Verify the commit.
173	pks := new(bytes.Buffer)
174	pkw, err := armor.Encode(pks, openpgp.PublicKeyType, nil)
175	c.Assert(err, IsNil)
176
177	err = key.Serialize(pkw)
178	c.Assert(err, IsNil)
179	err = pkw.Close()
180	c.Assert(err, IsNil)
181
182	expectedCommit, err := r.CommitObject(hash)
183	c.Assert(err, IsNil)
184	actual, err := expectedCommit.Verify(pks.String())
185	c.Assert(err, IsNil)
186	c.Assert(actual.PrimaryKey, DeepEquals, key.PrimaryKey)
187}
188
189func (s *WorktreeSuite) TestCommitSignBadKey(c *C) {
190	fs := memfs.New()
191	storage := memory.NewStorage()
192
193	r, err := Init(storage, fs)
194	c.Assert(err, IsNil)
195
196	w, err := r.Worktree()
197	c.Assert(err, IsNil)
198
199	util.WriteFile(fs, "foo", []byte("foo"), 0644)
200
201	_, err = w.Add("foo")
202	c.Assert(err, IsNil)
203
204	key := commitSignKey(c, false)
205	_, err = w.Commit("foo\n", &CommitOptions{Author: defaultSignature(), SignKey: key})
206	c.Assert(err, Equals, errors.InvalidArgumentError("signing key is encrypted"))
207}
208
209func (s *WorktreeSuite) TestCommitTreeSort(c *C) {
210	path, err := ioutil.TempDir(os.TempDir(), "test-commit-tree-sort")
211	c.Assert(err, IsNil)
212	fs := osfs.New(path)
213	st := filesystem.NewStorage(fs, cache.NewObjectLRUDefault())
214	r, err := Init(st, nil)
215	c.Assert(err, IsNil)
216
217	r, _ = Clone(memory.NewStorage(), memfs.New(), &CloneOptions{
218		URL: path,
219	})
220
221	w, err := r.Worktree()
222	c.Assert(err, IsNil)
223
224	mfs := w.Filesystem
225
226	err = mfs.MkdirAll("delta", 0755)
227	c.Assert(err, IsNil)
228
229	for _, p := range []string{"delta_last", "Gamma", "delta/middle", "Beta", "delta-first", "alpha"} {
230		util.WriteFile(mfs, p, []byte("foo"), 0644)
231		_, err = w.Add(p)
232		c.Assert(err, IsNil)
233	}
234
235	_, err = w.Commit("foo\n", &CommitOptions{
236		All:    true,
237		Author: defaultSignature(),
238	})
239	c.Assert(err, IsNil)
240
241	err = r.Push(&PushOptions{})
242	c.Assert(err, IsNil)
243
244	cmd := exec.Command("git", "fsck")
245	cmd.Dir = path
246	cmd.Env = os.Environ()
247	buf := &bytes.Buffer{}
248	cmd.Stderr = buf
249	cmd.Stdout = buf
250
251	err = cmd.Run()
252
253	c.Assert(err, IsNil, Commentf("%s", buf.Bytes()))
254}
255
256func assertStorageStatus(
257	c *C, r *Repository,
258	treesCount, blobCount, commitCount int, head plumbing.Hash,
259) {
260	trees, err := r.Storer.IterEncodedObjects(plumbing.TreeObject)
261	c.Assert(err, IsNil)
262	blobs, err := r.Storer.IterEncodedObjects(plumbing.BlobObject)
263	c.Assert(err, IsNil)
264	commits, err := r.Storer.IterEncodedObjects(plumbing.CommitObject)
265	c.Assert(err, IsNil)
266
267	c.Assert(lenIterEncodedObjects(trees), Equals, treesCount)
268	c.Assert(lenIterEncodedObjects(blobs), Equals, blobCount)
269	c.Assert(lenIterEncodedObjects(commits), Equals, commitCount)
270
271	ref, err := r.Head()
272	c.Assert(err, IsNil)
273	c.Assert(ref.Hash(), Equals, head)
274}
275
276func lenIterEncodedObjects(iter storer.EncodedObjectIter) int {
277	count := 0
278	iter.ForEach(func(plumbing.EncodedObject) error {
279		count++
280		return nil
281	})
282
283	return count
284}
285
286func defaultSignature() *object.Signature {
287	when, _ := time.Parse(object.DateFormat, "Thu May 04 00:03:43 2017 +0200")
288	return &object.Signature{
289		Name:  "foo",
290		Email: "foo@foo.foo",
291		When:  when,
292	}
293}
294
295func commitSignKey(c *C, decrypt bool) *openpgp.Entity {
296	s := strings.NewReader(armoredKeyRing)
297	es, err := openpgp.ReadArmoredKeyRing(s)
298	c.Assert(err, IsNil)
299
300	c.Assert(es, HasLen, 1)
301	c.Assert(es[0].Identities, HasLen, 1)
302	_, ok := es[0].Identities["foo bar <foo@foo.foo>"]
303	c.Assert(ok, Equals, true)
304
305	key := es[0]
306	if decrypt {
307		err = key.PrivateKey.Decrypt([]byte(keyPassphrase))
308		c.Assert(err, IsNil)
309	}
310
311	return key
312}
313
314const armoredKeyRing = `
315-----BEGIN PGP PRIVATE KEY BLOCK-----
316
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
369=gIM9
370-----END PGP PRIVATE KEY BLOCK-----
371`
372
373const keyPassphrase = "abcdef0123456789"
374