diff options
author | Andrew Suffield <asuffield@gmail.com> | 2021-03-25 10:21:38 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-25 11:21:38 +0100 |
commit | bf3471db54b0255ab5b159005069f37528a151b7 (patch) | |
tree | 9e91aa4f9c1de64aab04a04125e176fbbf3aa983 /remote.go | |
parent | 55ba7b2e0f7a1c542c88022d65b16ca79a300e6c (diff) | |
download | go-git-bf3471db54b0255ab5b159005069f37528a151b7.tar.gz |
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.
Diffstat (limited to 'remote.go')
-rw-r--r-- | remote.go | 34 |
1 files changed, 34 insertions, 0 deletions
@@ -119,6 +119,10 @@ func (r *Remote) PushContext(ctx context.Context, o *PushOptions) (err error) { return err } + if err := r.checkRequireRemoteRefs(o.RequireRemoteRefs, remoteRefs); err != nil { + return err + } + isDelete := false allDelete := true for _, rs := range o.RefSpecs { @@ -1166,3 +1170,33 @@ outer: return r.s.SetShallow(shallows) } + +func (r *Remote) checkRequireRemoteRefs(requires []config.RefSpec, remoteRefs storer.ReferenceStorer) error { + for _, require := range requires { + if require.IsWildcard() { + return fmt.Errorf("wildcards not supported in RequireRemoteRefs, got %s", require.String()) + } + + name := require.Dst("") + remote, err := remoteRefs.Reference(name) + if err != nil { + return fmt.Errorf("remote ref %s required to be %s but is absent", name.String(), require.Src()) + } + + var requireHash string + if require.IsExactSHA1() { + requireHash = require.Src() + } else { + target, err := storer.ResolveReference(remoteRefs, plumbing.ReferenceName(require.Src())) + if err != nil { + return fmt.Errorf("could not resolve ref %s in RequireRemoteRefs", require.Src()) + } + requireHash = target.Hash().String() + } + + if remote.Hash().String() != requireHash { + return fmt.Errorf("remote ref %s required to be %s but is %s", name.String(), requireHash, remote.Hash().String()) + } + } + return nil +} |