fork of go-git with some jj specific features
at v5.12.0 18 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 util.WriteFile(fs, "bar", []byte("bar"), 0644) 135 136 _, err = w.Add("bar") 137 c.Assert(err, IsNil) 138 139 amendedHash, err := w.Commit("bar\n", &CommitOptions{Amend: true}) 140 c.Assert(err, IsNil) 141 142 headRef, err := w.r.Head() 143 c.Assert(err, IsNil) 144 145 c.Assert(amendedHash, Equals, headRef.Hash()) 146 147 commit, err := w.r.CommitObject(headRef.Hash()) 148 c.Assert(err, IsNil) 149 c.Assert(commit.Message, Equals, "bar\n") 150 c.Assert(commit.NumParents(), Equals, 1) 151 152 stats, err := commit.Stats() 153 c.Assert(err, IsNil) 154 c.Assert(stats, HasLen, 2) 155 c.Assert(stats[0], Equals, object.FileStat{ 156 Name: "bar", 157 Addition: 1, 158 }) 159 c.Assert(stats[1], Equals, object.FileStat{ 160 Name: "foo", 161 Addition: 1, 162 }) 163 164 assertStorageStatus(c, s.Repository, 14, 12, 11, amendedHash) 165} 166 167func (s *WorktreeSuite) TestAddAndCommitWithSkipStatus(c *C) { 168 expected := plumbing.NewHash("375a3808ffde7f129cdd3c8c252fd0fe37cfd13b") 169 170 fs := memfs.New() 171 w := &Worktree{ 172 r: s.Repository, 173 Filesystem: fs, 174 } 175 176 err := w.Checkout(&CheckoutOptions{}) 177 c.Assert(err, IsNil) 178 179 util.WriteFile(fs, "LICENSE", []byte("foo"), 0644) 180 util.WriteFile(fs, "foo", []byte("foo"), 0644) 181 182 err = w.AddWithOptions(&AddOptions{ 183 Path: "foo", 184 SkipStatus: true, 185 }) 186 c.Assert(err, IsNil) 187 188 hash, err := w.Commit("commit foo only\n", &CommitOptions{ 189 Author: defaultSignature(), 190 }) 191 192 c.Assert(hash, Equals, expected) 193 c.Assert(err, IsNil) 194 195 assertStorageStatus(c, s.Repository, 13, 11, 10, expected) 196} 197 198func (s *WorktreeSuite) TestAddAndCommitWithSkipStatusPathNotModified(c *C) { 199 expected := plumbing.NewHash("375a3808ffde7f129cdd3c8c252fd0fe37cfd13b") 200 expected2 := plumbing.NewHash("8691273baf8f6ee2cccfc05e910552c04d02d472") 201 202 fs := memfs.New() 203 w := &Worktree{ 204 r: s.Repository, 205 Filesystem: fs, 206 } 207 208 err := w.Checkout(&CheckoutOptions{}) 209 c.Assert(err, IsNil) 210 211 util.WriteFile(fs, "foo", []byte("foo"), 0644) 212 213 status, err := w.Status() 214 c.Assert(err, IsNil) 215 foo := status.File("foo") 216 c.Assert(foo.Staging, Equals, Untracked) 217 c.Assert(foo.Worktree, Equals, Untracked) 218 219 err = w.AddWithOptions(&AddOptions{ 220 Path: "foo", 221 SkipStatus: true, 222 }) 223 c.Assert(err, IsNil) 224 225 status, err = w.Status() 226 c.Assert(err, IsNil) 227 foo = status.File("foo") 228 c.Assert(foo.Staging, Equals, Added) 229 c.Assert(foo.Worktree, Equals, Unmodified) 230 231 hash, err := w.Commit("commit foo only\n", &CommitOptions{All: true, 232 Author: defaultSignature(), 233 }) 234 c.Assert(hash, Equals, expected) 235 c.Assert(err, IsNil) 236 commit1, err := w.r.CommitObject(hash) 237 238 status, err = w.Status() 239 c.Assert(err, IsNil) 240 foo = status.File("foo") 241 c.Assert(foo.Staging, Equals, Untracked) 242 c.Assert(foo.Worktree, Equals, Untracked) 243 244 assertStorageStatus(c, s.Repository, 13, 11, 10, expected) 245 246 err = w.AddWithOptions(&AddOptions{ 247 Path: "foo", 248 SkipStatus: true, 249 }) 250 c.Assert(err, IsNil) 251 252 status, err = w.Status() 253 c.Assert(err, IsNil) 254 foo = status.File("foo") 255 c.Assert(foo.Staging, Equals, Untracked) 256 c.Assert(foo.Worktree, Equals, Untracked) 257 258 hash, err = w.Commit("commit with no changes\n", &CommitOptions{ 259 Author: defaultSignature(), 260 }) 261 c.Assert(hash, Equals, expected2) 262 c.Assert(err, IsNil) 263 commit2, err := w.r.CommitObject(hash) 264 265 status, err = w.Status() 266 c.Assert(err, IsNil) 267 foo = status.File("foo") 268 c.Assert(foo.Staging, Equals, Untracked) 269 c.Assert(foo.Worktree, Equals, Untracked) 270 271 patch, err := commit2.Patch(commit1) 272 c.Assert(err, IsNil) 273 files := patch.FilePatches() 274 c.Assert(files, IsNil) 275 276 assertStorageStatus(c, s.Repository, 13, 11, 11, expected2) 277} 278 279func (s *WorktreeSuite) TestCommitAll(c *C) { 280 expected := plumbing.NewHash("aede6f8c9c1c7ec9ca8d287c64b8ed151276fa28") 281 282 fs := memfs.New() 283 w := &Worktree{ 284 r: s.Repository, 285 Filesystem: fs, 286 } 287 288 err := w.Checkout(&CheckoutOptions{}) 289 c.Assert(err, IsNil) 290 291 util.WriteFile(fs, "LICENSE", []byte("foo"), 0644) 292 util.WriteFile(fs, "foo", []byte("foo"), 0644) 293 294 hash, err := w.Commit("foo\n", &CommitOptions{ 295 All: true, 296 Author: defaultSignature(), 297 }) 298 299 c.Assert(hash, Equals, expected) 300 c.Assert(err, IsNil) 301 302 assertStorageStatus(c, s.Repository, 13, 11, 10, expected) 303} 304 305func (s *WorktreeSuite) TestRemoveAndCommitAll(c *C) { 306 expected := plumbing.NewHash("907cd576c6ced2ecd3dab34a72bf9cf65944b9a9") 307 308 fs := memfs.New() 309 w := &Worktree{ 310 r: s.Repository, 311 Filesystem: fs, 312 } 313 314 err := w.Checkout(&CheckoutOptions{}) 315 c.Assert(err, IsNil) 316 317 util.WriteFile(fs, "foo", []byte("foo"), 0644) 318 _, err = w.Add("foo") 319 c.Assert(err, IsNil) 320 321 _, errFirst := w.Commit("Add in Repo\n", &CommitOptions{ 322 Author: defaultSignature(), 323 }) 324 c.Assert(errFirst, IsNil) 325 326 errRemove := fs.Remove("foo") 327 c.Assert(errRemove, IsNil) 328 329 hash, errSecond := w.Commit("Remove foo\n", &CommitOptions{ 330 All: true, 331 Author: defaultSignature(), 332 }) 333 c.Assert(errSecond, IsNil) 334 335 c.Assert(hash, Equals, expected) 336 c.Assert(err, IsNil) 337 338 assertStorageStatus(c, s.Repository, 13, 11, 11, expected) 339} 340 341func (s *WorktreeSuite) TestCommitSign(c *C) { 342 fs := memfs.New() 343 storage := memory.NewStorage() 344 345 r, err := Init(storage, fs) 346 c.Assert(err, IsNil) 347 348 w, err := r.Worktree() 349 c.Assert(err, IsNil) 350 351 util.WriteFile(fs, "foo", []byte("foo"), 0644) 352 353 _, err = w.Add("foo") 354 c.Assert(err, IsNil) 355 356 key := commitSignKey(c, true) 357 hash, err := w.Commit("foo\n", &CommitOptions{Author: defaultSignature(), SignKey: key}) 358 c.Assert(err, IsNil) 359 360 // Verify the commit. 361 pks := new(bytes.Buffer) 362 pkw, err := armor.Encode(pks, openpgp.PublicKeyType, nil) 363 c.Assert(err, IsNil) 364 365 err = key.Serialize(pkw) 366 c.Assert(err, IsNil) 367 err = pkw.Close() 368 c.Assert(err, IsNil) 369 370 expectedCommit, err := r.CommitObject(hash) 371 c.Assert(err, IsNil) 372 actual, err := expectedCommit.Verify(pks.String()) 373 c.Assert(err, IsNil) 374 c.Assert(actual.PrimaryKey, DeepEquals, key.PrimaryKey) 375} 376 377func (s *WorktreeSuite) TestCommitSignBadKey(c *C) { 378 fs := memfs.New() 379 storage := memory.NewStorage() 380 381 r, err := Init(storage, fs) 382 c.Assert(err, IsNil) 383 384 w, err := r.Worktree() 385 c.Assert(err, IsNil) 386 387 util.WriteFile(fs, "foo", []byte("foo"), 0644) 388 389 _, err = w.Add("foo") 390 c.Assert(err, IsNil) 391 392 key := commitSignKey(c, false) 393 _, err = w.Commit("foo\n", &CommitOptions{Author: defaultSignature(), SignKey: key}) 394 c.Assert(err, Equals, errors.InvalidArgumentError("signing key is encrypted")) 395} 396 397func (s *WorktreeSuite) TestCommitTreeSort(c *C) { 398 fs, clean := s.TemporalFilesystem() 399 defer clean() 400 401 st := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 402 _, err := Init(st, nil) 403 c.Assert(err, IsNil) 404 405 r, _ := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{ 406 URL: fs.Root(), 407 }) 408 409 w, err := r.Worktree() 410 c.Assert(err, IsNil) 411 412 mfs := w.Filesystem 413 414 err = mfs.MkdirAll("delta", 0755) 415 c.Assert(err, IsNil) 416 417 for _, p := range []string{"delta_last", "Gamma", "delta/middle", "Beta", "delta-first", "alpha"} { 418 util.WriteFile(mfs, p, []byte("foo"), 0644) 419 _, err = w.Add(p) 420 c.Assert(err, IsNil) 421 } 422 423 _, err = w.Commit("foo\n", &CommitOptions{ 424 All: true, 425 Author: defaultSignature(), 426 }) 427 c.Assert(err, IsNil) 428 429 err = r.Push(&PushOptions{}) 430 c.Assert(err, IsNil) 431 432 cmd := exec.Command("git", "fsck") 433 cmd.Dir = fs.Root() 434 cmd.Env = os.Environ() 435 buf := &bytes.Buffer{} 436 cmd.Stderr = buf 437 cmd.Stdout = buf 438 439 err = cmd.Run() 440 441 c.Assert(err, IsNil, Commentf("%s", buf.Bytes())) 442} 443 444// https://github.com/go-git/go-git/pull/224 445func (s *WorktreeSuite) TestJustStoreObjectsNotAlreadyStored(c *C) { 446 fs, clean := s.TemporalFilesystem() 447 defer clean() 448 449 fsDotgit, err := fs.Chroot(".git") // real fs to get modified timestamps 450 c.Assert(err, IsNil) 451 storage := filesystem.NewStorage(fsDotgit, cache.NewObjectLRUDefault()) 452 453 r, err := Init(storage, fs) 454 c.Assert(err, IsNil) 455 456 w, err := r.Worktree() 457 c.Assert(err, IsNil) 458 459 // Step 1: Write LICENSE 460 util.WriteFile(fs, "LICENSE", []byte("license"), 0644) 461 hLicense, err := w.Add("LICENSE") 462 c.Assert(err, IsNil) 463 c.Assert(hLicense, Equals, plumbing.NewHash("0484eba0d41636ba71fa612c78559cd6c3006cde")) 464 465 hash, err := w.Commit("commit 1\n", &CommitOptions{ 466 All: true, 467 Author: defaultSignature(), 468 }) 469 c.Assert(err, IsNil) 470 c.Assert(hash, Equals, plumbing.NewHash("7a7faee4630d2664a6869677cc8ab614f3fd4a18")) 471 472 infoLicense, err := fsDotgit.Stat(filepath.Join("objects", "04", "84eba0d41636ba71fa612c78559cd6c3006cde")) 473 c.Assert(err, IsNil) // checking objects file exists 474 475 // Step 2: Write foo. 476 time.Sleep(5 * time.Millisecond) // uncool, but we need to get different timestamps... 477 util.WriteFile(fs, "foo", []byte("foo"), 0644) 478 hFoo, err := w.Add("foo") 479 c.Assert(err, IsNil) 480 c.Assert(hFoo, Equals, plumbing.NewHash("19102815663d23f8b75a47e7a01965dcdc96468c")) 481 482 hash, err = w.Commit("commit 2\n", &CommitOptions{ 483 All: true, 484 Author: defaultSignature(), 485 }) 486 c.Assert(err, IsNil) 487 c.Assert(hash, Equals, plumbing.NewHash("97c0c5177e6ac57d10e8ea0017f2d39b91e2b364")) 488 489 // Step 3: Check 490 // There is no need to overwrite the object of LICENSE, because its content 491 // was not changed. Just a write on the object of foo is required. This behaviour 492 // is fixed by #224 and tested by comparing the timestamps of the stored objects. 493 infoFoo, err := fsDotgit.Stat(filepath.Join("objects", "19", "102815663d23f8b75a47e7a01965dcdc96468c")) 494 c.Assert(err, IsNil) // checking objects file exists 495 c.Assert(infoLicense.ModTime().Before(infoFoo.ModTime()), Equals, true) // object of foo has another/greaterThan timestamp than LICENSE 496 497 infoLicenseSecond, err := fsDotgit.Stat(filepath.Join("objects", "04", "84eba0d41636ba71fa612c78559cd6c3006cde")) 498 c.Assert(err, IsNil) 499 500 log.Printf("comparing mod time: %v == %v on %v (%v)", infoLicenseSecond.ModTime(), infoLicense.ModTime(), runtime.GOOS, runtime.GOARCH) 501 c.Assert(infoLicenseSecond.ModTime(), Equals, infoLicense.ModTime()) // object of LICENSE should have the same timestamp because no additional write operation was performed 502} 503 504func assertStorageStatus( 505 c *C, r *Repository, 506 treesCount, blobCount, commitCount int, head plumbing.Hash, 507) { 508 trees, err := r.Storer.IterEncodedObjects(plumbing.TreeObject) 509 c.Assert(err, IsNil) 510 blobs, err := r.Storer.IterEncodedObjects(plumbing.BlobObject) 511 c.Assert(err, IsNil) 512 commits, err := r.Storer.IterEncodedObjects(plumbing.CommitObject) 513 c.Assert(err, IsNil) 514 515 c.Assert(lenIterEncodedObjects(trees), Equals, treesCount) 516 c.Assert(lenIterEncodedObjects(blobs), Equals, blobCount) 517 c.Assert(lenIterEncodedObjects(commits), Equals, commitCount) 518 519 ref, err := r.Head() 520 c.Assert(err, IsNil) 521 c.Assert(ref.Hash(), Equals, head) 522} 523 524func lenIterEncodedObjects(iter storer.EncodedObjectIter) int { 525 count := 0 526 iter.ForEach(func(plumbing.EncodedObject) error { 527 count++ 528 return nil 529 }) 530 531 return count 532} 533 534func defaultSignature() *object.Signature { 535 when, _ := time.Parse(object.DateFormat, "Thu May 04 00:03:43 2017 +0200") 536 return &object.Signature{ 537 Name: "foo", 538 Email: "foo@foo.foo", 539 When: when, 540 } 541} 542 543func commitSignKey(c *C, decrypt bool) *openpgp.Entity { 544 s := strings.NewReader(armoredKeyRing) 545 es, err := openpgp.ReadArmoredKeyRing(s) 546 c.Assert(err, IsNil) 547 548 c.Assert(es, HasLen, 1) 549 c.Assert(es[0].Identities, HasLen, 1) 550 _, ok := es[0].Identities["foo bar <foo@foo.foo>"] 551 c.Assert(ok, Equals, true) 552 553 key := es[0] 554 if decrypt { 555 err = key.PrivateKey.Decrypt([]byte(keyPassphrase)) 556 c.Assert(err, IsNil) 557 } 558 559 return key 560} 561 562const armoredKeyRing = ` 563-----BEGIN PGP PRIVATE KEY BLOCK----- 564 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 617=gIM9 618-----END PGP PRIVATE KEY BLOCK----- 619` 620 621const keyPassphrase = "abcdef0123456789"