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