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/object"
13 "gopkg.in/src-d/go-git.v4/plumbing/storer"
14 "gopkg.in/src-d/go-git.v4/storage/filesystem"
15 "gopkg.in/src-d/go-git.v4/storage/memory"
16
17 "golang.org/x/crypto/openpgp"
18 "golang.org/x/crypto/openpgp/armor"
19 "golang.org/x/crypto/openpgp/errors"
20 . "gopkg.in/check.v1"
21 "gopkg.in/src-d/go-billy.v4/memfs"
22 "gopkg.in/src-d/go-billy.v4/osfs"
23 "gopkg.in/src-d/go-billy.v4/util"
24)
25
26func (s *WorktreeSuite) TestCommitInvalidOptions(c *C) {
27 r, err := Init(memory.NewStorage(), memfs.New())
28 c.Assert(err, IsNil)
29
30 w, err := r.Worktree()
31 c.Assert(err, IsNil)
32
33 hash, err := w.Commit("", &CommitOptions{})
34 c.Assert(err, Equals, ErrMissingAuthor)
35 c.Assert(hash.IsZero(), Equals, true)
36}
37
38func (s *WorktreeSuite) TestCommitInitial(c *C) {
39 expected := plumbing.NewHash("98c4ac7c29c913f7461eae06e024dc18e80d23a4")
40
41 fs := memfs.New()
42 storage := memory.NewStorage()
43
44 r, err := Init(storage, fs)
45 c.Assert(err, IsNil)
46
47 w, err := r.Worktree()
48 c.Assert(err, IsNil)
49
50 util.WriteFile(fs, "foo", []byte("foo"), 0644)
51
52 _, err = w.Add("foo")
53 c.Assert(err, IsNil)
54
55 hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature()})
56 c.Assert(hash, Equals, expected)
57 c.Assert(err, IsNil)
58
59 assertStorageStatus(c, r, 1, 1, 1, expected)
60}
61
62func (s *WorktreeSuite) TestCommitParent(c *C) {
63 expected := plumbing.NewHash("ef3ca05477530b37f48564be33ddd48063fc7a22")
64
65 fs := memfs.New()
66 w := &Worktree{
67 r: s.Repository,
68 Filesystem: fs,
69 }
70
71 err := w.Checkout(&CheckoutOptions{})
72 c.Assert(err, IsNil)
73
74 util.WriteFile(fs, "foo", []byte("foo"), 0644)
75
76 _, err = w.Add("foo")
77 c.Assert(err, IsNil)
78
79 hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature()})
80 c.Assert(hash, Equals, expected)
81 c.Assert(err, IsNil)
82
83 assertStorageStatus(c, s.Repository, 13, 11, 10, expected)
84}
85
86func (s *WorktreeSuite) TestCommitAll(c *C) {
87 expected := plumbing.NewHash("aede6f8c9c1c7ec9ca8d287c64b8ed151276fa28")
88
89 fs := memfs.New()
90 w := &Worktree{
91 r: s.Repository,
92 Filesystem: fs,
93 }
94
95 err := w.Checkout(&CheckoutOptions{})
96 c.Assert(err, IsNil)
97
98 util.WriteFile(fs, "LICENSE", []byte("foo"), 0644)
99 util.WriteFile(fs, "foo", []byte("foo"), 0644)
100
101 hash, err := w.Commit("foo\n", &CommitOptions{
102 All: true,
103 Author: defaultSignature(),
104 })
105
106 c.Assert(hash, Equals, expected)
107 c.Assert(err, IsNil)
108
109 assertStorageStatus(c, s.Repository, 13, 11, 10, expected)
110}
111
112func (s *WorktreeSuite) TestRemoveAndCommitAll(c *C) {
113 expected := plumbing.NewHash("907cd576c6ced2ecd3dab34a72bf9cf65944b9a9")
114
115 fs := memfs.New()
116 w := &Worktree{
117 r: s.Repository,
118 Filesystem: fs,
119 }
120
121 err := w.Checkout(&CheckoutOptions{})
122 c.Assert(err, IsNil)
123
124 util.WriteFile(fs, "foo", []byte("foo"), 0644)
125 _, err = w.Add("foo")
126 c.Assert(err, IsNil)
127
128 _, errFirst := w.Commit("Add in Repo\n", &CommitOptions{
129 Author: defaultSignature(),
130 })
131 c.Assert(errFirst, IsNil)
132
133 errRemove := fs.Remove("foo")
134 c.Assert(errRemove, IsNil)
135
136 hash, errSecond := w.Commit("Remove foo\n", &CommitOptions{
137 All: true,
138 Author: defaultSignature(),
139 })
140 c.Assert(errSecond, IsNil)
141
142 c.Assert(hash, Equals, expected)
143 c.Assert(err, IsNil)
144
145 assertStorageStatus(c, s.Repository, 13, 11, 11, expected)
146}
147
148func (s *WorktreeSuite) TestCommitSign(c *C) {
149 fs := memfs.New()
150 storage := memory.NewStorage()
151
152 r, err := Init(storage, fs)
153 c.Assert(err, IsNil)
154
155 w, err := r.Worktree()
156 c.Assert(err, IsNil)
157
158 util.WriteFile(fs, "foo", []byte("foo"), 0644)
159
160 _, err = w.Add("foo")
161 c.Assert(err, IsNil)
162
163 key := commitSignKey(c, true)
164 hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature(), SignKey: key})
165 c.Assert(err, IsNil)
166
167 // Verify the commit.
168 pks := new(bytes.Buffer)
169 pkw, err := armor.Encode(pks, openpgp.PublicKeyType, nil)
170 c.Assert(err, IsNil)
171
172 err = key.Serialize(pkw)
173 c.Assert(err, IsNil)
174 err = pkw.Close()
175 c.Assert(err, IsNil)
176
177 expectedCommit, err := r.CommitObject(hash)
178 c.Assert(err, IsNil)
179 actual, err := expectedCommit.Verify(pks.String())
180 c.Assert(err, IsNil)
181 c.Assert(actual.PrimaryKey, DeepEquals, key.PrimaryKey)
182}
183
184func (s *WorktreeSuite) TestCommitSignBadKey(c *C) {
185 fs := memfs.New()
186 storage := memory.NewStorage()
187
188 r, err := Init(storage, fs)
189 c.Assert(err, IsNil)
190
191 w, err := r.Worktree()
192 c.Assert(err, IsNil)
193
194 util.WriteFile(fs, "foo", []byte("foo"), 0644)
195
196 _, err = w.Add("foo")
197 c.Assert(err, IsNil)
198
199 key := commitSignKey(c, false)
200 _, err = w.Commit("foo\n", &CommitOptions{Author: defaultSignature(), SignKey: key})
201 c.Assert(err, Equals, errors.InvalidArgumentError("signing key is encrypted"))
202}
203
204func (s *WorktreeSuite) TestCommitTreeSort(c *C) {
205 path, err := ioutil.TempDir(os.TempDir(), "test-commit-tree-sort")
206 c.Assert(err, IsNil)
207 fs := osfs.New(path)
208 st, err := filesystem.NewStorage(fs)
209 c.Assert(err, IsNil)
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=8g3t
366-----END PGP PRIVATE KEY BLOCK-----
367`
368
369const keyPassphrase = "abcdef0123456789"