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