fork of go-git with some jj specific features
at main 52 kB view raw
1package git 2 3import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "os" 10 "path/filepath" 11 "runtime" 12 "strings" 13 "testing" 14 "time" 15 16 "github.com/go-git/go-billy/v5/memfs" 17 "github.com/go-git/go-billy/v5/osfs" 18 "github.com/go-git/go-billy/v5/util" 19 "github.com/stretchr/testify/suite" 20 21 "github.com/go-git/go-git/v5/config" 22 "github.com/go-git/go-git/v5/plumbing" 23 "github.com/go-git/go-git/v5/plumbing/cache" 24 "github.com/go-git/go-git/v5/plumbing/object" 25 "github.com/go-git/go-git/v5/plumbing/protocol/packp" 26 "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability" 27 "github.com/go-git/go-git/v5/plumbing/storer" 28 "github.com/go-git/go-git/v5/storage" 29 "github.com/go-git/go-git/v5/storage/filesystem" 30 "github.com/go-git/go-git/v5/storage/memory" 31 32 fixtures "github.com/go-git/go-git-fixtures/v4" 33) 34 35type RemoteSuite struct { 36 suite.Suite 37 BaseSuite 38} 39 40func TestRemoteSuite(t *testing.T) { 41 suite.Run(t, new(RemoteSuite)) 42} 43 44func (s *RemoteSuite) TestFetchInvalidEndpoint() { 45 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"http://\\"}}) 46 err := r.Fetch(&FetchOptions{RemoteName: "foo"}) 47 s.ErrorContains(err, "invalid character") 48} 49 50func (s *RemoteSuite) TestFetchNonExistentEndpoint() { 51 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"ssh://non-existent/foo.git"}}) 52 err := r.Fetch(&FetchOptions{}) 53 s.NotNil(err) 54} 55 56func (s *RemoteSuite) TestFetchInvalidSchemaEndpoint() { 57 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"qux://foo"}}) 58 err := r.Fetch(&FetchOptions{}) 59 s.ErrorContains(err, "unsupported scheme") 60} 61 62func (s *RemoteSuite) TestFetchOverriddenEndpoint() { 63 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"http://perfectly-valid-url.example.com"}}) 64 err := r.Fetch(&FetchOptions{RemoteURL: "http://\\"}) 65 s.ErrorContains(err, "invalid character") 66} 67 68func (s *RemoteSuite) TestFetchInvalidFetchOptions() { 69 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"qux://foo"}}) 70 invalid := config.RefSpec("^*$ñ") 71 err := r.Fetch(&FetchOptions{RefSpecs: []config.RefSpec{invalid}}) 72 s.ErrorIs(err, config.ErrRefSpecMalformedSeparator) 73} 74 75func (s *RemoteSuite) TestFetchWildcard() { 76 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 77 URLs: []string{s.GetBasicLocalRepositoryURL()}, 78 }) 79 80 s.testFetch(r, &FetchOptions{ 81 RefSpecs: []config.RefSpec{ 82 config.RefSpec("+refs/heads/*:refs/remotes/origin/*"), 83 }, 84 }, []*plumbing.Reference{ 85 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), 86 plumbing.NewReferenceFromStrings("refs/remotes/origin/branch", "e8d3ffab552895c19b9fcf7aa264d277cde33881"), 87 plumbing.NewReferenceFromStrings("refs/tags/v1.0.0", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), 88 }) 89} 90 91func (s *RemoteSuite) TestFetchExactSHA1() { 92 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 93 URLs: []string{"https://github.com/git-fixtures/basic.git"}, 94 }) 95 96 s.testFetch(r, &FetchOptions{ 97 RefSpecs: []config.RefSpec{ 98 config.RefSpec("35e85108805c84807bc66a02d91535e1e24b38b9:refs/heads/foo"), 99 }, 100 }, []*plumbing.Reference{ 101 plumbing.NewReferenceFromStrings("refs/heads/foo", "35e85108805c84807bc66a02d91535e1e24b38b9"), 102 }) 103} 104 105func (s *RemoteSuite) TestFetchExactSHA1_NotSoported() { 106 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 107 URLs: []string{s.GetBasicLocalRepositoryURL()}, 108 }) 109 110 err := r.Fetch(&FetchOptions{ 111 RefSpecs: []config.RefSpec{ 112 config.RefSpec("35e85108805c84807bc66a02d91535e1e24b38b9:refs/heads/foo"), 113 }, 114 }) 115 116 s.ErrorIs(err, ErrExactSHA1NotSupported) 117} 118 119func (s *RemoteSuite) TestFetchWildcardTags() { 120 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 121 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())}, 122 }) 123 124 s.testFetch(r, &FetchOptions{ 125 RefSpecs: []config.RefSpec{ 126 config.RefSpec("+refs/heads/*:refs/remotes/origin/*"), 127 }, 128 }, []*plumbing.Reference{ 129 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), 130 plumbing.NewReferenceFromStrings("refs/tags/annotated-tag", "b742a2a9fa0afcfa9a6fad080980fbc26b007c69"), 131 plumbing.NewReferenceFromStrings("refs/tags/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"), 132 plumbing.NewReferenceFromStrings("refs/tags/commit-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"), 133 plumbing.NewReferenceFromStrings("refs/tags/blob-tag", "fe6cb94756faa81e5ed9240f9191b833db5f40ae"), 134 plumbing.NewReferenceFromStrings("refs/tags/lightweight-tag", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), 135 }) 136} 137 138func (s *RemoteSuite) TestFetch() { 139 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 140 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())}, 141 }) 142 143 s.testFetch(r, &FetchOptions{ 144 RefSpecs: []config.RefSpec{ 145 config.RefSpec("+refs/heads/master:refs/remotes/origin/master"), 146 }, 147 }, []*plumbing.Reference{ 148 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), 149 }) 150} 151 152func (s *RemoteSuite) TestFetchToNewBranch() { 153 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 154 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())}, 155 }) 156 157 s.testFetch(r, &FetchOptions{ 158 RefSpecs: []config.RefSpec{ 159 // qualified branch to unqualified branch 160 "refs/heads/master:foo", 161 // unqualified branch to unqualified branch 162 "+master:bar", 163 // unqualified tag to unqualified branch 164 config.RefSpec("tree-tag:tree-tag"), 165 // unqualified tag to qualified tag 166 config.RefSpec("+commit-tag:refs/tags/renamed-tag"), 167 }, 168 }, []*plumbing.Reference{ 169 plumbing.NewReferenceFromStrings("refs/heads/foo", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), 170 plumbing.NewReferenceFromStrings("refs/heads/bar", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), 171 plumbing.NewReferenceFromStrings("refs/heads/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"), 172 plumbing.NewReferenceFromStrings("refs/tags/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"), 173 plumbing.NewReferenceFromStrings("refs/tags/renamed-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"), 174 plumbing.NewReferenceFromStrings("refs/tags/commit-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"), 175 }) 176} 177 178func (s *RemoteSuite) TestFetchToNewBranchWithAllTags() { 179 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 180 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())}, 181 }) 182 183 s.testFetch(r, &FetchOptions{ 184 Tags: AllTags, 185 RefSpecs: []config.RefSpec{ 186 // qualified branch to unqualified branch 187 "+refs/heads/master:foo", 188 // unqualified branch to unqualified branch 189 "master:bar", 190 // unqualified tag to unqualified branch 191 config.RefSpec("+tree-tag:tree-tag"), 192 // unqualified tag to qualified tag 193 config.RefSpec("commit-tag:refs/tags/renamed-tag"), 194 }, 195 }, []*plumbing.Reference{ 196 plumbing.NewReferenceFromStrings("refs/heads/foo", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), 197 plumbing.NewReferenceFromStrings("refs/heads/bar", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), 198 plumbing.NewReferenceFromStrings("refs/heads/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"), 199 plumbing.NewReferenceFromStrings("refs/tags/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"), 200 plumbing.NewReferenceFromStrings("refs/tags/renamed-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"), 201 plumbing.NewReferenceFromStrings("refs/tags/commit-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"), 202 plumbing.NewReferenceFromStrings("refs/tags/annotated-tag", "b742a2a9fa0afcfa9a6fad080980fbc26b007c69"), 203 plumbing.NewReferenceFromStrings("refs/tags/blob-tag", "fe6cb94756faa81e5ed9240f9191b833db5f40ae"), 204 plumbing.NewReferenceFromStrings("refs/tags/lightweight-tag", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), 205 }) 206} 207 208func (s *RemoteSuite) TestFetchNonExistentReference() { 209 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 210 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())}, 211 }) 212 213 err := r.Fetch(&FetchOptions{ 214 RefSpecs: []config.RefSpec{ 215 config.RefSpec("+refs/heads/foo:refs/remotes/origin/foo"), 216 }, 217 }) 218 219 s.ErrorContains(err, "couldn't find remote ref") 220 s.True(errors.Is(err, NoMatchingRefSpecError{})) 221} 222 223func (s *RemoteSuite) TestFetchContext() { 224 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 225 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())}, 226 }) 227 228 ctx, cancel := context.WithCancel(context.Background()) 229 defer cancel() 230 231 err := r.FetchContext(ctx, &FetchOptions{ 232 RefSpecs: []config.RefSpec{ 233 config.RefSpec("+refs/heads/master:refs/remotes/origin/master"), 234 }, 235 }) 236 s.NoError(err) 237} 238 239func (s *RemoteSuite) TestFetchContextCanceled() { 240 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 241 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())}, 242 }) 243 244 ctx, cancel := context.WithCancel(context.Background()) 245 cancel() 246 247 err := r.FetchContext(ctx, &FetchOptions{ 248 RefSpecs: []config.RefSpec{ 249 config.RefSpec("+refs/heads/master:refs/remotes/origin/master"), 250 }, 251 }) 252 s.ErrorIs(err, context.Canceled) 253} 254 255func (s *RemoteSuite) TestFetchWithAllTags() { 256 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 257 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())}, 258 }) 259 260 s.testFetch(r, &FetchOptions{ 261 Tags: AllTags, 262 RefSpecs: []config.RefSpec{ 263 config.RefSpec("+refs/heads/master:refs/remotes/origin/master"), 264 }, 265 }, []*plumbing.Reference{ 266 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), 267 plumbing.NewReferenceFromStrings("refs/tags/annotated-tag", "b742a2a9fa0afcfa9a6fad080980fbc26b007c69"), 268 plumbing.NewReferenceFromStrings("refs/tags/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"), 269 plumbing.NewReferenceFromStrings("refs/tags/commit-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"), 270 plumbing.NewReferenceFromStrings("refs/tags/blob-tag", "fe6cb94756faa81e5ed9240f9191b833db5f40ae"), 271 plumbing.NewReferenceFromStrings("refs/tags/lightweight-tag", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), 272 }) 273} 274 275func (s *RemoteSuite) TestFetchWithNoTags() { 276 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 277 URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())}, 278 }) 279 280 s.testFetch(r, &FetchOptions{ 281 Tags: NoTags, 282 RefSpecs: []config.RefSpec{ 283 config.RefSpec("+refs/heads/*:refs/remotes/origin/*"), 284 }, 285 }, []*plumbing.Reference{ 286 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"), 287 }) 288} 289 290func (s *RemoteSuite) TestFetchWithDepth() { 291 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 292 URLs: []string{s.GetBasicLocalRepositoryURL()}, 293 }) 294 295 s.testFetch(r, &FetchOptions{ 296 Depth: 1, 297 RefSpecs: []config.RefSpec{ 298 config.RefSpec("+refs/heads/*:refs/remotes/origin/*"), 299 }, 300 }, []*plumbing.Reference{ 301 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), 302 plumbing.NewReferenceFromStrings("refs/remotes/origin/branch", "e8d3ffab552895c19b9fcf7aa264d277cde33881"), 303 plumbing.NewReferenceFromStrings("refs/tags/v1.0.0", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), 304 }) 305 306 s.Len(r.s.(*memory.Storage).Objects, 18) 307} 308 309func (s *RemoteSuite) TestFetchWithDepthChange() { 310 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 311 URLs: []string{s.GetBasicLocalRepositoryURL()}, 312 }) 313 314 s.testFetch(r, &FetchOptions{ 315 Depth: 1, 316 RefSpecs: []config.RefSpec{ 317 config.RefSpec("refs/heads/master:refs/heads/master"), 318 }, 319 }, []*plumbing.Reference{ 320 plumbing.NewReferenceFromStrings("refs/heads/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), 321 }) 322 s.Len(r.s.(*memory.Storage).Commits, 1) 323 324 s.testFetch(r, &FetchOptions{ 325 Depth: 3, 326 RefSpecs: []config.RefSpec{ 327 config.RefSpec("refs/heads/master:refs/heads/master"), 328 }, 329 }, []*plumbing.Reference{ 330 plumbing.NewReferenceFromStrings("refs/heads/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), 331 }) 332 s.Len(r.s.(*memory.Storage).Commits, 3) 333} 334 335func (s *RemoteSuite) testFetch(r *Remote, o *FetchOptions, expected []*plumbing.Reference) { 336 err := r.Fetch(o) 337 s.NoError(err) 338 339 var refs int 340 l, err := r.s.IterReferences() 341 s.NoError(err) 342 l.ForEach(func(r *plumbing.Reference) error { refs++; return nil }) 343 344 s.Len(expected, refs) 345 346 for _, exp := range expected { 347 r, err := r.s.Reference(exp.Name()) 348 s.NoError(err) 349 s.Equal(r.String(), exp.String()) 350 } 351} 352 353func (s *RemoteSuite) TestFetchOfMissingObjects() { 354 tmp, err := os.MkdirTemp("", "") 355 s.NoError(err) 356 357 // clone to a local temp folder 358 _, err = PlainClone(tmp, true, &CloneOptions{ 359 URL: fixtures.Basic().One().DotGit().Root(), 360 }) 361 s.NoError(err) 362 363 // Delete the pack files 364 fsTmp := osfs.New(tmp) 365 err = util.RemoveAll(fsTmp, "objects/pack") 366 s.NoError(err) 367 368 // Reopen the repo from the filesystem (with missing objects) 369 r, err := Open(filesystem.NewStorage(fsTmp, cache.NewObjectLRUDefault()), nil) 370 s.NoError(err) 371 372 // Confirm we are missing a commit 373 _, err = r.CommitObject(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) 374 s.ErrorIs(err, plumbing.ErrObjectNotFound) 375 376 // Refetch to get all the missing objects 377 err = r.Fetch(&FetchOptions{}) 378 s.NoError(err) 379 380 // Confirm we now have the commit 381 _, err = r.CommitObject(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) 382 s.NoError(err) 383} 384 385func (s *RemoteSuite) TestFetchWithProgress() { 386 url := s.GetBasicLocalRepositoryURL() 387 sto := memory.NewStorage() 388 buf := bytes.NewBuffer(nil) 389 390 r := NewRemote(sto, &config.RemoteConfig{Name: "foo", URLs: []string{url}}) 391 392 refspec := config.RefSpec("+refs/heads/*:refs/remotes/origin/*") 393 err := r.Fetch(&FetchOptions{ 394 RefSpecs: []config.RefSpec{refspec}, 395 Progress: buf, 396 }) 397 398 s.NoError(err) 399 s.Len(sto.Objects, 31) 400 401 s.NotEqual(0, buf.Len()) 402} 403 404type mockPackfileWriter struct { 405 storage.Storer 406 PackfileWriterCalled bool 407} 408 409func (m *mockPackfileWriter) PackfileWriter() (io.WriteCloser, error) { 410 m.PackfileWriterCalled = true 411 return m.Storer.(storer.PackfileWriter).PackfileWriter() 412} 413 414func (s *RemoteSuite) TestFetchWithPackfileWriter() { 415 fs := s.TemporalFilesystem() 416 417 fss := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 418 mock := &mockPackfileWriter{Storer: fss} 419 420 url := s.GetBasicLocalRepositoryURL() 421 r := NewRemote(mock, &config.RemoteConfig{Name: "foo", URLs: []string{url}}) 422 423 refspec := config.RefSpec("+refs/heads/*:refs/remotes/origin/*") 424 err := r.Fetch(&FetchOptions{ 425 RefSpecs: []config.RefSpec{refspec}, 426 }) 427 428 s.NoError(err) 429 430 var count int 431 iter, err := mock.IterEncodedObjects(plumbing.AnyObject) 432 s.NoError(err) 433 434 iter.ForEach(func(plumbing.EncodedObject) error { 435 count++ 436 return nil 437 }) 438 439 s.Equal(31, count) 440 s.True(mock.PackfileWriterCalled) 441} 442 443func (s *RemoteSuite) TestFetchNoErrAlreadyUpToDate() { 444 url := s.GetBasicLocalRepositoryURL() 445 s.doTestFetchNoErrAlreadyUpToDate(url) 446} 447 448func (s *RemoteSuite) TestFetchNoErrAlreadyUpToDateButStillUpdateLocalRemoteRefs() { 449 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 450 URLs: []string{s.GetBasicLocalRepositoryURL()}, 451 }) 452 453 o := &FetchOptions{ 454 RefSpecs: []config.RefSpec{ 455 config.RefSpec("+refs/heads/*:refs/remotes/origin/*"), 456 }, 457 } 458 459 err := r.Fetch(o) 460 s.NoError(err) 461 462 // Simulate an out of date remote ref even though we have the new commit locally 463 r.s.SetReference(plumbing.NewReferenceFromStrings( 464 "refs/remotes/origin/master", "918c48b83bd081e863dbe1b80f8998f058cd8294", 465 )) 466 467 err = r.Fetch(o) 468 s.NoError(err) 469 470 exp := plumbing.NewReferenceFromStrings( 471 "refs/remotes/origin/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", 472 ) 473 474 ref, err := r.s.Reference("refs/remotes/origin/master") 475 s.NoError(err) 476 s.Equal(ref.String(), exp.String()) 477} 478 479func (s *RemoteSuite) TestFetchNoErrAlreadyUpToDateWithNonCommitObjects() { 480 fixture := fixtures.ByTag("tags").One() 481 url := s.GetLocalRepositoryURL(fixture) 482 s.doTestFetchNoErrAlreadyUpToDate(url) 483} 484 485func (s *RemoteSuite) doTestFetchNoErrAlreadyUpToDate(url string) { 486 r := NewRemote(memory.NewStorage(), &config.RemoteConfig{URLs: []string{url}}) 487 488 o := &FetchOptions{ 489 RefSpecs: []config.RefSpec{ 490 config.RefSpec("+refs/heads/*:refs/remotes/origin/*"), 491 }, 492 } 493 494 err := r.Fetch(o) 495 s.NoError(err) 496 err = r.Fetch(o) 497 s.ErrorIs(err, NoErrAlreadyUpToDate) 498} 499 500func (s *RemoteSuite) testFetchFastForward(sto storage.Storer) { 501 r := NewRemote(sto, &config.RemoteConfig{ 502 URLs: []string{s.GetBasicLocalRepositoryURL()}, 503 }) 504 505 s.testFetch(r, &FetchOptions{ 506 RefSpecs: []config.RefSpec{ 507 config.RefSpec("+refs/heads/master:refs/heads/master"), 508 }, 509 }, []*plumbing.Reference{ 510 plumbing.NewReferenceFromStrings("refs/heads/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), 511 }) 512 513 // First make sure that we error correctly when a force is required. 514 err := r.Fetch(&FetchOptions{ 515 RefSpecs: []config.RefSpec{ 516 config.RefSpec("refs/heads/branch:refs/heads/master"), 517 }, 518 }) 519 s.ErrorIs(err, ErrForceNeeded) 520 521 // And that forcing it fixes the problem. 522 err = r.Fetch(&FetchOptions{ 523 RefSpecs: []config.RefSpec{ 524 config.RefSpec("+refs/heads/branch:refs/heads/master"), 525 }, 526 }) 527 s.NoError(err) 528 529 // Now test that a fast-forward, non-force fetch works. 530 r.s.SetReference(plumbing.NewReferenceFromStrings( 531 "refs/heads/master", "918c48b83bd081e863dbe1b80f8998f058cd8294", 532 )) 533 s.testFetch(r, &FetchOptions{ 534 RefSpecs: []config.RefSpec{ 535 config.RefSpec("refs/heads/master:refs/heads/master"), 536 }, 537 }, []*plumbing.Reference{ 538 plumbing.NewReferenceFromStrings("refs/heads/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), 539 }) 540} 541 542func (s *RemoteSuite) TestFetchFastForwardMem() { 543 s.testFetchFastForward(memory.NewStorage()) 544} 545 546func (s *RemoteSuite) TestFetchFastForwardFS() { 547 fs := s.TemporalFilesystem() 548 549 fss := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 550 551 // This exercises `storage.filesystem.Storage.CheckAndSetReference()`. 552 s.testFetchFastForward(fss) 553} 554 555func (s *RemoteSuite) TestString() { 556 r := NewRemote(nil, &config.RemoteConfig{ 557 Name: "foo", 558 URLs: []string{"https://github.com/git-fixtures/basic.git"}, 559 }) 560 561 s.Equal(""+ 562 "foo\thttps://github.com/git-fixtures/basic.git (fetch)\n"+ 563 "foo\thttps://github.com/git-fixtures/basic.git (push)", 564 r.String(), 565 ) 566} 567 568func (s *RemoteSuite) TestPushToEmptyRepository() { 569 url, err := os.MkdirTemp("", "") 570 s.NoError(err) 571 572 server, err := PlainInit(url, true) 573 s.NoError(err) 574 575 srcFs := fixtures.Basic().One().DotGit() 576 sto := filesystem.NewStorage(srcFs, cache.NewObjectLRUDefault()) 577 578 r := NewRemote(sto, &config.RemoteConfig{ 579 Name: DefaultRemoteName, 580 URLs: []string{url}, 581 }) 582 583 rs := config.RefSpec("refs/heads/*:refs/heads/*") 584 err = r.Push(&PushOptions{ 585 RefSpecs: []config.RefSpec{rs}, 586 }) 587 s.NoError(err) 588 589 iter, err := r.s.IterReferences() 590 s.NoError(err) 591 592 expected := make(map[string]string) 593 iter.ForEach(func(ref *plumbing.Reference) error { 594 if !ref.Name().IsBranch() { 595 return nil 596 } 597 598 expected[ref.Name().String()] = ref.Hash().String() 599 return nil 600 }) 601 s.NoError(err) 602 603 AssertReferences(s.T(), server, expected) 604} 605 606func (s *RemoteSuite) TestPushContext() { 607 url, err := os.MkdirTemp("", "") 608 s.NoError(err) 609 610 _, err = PlainInit(url, true) 611 s.NoError(err) 612 613 fs := fixtures.ByURL("https://github.com/git-fixtures/tags.git").One().DotGit() 614 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 615 616 r := NewRemote(sto, &config.RemoteConfig{ 617 Name: DefaultRemoteName, 618 URLs: []string{url}, 619 }) 620 621 ctx, cancel := context.WithCancel(context.Background()) 622 defer cancel() 623 624 numGoroutines := runtime.NumGoroutine() 625 626 err = r.PushContext(ctx, &PushOptions{ 627 RefSpecs: []config.RefSpec{"refs/tags/*:refs/tags/*"}, 628 }) 629 s.NoError(err) 630 631 eventually(s, func() bool { 632 return runtime.NumGoroutine() <= numGoroutines 633 }) 634} 635 636func eventually(s *RemoteSuite, condition func() bool) { 637 select { 638 case <-time.After(5 * time.Second): 639 default: 640 if condition() { 641 break 642 } 643 time.Sleep(100 * time.Millisecond) 644 } 645 646 s.True(condition()) 647} 648 649func (s *RemoteSuite) TestPushContextCanceled() { 650 url, err := os.MkdirTemp("", "") 651 s.NoError(err) 652 653 _, err = PlainInit(url, true) 654 s.NoError(err) 655 656 fs := fixtures.ByURL("https://github.com/git-fixtures/tags.git").One().DotGit() 657 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 658 659 r := NewRemote(sto, &config.RemoteConfig{ 660 Name: DefaultRemoteName, 661 URLs: []string{url}, 662 }) 663 664 ctx, cancel := context.WithCancel(context.Background()) 665 cancel() 666 667 numGoroutines := runtime.NumGoroutine() 668 669 err = r.PushContext(ctx, &PushOptions{ 670 RefSpecs: []config.RefSpec{"refs/tags/*:refs/tags/*"}, 671 }) 672 s.ErrorIs(err, context.Canceled) 673 674 eventually(s, func() bool { 675 return runtime.NumGoroutine() <= numGoroutines 676 }) 677} 678 679func (s *RemoteSuite) TestPushTags() { 680 url, err := os.MkdirTemp("", "") 681 s.NoError(err) 682 683 server, err := PlainInit(url, true) 684 s.NoError(err) 685 686 fs := fixtures.ByURL("https://github.com/git-fixtures/tags.git").One().DotGit() 687 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 688 689 r := NewRemote(sto, &config.RemoteConfig{ 690 Name: DefaultRemoteName, 691 URLs: []string{url}, 692 }) 693 694 err = r.Push(&PushOptions{ 695 RefSpecs: []config.RefSpec{"refs/tags/*:refs/tags/*"}, 696 }) 697 s.NoError(err) 698 699 AssertReferences(s.T(), server, map[string]string{ 700 "refs/tags/lightweight-tag": "f7b877701fbf855b44c0a9e86f3fdce2c298b07f", 701 "refs/tags/annotated-tag": "b742a2a9fa0afcfa9a6fad080980fbc26b007c69", 702 "refs/tags/commit-tag": "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc", 703 "refs/tags/blob-tag": "fe6cb94756faa81e5ed9240f9191b833db5f40ae", 704 "refs/tags/tree-tag": "152175bf7e5580299fa1f0ba41ef6474cc043b70", 705 }) 706} 707 708func (s *RemoteSuite) TestPushFollowTags() { 709 url, err := os.MkdirTemp("", "") 710 s.NoError(err) 711 712 server, err := PlainInit(url, true) 713 s.NoError(err) 714 715 fs := fixtures.ByURL("https://github.com/git-fixtures/basic.git").One().DotGit() 716 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 717 718 r := NewRemote(sto, &config.RemoteConfig{ 719 Name: DefaultRemoteName, 720 URLs: []string{url}, 721 }) 722 723 localRepo := newRepository(sto, fs) 724 tipTag, err := localRepo.CreateTag( 725 "tip", 726 plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"), 727 &CreateTagOptions{ 728 Message: "an annotated tag", 729 }, 730 ) 731 s.NoError(err) 732 733 initialTag, err := localRepo.CreateTag( 734 "initial-commit", 735 plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"), 736 &CreateTagOptions{ 737 Message: "a tag for the initial commit", 738 }, 739 ) 740 s.NoError(err) 741 742 _, err = localRepo.CreateTag( 743 "master-tag", 744 plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), 745 &CreateTagOptions{ 746 Message: "a tag with a commit not reachable from branch", 747 }, 748 ) 749 s.NoError(err) 750 751 err = r.Push(&PushOptions{ 752 RefSpecs: []config.RefSpec{"+refs/heads/branch:refs/heads/branch"}, 753 FollowTags: true, 754 }) 755 s.NoError(err) 756 757 AssertReferences(s.T(), server, map[string]string{ 758 "refs/heads/branch": "e8d3ffab552895c19b9fcf7aa264d277cde33881", 759 "refs/tags/tip": tipTag.Hash().String(), 760 "refs/tags/initial-commit": initialTag.Hash().String(), 761 }) 762 763 AssertReferencesMissing(s.T(), server, []string{ 764 "refs/tags/master-tag", 765 }) 766} 767 768func (s *RemoteSuite) TestPushNoErrAlreadyUpToDate() { 769 fs := fixtures.Basic().One().DotGit() 770 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 771 772 r := NewRemote(sto, &config.RemoteConfig{ 773 Name: DefaultRemoteName, 774 URLs: []string{fs.Root()}, 775 }) 776 777 err := r.Push(&PushOptions{ 778 RefSpecs: []config.RefSpec{"refs/heads/*:refs/heads/*"}, 779 }) 780 s.ErrorIs(err, NoErrAlreadyUpToDate) 781} 782 783func (s *RemoteSuite) TestPushDeleteReference() { 784 fs := fixtures.Basic().One().DotGit() 785 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 786 787 url, err := os.MkdirTemp("", "") 788 s.NoError(err) 789 790 r, err := PlainClone(url, true, &CloneOptions{ 791 URL: fs.Root(), 792 }) 793 s.NoError(err) 794 795 remote, err := r.Remote(DefaultRemoteName) 796 s.NoError(err) 797 798 err = remote.Push(&PushOptions{ 799 RefSpecs: []config.RefSpec{":refs/heads/branch"}, 800 }) 801 s.NoError(err) 802 803 _, err = sto.Reference(plumbing.ReferenceName("refs/heads/branch")) 804 s.ErrorIs(err, plumbing.ErrReferenceNotFound) 805 806 _, err = r.Storer.Reference(plumbing.ReferenceName("refs/heads/branch")) 807 s.ErrorIs(err, plumbing.ErrReferenceNotFound) 808} 809 810func (s *RemoteSuite) TestForcePushDeleteReference() { 811 fs := fixtures.Basic().One().DotGit() 812 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 813 814 url, err := os.MkdirTemp("", "") 815 s.NoError(err) 816 817 r, err := PlainClone(url, true, &CloneOptions{ 818 URL: fs.Root(), 819 }) 820 s.NoError(err) 821 822 remote, err := r.Remote(DefaultRemoteName) 823 s.NoError(err) 824 825 err = remote.Push(&PushOptions{ 826 RefSpecs: []config.RefSpec{":refs/heads/branch"}, 827 Force: true, 828 }) 829 s.NoError(err) 830 831 _, err = sto.Reference(plumbing.ReferenceName("refs/heads/branch")) 832 s.ErrorIs(err, plumbing.ErrReferenceNotFound) 833 834 _, err = r.Storer.Reference(plumbing.ReferenceName("refs/heads/branch")) 835 s.ErrorIs(err, plumbing.ErrReferenceNotFound) 836} 837 838func (s *RemoteSuite) TestPushRejectNonFastForward() { 839 fs := fixtures.Basic().One().DotGit() 840 server := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 841 842 url, err := os.MkdirTemp("", "") 843 s.NoError(err) 844 845 r, err := PlainClone(url, true, &CloneOptions{ 846 URL: fs.Root(), 847 }) 848 s.NoError(err) 849 850 remote, err := r.Remote(DefaultRemoteName) 851 s.NoError(err) 852 853 branch := plumbing.ReferenceName("refs/heads/branch") 854 oldRef, err := server.Reference(branch) 855 s.NoError(err) 856 s.NotNil(oldRef) 857 858 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{ 859 "refs/heads/master:refs/heads/branch", 860 }}) 861 s.ErrorContains(err, "non-fast-forward update: refs/heads/branch") 862 863 newRef, err := server.Reference(branch) 864 s.NoError(err) 865 s.Equal(oldRef, newRef) 866} 867 868func (s *RemoteSuite) TestPushForce() { 869 f := fixtures.Basic().One() 870 sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) 871 872 dstFs := f.DotGit() 873 dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault()) 874 875 url := dstFs.Root() 876 r := NewRemote(sto, &config.RemoteConfig{ 877 Name: DefaultRemoteName, 878 URLs: []string{url}, 879 }) 880 881 oldRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) 882 s.NoError(err) 883 s.NotNil(oldRef) 884 885 err = r.Push(&PushOptions{RefSpecs: []config.RefSpec{ 886 config.RefSpec("+refs/heads/master:refs/heads/branch"), 887 }}) 888 s.NoError(err) 889 890 newRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) 891 s.NoError(err) 892 s.NotEqual(oldRef, newRef) 893} 894 895func (s *RemoteSuite) TestPushForceWithOption() { 896 f := fixtures.Basic().One() 897 sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) 898 899 dstFs := f.DotGit() 900 dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault()) 901 902 url := dstFs.Root() 903 r := NewRemote(sto, &config.RemoteConfig{ 904 Name: DefaultRemoteName, 905 URLs: []string{url}, 906 }) 907 908 oldRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) 909 s.NoError(err) 910 s.NotNil(oldRef) 911 912 err = r.Push(&PushOptions{ 913 RefSpecs: []config.RefSpec{"refs/heads/master:refs/heads/branch"}, 914 Force: true, 915 }) 916 s.NoError(err) 917 918 newRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) 919 s.NoError(err) 920 s.NotEqual(oldRef, newRef) 921} 922 923func (s *RemoteSuite) TestPushForceWithLease_success() { 924 testCases := []struct { 925 desc string 926 forceWithLease ForceWithLease 927 }{ 928 { 929 desc: "no arguments", 930 forceWithLease: ForceWithLease{}, 931 }, 932 { 933 desc: "ref name", 934 forceWithLease: ForceWithLease{ 935 RefName: plumbing.ReferenceName("refs/heads/branch"), 936 }, 937 }, 938 { 939 desc: "ref name and sha", 940 forceWithLease: ForceWithLease{ 941 RefName: plumbing.ReferenceName("refs/heads/branch"), 942 Hash: plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"), 943 }, 944 }, 945 } 946 947 for _, tc := range testCases { 948 s.T().Log("Executing test cases:", tc.desc) 949 950 f := fixtures.Basic().One() 951 sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) 952 dstFs := f.DotGit() 953 dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault()) 954 955 newCommit := plumbing.NewHashReference( 956 "refs/heads/branch", plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"), 957 ) 958 s.Nil(sto.SetReference(newCommit)) 959 960 ref, err := sto.Reference("refs/heads/branch") 961 s.NoError(err) 962 s.T().Log(ref.String()) 963 964 url := dstFs.Root() 965 r := NewRemote(sto, &config.RemoteConfig{ 966 Name: DefaultRemoteName, 967 URLs: []string{url}, 968 }) 969 970 oldRef, err := dstSto.Reference("refs/heads/branch") 971 s.NoError(err) 972 s.NotNil(oldRef) 973 974 s.NoError(r.Push(&PushOptions{ 975 RefSpecs: []config.RefSpec{"refs/heads/branch:refs/heads/branch"}, 976 ForceWithLease: &ForceWithLease{}, 977 })) 978 979 newRef, err := dstSto.Reference("refs/heads/branch") 980 s.NoError(err) 981 s.Equal(newCommit, newRef) 982 } 983} 984 985func (s *RemoteSuite) TestPushForceWithLease_failure() { 986 testCases := []struct { 987 desc string 988 forceWithLease ForceWithLease 989 }{ 990 { 991 desc: "no arguments", 992 forceWithLease: ForceWithLease{}, 993 }, 994 { 995 desc: "ref name", 996 forceWithLease: ForceWithLease{ 997 RefName: plumbing.ReferenceName("refs/heads/branch"), 998 }, 999 }, 1000 { 1001 desc: "ref name and sha", 1002 forceWithLease: ForceWithLease{ 1003 RefName: plumbing.ReferenceName("refs/heads/branch"), 1004 Hash: plumbing.NewHash("152175bf7e5580299fa1f0ba41ef6474cc043b70"), 1005 }, 1006 }, 1007 } 1008 1009 for _, tc := range testCases { 1010 s.T().Log("Executing test cases:", tc.desc) 1011 1012 f := fixtures.Basic().One() 1013 sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) 1014 s.NoError(sto.SetReference( 1015 plumbing.NewHashReference( 1016 "refs/heads/branch", plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"), 1017 ), 1018 )) 1019 1020 dstFs := f.DotGit() 1021 dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault()) 1022 s.NoError(dstSto.SetReference( 1023 plumbing.NewHashReference( 1024 "refs/heads/branch", plumbing.NewHash("ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"), 1025 ), 1026 )) 1027 1028 url := dstFs.Root() 1029 r := NewRemote(sto, &config.RemoteConfig{ 1030 Name: DefaultRemoteName, 1031 URLs: []string{url}, 1032 }) 1033 1034 oldRef, err := dstSto.Reference("refs/heads/branch") 1035 s.NoError(err) 1036 s.NotNil(oldRef) 1037 1038 err = r.Push(&PushOptions{ 1039 RefSpecs: []config.RefSpec{"refs/heads/branch:refs/heads/branch"}, 1040 ForceWithLease: &ForceWithLease{}, 1041 }) 1042 1043 s.ErrorContains(err, "non-fast-forward update: refs/heads/branch") 1044 1045 newRef, err := dstSto.Reference("refs/heads/branch") 1046 s.NoError(err) 1047 s.NotEqual(plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"), newRef) 1048 } 1049} 1050 1051func (s *RemoteSuite) TestPushPrune() { 1052 fs := fixtures.Basic().One().DotGit() 1053 1054 url, err := os.MkdirTemp("", "") 1055 s.NoError(err) 1056 1057 server, err := PlainClone(url, true, &CloneOptions{ 1058 URL: fs.Root(), 1059 }) 1060 s.NoError(err) 1061 1062 dir, err := os.MkdirTemp("", "") 1063 s.NoError(err) 1064 1065 r, err := PlainClone(dir, true, &CloneOptions{ 1066 URL: url, 1067 }) 1068 s.NoError(err) 1069 1070 tag, err := r.Reference(plumbing.ReferenceName("refs/tags/v1.0.0"), true) 1071 s.NoError(err) 1072 1073 err = r.DeleteTag("v1.0.0") 1074 s.NoError(err) 1075 1076 remote, err := r.Remote(DefaultRemoteName) 1077 s.NoError(err) 1078 1079 ref, err := r.Reference(plumbing.ReferenceName("refs/heads/master"), true) 1080 s.NoError(err) 1081 1082 err = remote.Push(&PushOptions{ 1083 RefSpecs: []config.RefSpec{ 1084 config.RefSpec("refs/heads/*:refs/heads/*"), 1085 }, 1086 Prune: true, 1087 }) 1088 s.ErrorIs(err, NoErrAlreadyUpToDate) 1089 1090 AssertReferences(s.T(), server, map[string]string{ 1091 "refs/tags/v1.0.0": tag.Hash().String(), 1092 }) 1093 1094 err = remote.Push(&PushOptions{ 1095 RefSpecs: []config.RefSpec{ 1096 config.RefSpec("*:*"), 1097 }, 1098 Prune: true, 1099 }) 1100 s.NoError(err) 1101 1102 AssertReferences(s.T(), server, map[string]string{ 1103 "refs/remotes/origin/master": ref.Hash().String(), 1104 }) 1105 1106 AssertReferences(s.T(), server, map[string]string{ 1107 "refs/remotes/origin/master": ref.Hash().String(), 1108 }) 1109 1110 _, err = server.Reference(plumbing.ReferenceName("refs/tags/v1.0.0"), true) 1111 s.ErrorIs(err, plumbing.ErrReferenceNotFound) 1112} 1113 1114func (s *RemoteSuite) TestPushNewReference() { 1115 fs := fixtures.Basic().One().DotGit() 1116 1117 url, err := os.MkdirTemp("", "") 1118 s.NoError(err) 1119 1120 server, err := PlainClone(url, true, &CloneOptions{ 1121 URL: fs.Root(), 1122 }) 1123 s.NoError(err) 1124 1125 dir, err := os.MkdirTemp("", "") 1126 s.NoError(err) 1127 1128 r, err := PlainClone(dir, true, &CloneOptions{ 1129 URL: url, 1130 }) 1131 s.NoError(err) 1132 1133 remote, err := r.Remote(DefaultRemoteName) 1134 s.NoError(err) 1135 1136 ref, err := r.Reference(plumbing.ReferenceName("refs/heads/master"), true) 1137 s.NoError(err) 1138 1139 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{ 1140 "refs/heads/master:refs/heads/branch2", 1141 }}) 1142 s.NoError(err) 1143 1144 AssertReferences(s.T(), server, map[string]string{ 1145 "refs/heads/branch2": ref.Hash().String(), 1146 }) 1147 1148 AssertReferences(s.T(), r, map[string]string{ 1149 "refs/remotes/origin/branch2": ref.Hash().String(), 1150 }) 1151} 1152 1153func (s *RemoteSuite) TestPushNewReferenceAndDeleteInBatch() { 1154 fs := fixtures.Basic().One().DotGit() 1155 1156 url, err := os.MkdirTemp("", "") 1157 s.NoError(err) 1158 1159 server, err := PlainClone(url, true, &CloneOptions{ 1160 URL: fs.Root(), 1161 }) 1162 s.NoError(err) 1163 1164 dir, err := os.MkdirTemp("", "") 1165 s.NoError(err) 1166 1167 r, err := PlainClone(dir, true, &CloneOptions{ 1168 URL: url, 1169 }) 1170 s.NoError(err) 1171 1172 remote, err := r.Remote(DefaultRemoteName) 1173 s.NoError(err) 1174 1175 ref, err := r.Reference(plumbing.ReferenceName("refs/heads/master"), true) 1176 s.NoError(err) 1177 1178 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{ 1179 "refs/heads/master:refs/heads/branch2", 1180 ":refs/heads/branch", 1181 }}) 1182 s.NoError(err) 1183 1184 AssertReferences(s.T(), server, map[string]string{ 1185 "refs/heads/branch2": ref.Hash().String(), 1186 }) 1187 1188 AssertReferences(s.T(), r, map[string]string{ 1189 "refs/remotes/origin/branch2": ref.Hash().String(), 1190 }) 1191 1192 _, err = server.Storer.Reference(plumbing.ReferenceName("refs/heads/branch")) 1193 s.ErrorIs(err, plumbing.ErrReferenceNotFound) 1194} 1195 1196func (s *RemoteSuite) TestPushInvalidEndpoint() { 1197 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"http://\\"}}) 1198 err := r.Push(&PushOptions{RemoteName: "foo"}) 1199 s.ErrorContains(err, "invalid character") 1200} 1201 1202func (s *RemoteSuite) TestPushNonExistentEndpoint() { 1203 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"ssh://non-existent/foo.git"}}) 1204 err := r.Push(&PushOptions{}) 1205 s.NotNil(err) 1206} 1207 1208func (s *RemoteSuite) TestPushOverriddenEndpoint() { 1209 r := NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{"http://perfectly-valid-url.example.com"}}) 1210 err := r.Push(&PushOptions{RemoteURL: "http://\\"}) 1211 s.ErrorContains(err, "invalid character") 1212} 1213 1214func (s *RemoteSuite) TestPushInvalidSchemaEndpoint() { 1215 r := NewRemote(nil, &config.RemoteConfig{Name: "origin", URLs: []string{"qux://foo"}}) 1216 err := r.Push(&PushOptions{}) 1217 s.ErrorContains(err, "unsupported scheme") 1218} 1219 1220func (s *RemoteSuite) TestPushInvalidFetchOptions() { 1221 r := NewRemote(nil, &config.RemoteConfig{Name: "foo", URLs: []string{"qux://foo"}}) 1222 invalid := config.RefSpec("^*$ñ") 1223 err := r.Push(&PushOptions{RefSpecs: []config.RefSpec{invalid}}) 1224 s.ErrorIs(err, config.ErrRefSpecMalformedSeparator) 1225} 1226 1227func (s *RemoteSuite) TestPushInvalidRefSpec() { 1228 r := NewRemote(nil, &config.RemoteConfig{ 1229 Name: DefaultRemoteName, 1230 URLs: []string{"some-url"}, 1231 }) 1232 1233 rs := config.RefSpec("^*$**") 1234 err := r.Push(&PushOptions{ 1235 RefSpecs: []config.RefSpec{rs}, 1236 }) 1237 s.ErrorIs(err, config.ErrRefSpecMalformedSeparator) 1238} 1239 1240func (s *RemoteSuite) TestPushWrongRemoteName() { 1241 r := NewRemote(nil, &config.RemoteConfig{ 1242 Name: DefaultRemoteName, 1243 URLs: []string{"some-url"}, 1244 }) 1245 1246 err := r.Push(&PushOptions{ 1247 RemoteName: "other-remote", 1248 }) 1249 s.ErrorContains(err, "remote names don't match") 1250} 1251 1252func (s *RemoteSuite) TestGetHaves() { 1253 f := fixtures.Basic().One() 1254 sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) 1255 1256 localRefs := []*plumbing.Reference{ 1257 // Exists 1258 plumbing.NewReferenceFromStrings( 1259 "foo", 1260 "b029517f6300c2da0f4b651b8642506cd6aaf45d", 1261 ), 1262 // Exists 1263 plumbing.NewReferenceFromStrings( 1264 "bar", 1265 "b8e471f58bcbca63b07bda20e428190409c2db47", 1266 ), 1267 // Doesn't Exist 1268 plumbing.NewReferenceFromStrings( 1269 "qux", 1270 "0000000", 1271 ), 1272 } 1273 1274 l, err := getHaves(localRefs, memory.NewStorage(), sto, 0) 1275 s.NoError(err) 1276 s.Len(l, 2) 1277} 1278 1279func (s *RemoteSuite) TestList() { 1280 repo := fixtures.Basic().One() 1281 remote := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 1282 Name: DefaultRemoteName, 1283 URLs: []string{repo.URL}, 1284 }) 1285 1286 refs, err := remote.List(&ListOptions{}) 1287 s.NoError(err) 1288 1289 expected := []*plumbing.Reference{ 1290 plumbing.NewSymbolicReference("HEAD", "refs/heads/master"), 1291 plumbing.NewReferenceFromStrings("refs/heads/master", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5"), 1292 plumbing.NewReferenceFromStrings("refs/heads/branch", "e8d3ffab552895c19b9fcf7aa264d277cde33881"), 1293 plumbing.NewReferenceFromStrings("refs/pull/1/head", "b8e471f58bcbca63b07bda20e428190409c2db47"), 1294 plumbing.NewReferenceFromStrings("refs/pull/2/head", "9632f02833b2f9613afb5e75682132b0b22e4a31"), 1295 plumbing.NewReferenceFromStrings("refs/pull/2/merge", "c37f58a130ca555e42ff96a071cb9ccb3f437504"), 1296 } 1297 s.Len(expected, len(refs)) 1298 for _, e := range expected { 1299 found := false 1300 for _, r := range refs { 1301 if r.Name() == e.Name() { 1302 found = true 1303 s.Equal(e, r) 1304 } 1305 } 1306 s.True(found) 1307 } 1308} 1309 1310func (s *RemoteSuite) TestListPeeling() { 1311 remote := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 1312 Name: DefaultRemoteName, 1313 URLs: []string{"https://github.com/git-fixtures/tags.git"}, 1314 }) 1315 1316 for _, tc := range []struct { 1317 peelingOption PeelingOption 1318 expectPeeled bool 1319 expectNonPeeled bool 1320 }{ 1321 {peelingOption: AppendPeeled, expectPeeled: true, expectNonPeeled: true}, 1322 {peelingOption: IgnorePeeled, expectPeeled: false, expectNonPeeled: true}, 1323 {peelingOption: OnlyPeeled, expectPeeled: true, expectNonPeeled: false}, 1324 } { 1325 refs, err := remote.List(&ListOptions{ 1326 PeelingOption: tc.peelingOption, 1327 }) 1328 s.NoError(err) 1329 s.True(len(refs) > 0) 1330 1331 foundPeeled, foundNonPeeled := false, false 1332 for _, ref := range refs { 1333 if strings.HasSuffix(ref.Name().String(), peeledSuffix) { 1334 foundPeeled = true 1335 } else { 1336 foundNonPeeled = true 1337 } 1338 } 1339 1340 s.Equal(tc.expectPeeled, foundPeeled) 1341 s.Equal(tc.expectNonPeeled, foundNonPeeled) 1342 } 1343} 1344 1345func (s *RemoteSuite) TestListTimeout() { 1346 remote := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 1347 Name: DefaultRemoteName, 1348 URLs: []string{"https://deelay.me/60000/https://httpstat.us/503"}, 1349 }) 1350 1351 _, err := remote.List(&ListOptions{}) 1352 1353 s.NotNil(err) 1354} 1355 1356func (s *RemoteSuite) TestUpdateShallows() { 1357 hashes := []plumbing.Hash{ 1358 plumbing.NewHash("0000000000000000000000000000000000000001"), 1359 plumbing.NewHash("0000000000000000000000000000000000000002"), 1360 plumbing.NewHash("0000000000000000000000000000000000000003"), 1361 plumbing.NewHash("0000000000000000000000000000000000000004"), 1362 plumbing.NewHash("0000000000000000000000000000000000000005"), 1363 plumbing.NewHash("0000000000000000000000000000000000000006"), 1364 } 1365 1366 tests := []struct { 1367 hashes []plumbing.Hash 1368 result []plumbing.Hash 1369 }{ 1370 // add to empty shallows 1371 {hashes[0:2], hashes[0:2]}, 1372 // add new hashes 1373 {hashes[2:4], hashes[0:4]}, 1374 // add some hashes already in shallow list 1375 {hashes[2:6], hashes[0:6]}, 1376 // add all hashes 1377 {hashes[0:6], hashes[0:6]}, 1378 // add empty list 1379 {nil, hashes[0:6]}, 1380 } 1381 1382 remote := NewRemote(memory.NewStorage(), &config.RemoteConfig{ 1383 Name: DefaultRemoteName, 1384 }) 1385 1386 shallows, err := remote.s.Shallow() 1387 s.NoError(err) 1388 s.Len(shallows, 0) 1389 1390 resp := new(packp.UploadPackResponse) 1391 o := &FetchOptions{ 1392 Depth: 1, 1393 } 1394 1395 for _, t := range tests { 1396 resp.Shallows = t.hashes 1397 err = remote.updateShallow(o, resp) 1398 s.NoError(err) 1399 1400 shallow, err := remote.s.Shallow() 1401 s.NoError(err) 1402 s.Len(t.result, len(shallow)) 1403 s.Equal(t.result, shallow) 1404 } 1405} 1406 1407func (s *RemoteSuite) TestUseRefDeltas() { 1408 url, err := os.MkdirTemp("", "") 1409 s.NoError(err) 1410 1411 _, err = PlainInit(url, true) 1412 s.NoError(err) 1413 1414 fs := fixtures.ByURL("https://github.com/git-fixtures/tags.git").One().DotGit() 1415 sto := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 1416 1417 r := NewRemote(sto, &config.RemoteConfig{ 1418 Name: DefaultRemoteName, 1419 URLs: []string{url}, 1420 }) 1421 1422 ar := packp.NewAdvRefs() 1423 1424 ar.Capabilities.Add(capability.OFSDelta) 1425 s.False(r.useRefDeltas(ar)) 1426 1427 ar.Capabilities.Delete(capability.OFSDelta) 1428 s.True(r.useRefDeltas(ar)) 1429} 1430 1431func (s *RemoteSuite) TestPushRequireRemoteRefs() { 1432 f := fixtures.Basic().One() 1433 sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) 1434 1435 dstFs := f.DotGit() 1436 dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault()) 1437 1438 url := dstFs.Root() 1439 r := NewRemote(sto, &config.RemoteConfig{ 1440 Name: DefaultRemoteName, 1441 URLs: []string{url}, 1442 }) 1443 1444 oldRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) 1445 s.NoError(err) 1446 s.NotNil(oldRef) 1447 1448 otherRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/master")) 1449 s.NoError(err) 1450 s.NotNil(otherRef) 1451 1452 err = r.Push(&PushOptions{ 1453 RefSpecs: []config.RefSpec{"refs/heads/master:refs/heads/branch"}, 1454 RequireRemoteRefs: []config.RefSpec{config.RefSpec(otherRef.Hash().String() + ":refs/heads/branch")}, 1455 }) 1456 s.ErrorContains(err, "remote ref refs/heads/branch required to be 6ecf0ef2c2dffb796033e5a02219af86ec6584e5 but is e8d3ffab552895c19b9fcf7aa264d277cde33881") 1457 1458 newRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) 1459 s.NoError(err) 1460 s.Equal(oldRef, newRef) 1461 1462 err = r.Push(&PushOptions{ 1463 RefSpecs: []config.RefSpec{"refs/heads/master:refs/heads/branch"}, 1464 RequireRemoteRefs: []config.RefSpec{config.RefSpec(oldRef.Hash().String() + ":refs/heads/branch")}, 1465 }) 1466 s.ErrorContains(err, "non-fast-forward update: ") 1467 1468 newRef, err = dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) 1469 s.NoError(err) 1470 s.Equal(oldRef, newRef) 1471 1472 err = r.Push(&PushOptions{ 1473 RefSpecs: []config.RefSpec{"refs/heads/master:refs/heads/branch"}, 1474 RequireRemoteRefs: []config.RefSpec{config.RefSpec(oldRef.Hash().String() + ":refs/heads/branch")}, 1475 Force: true, 1476 }) 1477 s.NoError(err) 1478 1479 newRef, err = dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) 1480 s.NoError(err) 1481 s.NotEqual(oldRef, newRef) 1482} 1483 1484func (s *RemoteSuite) TestFetchPrune() { 1485 fs := fixtures.Basic().One().DotGit() 1486 1487 url, err := os.MkdirTemp("", "") 1488 s.NoError(err) 1489 1490 _, err = PlainClone(url, true, &CloneOptions{ 1491 URL: fs.Root(), 1492 }) 1493 s.NoError(err) 1494 1495 dir, err := os.MkdirTemp("", "") 1496 s.NoError(err) 1497 1498 r, err := PlainClone(dir, true, &CloneOptions{ 1499 URL: url, 1500 }) 1501 s.NoError(err) 1502 1503 remote, err := r.Remote(DefaultRemoteName) 1504 s.NoError(err) 1505 1506 ref, err := r.Reference(plumbing.ReferenceName("refs/heads/master"), true) 1507 s.NoError(err) 1508 1509 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{ 1510 "refs/heads/master:refs/heads/branch", 1511 }}) 1512 s.NoError(err) 1513 1514 dirSave, err := os.MkdirTemp("", "") 1515 s.NoError(err) 1516 1517 rSave, err := PlainClone(dirSave, true, &CloneOptions{ 1518 URL: url, 1519 }) 1520 s.NoError(err) 1521 1522 AssertReferences(s.T(), rSave, map[string]string{ 1523 "refs/remotes/origin/branch": ref.Hash().String(), 1524 }) 1525 1526 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{ 1527 ":refs/heads/branch", 1528 }}) 1529 s.NoError(err) 1530 1531 AssertReferences(s.T(), rSave, map[string]string{ 1532 "refs/remotes/origin/branch": ref.Hash().String(), 1533 }) 1534 1535 err = rSave.Fetch(&FetchOptions{Prune: true}) 1536 s.NoError(err) 1537 1538 _, err = rSave.Reference("refs/remotes/origin/branch", true) 1539 s.ErrorContains(err, "reference not found") 1540} 1541 1542func (s *RemoteSuite) TestFetchPruneTags() { 1543 fs := fixtures.Basic().One().DotGit() 1544 1545 url, err := os.MkdirTemp("", "") 1546 s.NoError(err) 1547 1548 _, err = PlainClone(url, true, &CloneOptions{ 1549 URL: fs.Root(), 1550 }) 1551 s.NoError(err) 1552 1553 dir, err := os.MkdirTemp("", "") 1554 s.NoError(err) 1555 1556 r, err := PlainClone(dir, true, &CloneOptions{ 1557 URL: url, 1558 }) 1559 s.NoError(err) 1560 1561 remote, err := r.Remote(DefaultRemoteName) 1562 s.NoError(err) 1563 1564 ref, err := r.Reference(plumbing.ReferenceName("refs/heads/master"), true) 1565 s.NoError(err) 1566 1567 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{ 1568 "refs/heads/master:refs/tags/v1", 1569 }}) 1570 s.NoError(err) 1571 1572 dirSave, err := os.MkdirTemp("", "") 1573 s.NoError(err) 1574 1575 rSave, err := PlainClone(dirSave, true, &CloneOptions{ 1576 URL: url, 1577 }) 1578 s.NoError(err) 1579 1580 AssertReferences(s.T(), rSave, map[string]string{ 1581 "refs/tags/v1": ref.Hash().String(), 1582 }) 1583 1584 err = remote.Push(&PushOptions{RefSpecs: []config.RefSpec{ 1585 ":refs/tags/v1", 1586 }}) 1587 s.NoError(err) 1588 1589 AssertReferences(s.T(), rSave, map[string]string{ 1590 "refs/tags/v1": ref.Hash().String(), 1591 }) 1592 1593 err = rSave.Fetch(&FetchOptions{Prune: true, RefSpecs: []config.RefSpec{"refs/tags/*:refs/tags/*"}}) 1594 s.NoError(err) 1595 1596 _, err = rSave.Reference("refs/tags/v1", true) 1597 s.ErrorContains(err, "reference not found") 1598} 1599 1600func (s *RemoteSuite) TestCanPushShasToReference() { 1601 d, err := os.MkdirTemp("", "") 1602 s.NoError(err) 1603 1604 d, err = os.MkdirTemp(d, "TestCanPushShasToReference") 1605 s.NoError(err) 1606 if err != nil { 1607 return 1608 } 1609 1610 // remote currently forces a plain path for path based remotes inside the PushContext function. 1611 // This makes it impossible, in the current state to use memfs. 1612 // For the sake of readability, use the same osFS everywhere and use plain git repositories on temporary files 1613 remote, err := PlainInit(filepath.Join(d, "remote"), true) 1614 s.NoError(err) 1615 s.NotNil(remote) 1616 1617 repo, err := PlainInit(filepath.Join(d, "repo"), false) 1618 s.NoError(err) 1619 s.NotNil(repo) 1620 1621 sha := CommitNewFile(s.T(), repo, "README.md") 1622 1623 gitremote, err := repo.CreateRemote(&config.RemoteConfig{ 1624 Name: "local", 1625 URLs: []string{filepath.Join(d, "remote")}, 1626 }) 1627 s.NoError(err) 1628 if err != nil { 1629 return 1630 } 1631 1632 err = gitremote.Push(&PushOptions{ 1633 RemoteName: "local", 1634 RefSpecs: []config.RefSpec{ 1635 // TODO: check with short hashes that this is still respected 1636 config.RefSpec(sha.String() + ":refs/heads/branch"), 1637 }, 1638 }) 1639 s.NoError(err) 1640 if err != nil { 1641 return 1642 } 1643 1644 ref, err := remote.Reference(plumbing.ReferenceName("refs/heads/branch"), false) 1645 s.NoError(err) 1646 if err != nil { 1647 return 1648 } 1649 s.Equal(sha.String(), ref.Hash().String()) 1650} 1651 1652func (s *RemoteSuite) TestFetchAfterShallowClone() { 1653 tempDir, err := os.MkdirTemp("", "") 1654 s.NoError(err) 1655 remoteUrl := filepath.Join(tempDir, "remote") 1656 repoDir := filepath.Join(tempDir, "repo") 1657 1658 // Create a new repo and add more than 1 commit (so we can have a shallow commit) 1659 remote, err := PlainInit(remoteUrl, false) 1660 s.NoError(err) 1661 s.NotNil(remote) 1662 1663 _ = CommitNewFile(s.T(), remote, "File1") 1664 _ = CommitNewFile(s.T(), remote, "File2") 1665 1666 // Clone the repo with a depth of 1 1667 repo, err := PlainClone(repoDir, false, &CloneOptions{ 1668 URL: remoteUrl, 1669 Depth: 1, 1670 Tags: plumbing.NoTags, 1671 SingleBranch: true, 1672 ReferenceName: "master", 1673 }) 1674 s.NoError(err) 1675 1676 // Add new commits to the origin (more than 1 so that our next test hits a missing commit) 1677 _ = CommitNewFile(s.T(), remote, "File3") 1678 sha4 := CommitNewFile(s.T(), remote, "File4") 1679 1680 // Try fetch with depth of 1 again (note, we need to ensure no remote branch remains pointing at the old commit) 1681 r, err := repo.Remote(DefaultRemoteName) 1682 s.NoError(err) 1683 s.testFetch(r, &FetchOptions{ 1684 Depth: 2, 1685 Tags: plumbing.NoTags, 1686 1687 RefSpecs: []config.RefSpec{ 1688 "+refs/heads/master:refs/heads/master", 1689 "+refs/heads/master:refs/remotes/origin/master", 1690 }, 1691 }, []*plumbing.Reference{ 1692 plumbing.NewReferenceFromStrings("refs/heads/master", sha4.String()), 1693 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", sha4.String()), 1694 plumbing.NewSymbolicReference("HEAD", "refs/heads/master"), 1695 }) 1696 1697 // Add another commit to the origin 1698 sha5 := CommitNewFile(s.T(), remote, "File5") 1699 1700 // Try fetch with depth of 2 this time (to reach a commit that we don't have locally) 1701 r, err = repo.Remote(DefaultRemoteName) 1702 s.NoError(err) 1703 s.testFetch(r, &FetchOptions{ 1704 Depth: 1, 1705 Tags: plumbing.NoTags, 1706 1707 RefSpecs: []config.RefSpec{ 1708 "+refs/heads/master:refs/heads/master", 1709 "+refs/heads/master:refs/remotes/origin/master", 1710 }, 1711 }, []*plumbing.Reference{ 1712 plumbing.NewReferenceFromStrings("refs/heads/master", sha5.String()), 1713 plumbing.NewReferenceFromStrings("refs/remotes/origin/master", sha5.String()), 1714 plumbing.NewSymbolicReference("HEAD", "refs/heads/master"), 1715 }) 1716} 1717 1718func TestFetchFastForwardForCustomRef(t *testing.T) { 1719 customRef := "refs/custom/branch" 1720 // 1. Set up a remote with a URL 1721 remoteURL := t.TempDir() 1722 remoteRepo, err := PlainInit(remoteURL, true) 1723 if err != nil { 1724 t.Fatal(err) 1725 } 1726 1727 // 2. Add a commit with an empty tree to master and custom ref, also set HEAD 1728 emptyTreeID := writeEmptyTree(t, remoteRepo) 1729 writeCommitToRef(t, remoteRepo, "refs/heads/master", emptyTreeID, time.Now()) 1730 writeCommitToRef(t, remoteRepo, customRef, emptyTreeID, time.Now()) 1731 if err := remoteRepo.Storer.SetReference(plumbing.NewSymbolicReference(plumbing.HEAD, "refs/heads/master")); err != nil { 1732 t.Fatal(err) 1733 } 1734 1735 // 3. Clone repo, then fetch the custom ref 1736 // Note that using custom ref in ReferenceName has an IsBranch issue 1737 localRepo, err := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{ 1738 URL: remoteURL, 1739 }) 1740 if err != nil { 1741 t.Fatal(err) 1742 } 1743 if err := localRepo.Fetch(&FetchOptions{ 1744 RefSpecs: []config.RefSpec{ 1745 config.RefSpec(fmt.Sprintf("%s:%s", customRef, customRef)), 1746 }, 1747 }); err != nil { 1748 t.Fatal(err) 1749 } 1750 1751 // 4. Make divergent changes 1752 remoteCommitID := writeCommitToRef(t, remoteRepo, customRef, emptyTreeID, time.Now()) 1753 // Consecutive calls to writeCommitToRef with time.Now() might have the same 1754 // time value, explicitly set distinct ones to ensure the commit hashes 1755 // differ 1756 writeCommitToRef(t, localRepo, customRef, emptyTreeID, time.Now().Add(time.Second)) 1757 1758 // 5. Try to fetch with fast-forward only mode 1759 remote, err := localRepo.Remote(DefaultRemoteName) 1760 if err != nil { 1761 t.Fatal(err) 1762 } 1763 1764 err = remote.Fetch(&FetchOptions{RefSpecs: []config.RefSpec{ 1765 config.RefSpec(fmt.Sprintf("%s:%s", customRef, customRef)), 1766 }}) 1767 if !errors.Is(err, ErrForceNeeded) { 1768 t.Errorf("expected %v, got %v", ErrForceNeeded, err) 1769 } 1770 1771 // 6. Fetch with force 1772 err = remote.Fetch(&FetchOptions{RefSpecs: []config.RefSpec{ 1773 config.RefSpec(fmt.Sprintf("+%s:%s", customRef, customRef)), 1774 }}) 1775 if err != nil { 1776 t.Errorf("unexpected error %v", err) 1777 } 1778 1779 // 7. Assert commit ID matches 1780 ref, err := localRepo.Reference(plumbing.ReferenceName(customRef), true) 1781 if err != nil { 1782 t.Fatal(err) 1783 } 1784 if remoteCommitID != ref.Hash() { 1785 t.Errorf("expected %s, got %s", remoteCommitID.String(), ref.Hash().String()) 1786 } 1787} 1788 1789func writeEmptyTree(t *testing.T, repo *Repository) plumbing.Hash { 1790 t.Helper() 1791 1792 obj := repo.Storer.NewEncodedObject() 1793 obj.SetType(plumbing.TreeObject) 1794 1795 tree := object.Tree{Entries: nil} 1796 if err := tree.Encode(obj); err != nil { 1797 t.Fatal(err) 1798 } 1799 1800 treeID, err := repo.Storer.SetEncodedObject(obj) 1801 if err != nil { 1802 t.Fatal(err) 1803 } 1804 1805 return treeID 1806} 1807 1808func writeCommitToRef(t *testing.T, repo *Repository, refName string, treeID plumbing.Hash, when time.Time) plumbing.Hash { 1809 t.Helper() 1810 1811 ref, err := repo.Reference(plumbing.ReferenceName(refName), true) 1812 if err != nil { 1813 if errors.Is(err, plumbing.ErrReferenceNotFound) { 1814 if err := repo.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName(refName), plumbing.ZeroHash)); err != nil { 1815 t.Fatal(err) 1816 } 1817 1818 ref, err = repo.Reference(plumbing.ReferenceName(refName), true) 1819 if err != nil { 1820 t.Fatal(err) 1821 } 1822 } else { 1823 t.Fatal(err) 1824 } 1825 } 1826 1827 commit := &object.Commit{ 1828 TreeHash: treeID, 1829 Author: object.Signature{ 1830 When: when, 1831 }, 1832 } 1833 if !ref.Hash().IsZero() { 1834 commit.ParentHashes = []plumbing.Hash{ref.Hash()} 1835 } 1836 1837 obj := repo.Storer.NewEncodedObject() 1838 if err := commit.Encode(obj); err != nil { 1839 t.Fatal(err) 1840 } 1841 1842 commitID, err := repo.Storer.SetEncodedObject(obj) 1843 if err != nil { 1844 t.Fatal(err) 1845 } 1846 1847 newRef := plumbing.NewHashReference(plumbing.ReferenceName(refName), commitID) 1848 if err := repo.Storer.CheckAndSetReference(newRef, ref); err != nil { 1849 t.Fatal(err) 1850 } 1851 1852 return commitID 1853}