Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote: add RequireRemoteRefs to PushOptions #258

Merged
merged 1 commit into from Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions options.go
Expand Up @@ -210,6 +210,9 @@ type PushOptions struct {
InsecureSkipTLS bool
// CABundle specify additional ca bundle with system cert pool
CABundle []byte
// RequireRemoteRefs only allows a remote ref to be updated if its current
// value is the one specified here.
RequireRemoteRefs []config.RefSpec
}

// Validate validates the fields and sets the default values.
Expand Down
34 changes: 34 additions & 0 deletions remote.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
53 changes: 53 additions & 0 deletions remote_test.go
Expand Up @@ -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)
}