From bf3471db54b0255ab5b159005069f37528a151b7 Mon Sep 17 00:00:00 2001 From: Andrew Suffield Date: Thu, 25 Mar 2021 10:21:38 +0000 Subject: add RequireRemoteRefs to PushOptions (#258) The git protocol itself uses a compare-and-swap mechanism, where changes send the old and new values and the change is only applied if the old value matches. This is used to implement the --force-with-lease feature in git push. go-git populates the `old` field with the current value of the ref that is read from the remote. We can implement a convenient (albeit more limited) form of the --force-with-lease feature just by allowing the caller to specify particular values for this ref. Callers can then implement complex multi-step atomic operations by reading the ref themselves at the start of the process, and passing to in RequireRemoteRefs at the end. This is also a suitable building block for implementing --force-with-lease (#101), which is mostly an exercise in computing the correct hash to require. Hence, this appears to be the most reasonable API to expose. --- remote_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'remote_test.go') diff --git a/remote_test.go b/remote_test.go index 3446f1a..bc05b7e 100644 --- a/remote_test.go +++ b/remote_test.go @@ -971,3 +971,56 @@ func (s *RemoteSuite) TestUseRefDeltas(c *C) { ar.Capabilities.Delete(capability.OFSDelta) c.Assert(r.useRefDeltas(ar), Equals, true) } + +func (s *RemoteSuite) TestPushRequireRemoteRefs(c *C) { + f := fixtures.Basic().One() + sto := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) + + dstFs := f.DotGit() + dstSto := filesystem.NewStorage(dstFs, cache.NewObjectLRUDefault()) + + url := dstFs.Root() + r := NewRemote(sto, &config.RemoteConfig{ + Name: DefaultRemoteName, + URLs: []string{url}, + }) + + oldRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) + c.Assert(err, IsNil) + c.Assert(oldRef, NotNil) + + otherRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/master")) + c.Assert(err, IsNil) + c.Assert(otherRef, NotNil) + + err = r.Push(&PushOptions{ + RefSpecs: []config.RefSpec{"refs/heads/master:refs/heads/branch"}, + RequireRemoteRefs: []config.RefSpec{config.RefSpec(otherRef.Hash().String() + ":refs/heads/branch")}, + }) + c.Assert(err, ErrorMatches, "remote ref refs/heads/branch required to be .* but is .*") + + newRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) + c.Assert(err, IsNil) + c.Assert(newRef, DeepEquals, oldRef) + + err = r.Push(&PushOptions{ + RefSpecs: []config.RefSpec{"refs/heads/master:refs/heads/branch"}, + RequireRemoteRefs: []config.RefSpec{config.RefSpec(oldRef.Hash().String() + ":refs/heads/branch")}, + }) + c.Assert(err, ErrorMatches, "non-fast-forward update: .*") + + newRef, err = dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) + c.Assert(err, IsNil) + c.Assert(newRef, DeepEquals, oldRef) + + err = r.Push(&PushOptions{ + RefSpecs: []config.RefSpec{"refs/heads/master:refs/heads/branch"}, + RequireRemoteRefs: []config.RefSpec{config.RefSpec(oldRef.Hash().String() + ":refs/heads/branch")}, + Force: true, + }) + c.Assert(err, IsNil) + + newRef, err = dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) + c.Assert(err, IsNil) + c.Assert(newRef, Not(DeepEquals), oldRef) +} -- cgit