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