diff options
-rw-r--r-- | plumbing/reference.go | 36 | ||||
-rw-r--r-- | plumbing/reference_test.go | 5 | ||||
-rw-r--r-- | remote.go | 60 | ||||
-rw-r--r-- | remote_test.go | 47 |
4 files changed, 118 insertions, 30 deletions
diff --git a/plumbing/reference.go b/plumbing/reference.go index 8fa103e..5d477b9 100644 --- a/plumbing/reference.go +++ b/plumbing/reference.go @@ -15,15 +15,16 @@ const ( symrefPrefix = "ref: " ) -var ( - refPrefixes = []string{ - refHeadPrefix, - refTagPrefix, - refRemotePrefix, - refNotePrefix, - refPrefix, - } -) +// refRevParseRules are a set of rules to parse references into short names. +// These are the same rules as used by git in shorten_unambiguous_ref. +// See: https://github.com/git/git/blob/e0aaa1b6532cfce93d87af9bc813fb2e7a7ce9d7/refs.c#L417 +var refRevParseRules = []string{ + "refs/%s", + "refs/tags/%s", + "refs/heads/%s", + "refs/remotes/%s", + "refs/remotes/%s/HEAD", +} var ( ErrReferenceNotFound = errors.New("reference not found") @@ -60,17 +61,16 @@ func (r ReferenceName) String() string { // Short returns the short name of a ReferenceName func (r ReferenceName) Short() string { - return r.removeRefPrefix() -} - -// Instead of hardcoding a number of components, we should remove the prefixes -// refHeadPrefix, refTagPrefix, refRemotePrefix, refNotePrefix and refPrefix -func (r ReferenceName) removeRefPrefix() string { s := string(r) - for _, prefix := range refPrefixes { - s = strings.TrimPrefix(s, prefix) + res := s + for _, format := range refRevParseRules { + _, err := fmt.Sscanf(s, format, &res) + if err == nil { + continue + } } - return s + + return res } const ( diff --git a/plumbing/reference_test.go b/plumbing/reference_test.go index 6a695f4..97c8772 100644 --- a/plumbing/reference_test.go +++ b/plumbing/reference_test.go @@ -23,6 +23,11 @@ func (s *ReferenceSuite) TestReferenceNameWithSlash(c *C) { c.Assert(r.Short(), Equals, "origin/feature/AllowSlashes") } +func (s *ReferenceSuite) TestReferenceNameNote(c *C) { + r := ReferenceName("refs/notes/foo") + c.Assert(r.Short(), Equals, "notes/foo") +} + func (s *ReferenceSuite) TestNewReferenceFromStrings(c *C) { r := NewReferenceFromStrings("refs/heads/v4", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5") c.Assert(r.Type(), Equals, HashReference) @@ -238,24 +238,60 @@ func (r *Remote) addReferencesToUpdate(refspecs []config.RefSpec, req *packp.ReferenceUpdateRequest) error { for _, rs := range refspecs { - iter, err := r.s.IterReferences() - if err != nil { - return err - } - - err = iter.ForEach(func(ref *plumbing.Reference) error { - return r.addReferenceIfRefSpecMatches( - rs, remoteRefs, ref, req, - ) - }) - if err != nil { - return err + if rs.IsDelete() { + if err := r.deleteReferences(rs, remoteRefs, req); err != nil { + return err + } + } else { + if err := r.addOrUpdateReferences(rs, remoteRefs, req); err != nil { + return err + } } } return nil } +func (r *Remote) addOrUpdateReferences(rs config.RefSpec, + remoteRefs storer.ReferenceStorer, req *packp.ReferenceUpdateRequest) error { + iter, err := r.s.IterReferences() + if err != nil { + return err + } + + return iter.ForEach(func(ref *plumbing.Reference) error { + return r.addReferenceIfRefSpecMatches( + rs, remoteRefs, ref, req, + ) + }) +} + +func (r *Remote) deleteReferences(rs config.RefSpec, + remoteRefs storer.ReferenceStorer, req *packp.ReferenceUpdateRequest) error { + iter, err := remoteRefs.IterReferences() + if err != nil { + return err + } + + return iter.ForEach(func(ref *plumbing.Reference) error { + if ref.Type() != plumbing.HashReference { + return nil + } + + if rs.Dst("") != ref.Name() { + return nil + } + + cmd := &packp.Command{ + Name: ref.Name(), + Old: ref.Hash(), + New: plumbing.ZeroHash, + } + req.Commands = append(req.Commands, cmd) + return nil + }) +} + func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec, remoteRefs storer.ReferenceStorer, localRef *plumbing.Reference, req *packp.ReferenceUpdateRequest) error { diff --git a/remote_test.go b/remote_test.go index 4297b81..c12d5d5 100644 --- a/remote_test.go +++ b/remote_test.go @@ -5,6 +5,8 @@ import ( "io" "io/ioutil" "os" + "path/filepath" + "strings" "github.com/src-d/go-git-fixtures" "gopkg.in/src-d/go-git.v4/config" @@ -334,6 +336,32 @@ func (s *RemoteSuite) TestPushNoErrAlreadyUpToDate(c *C) { c.Assert(err, Equals, NoErrAlreadyUpToDate) } +func (s *RemoteSuite) TestPushDeleteReference(c *C) { + f := fixtures.Basic().One() + sto, err := filesystem.NewStorage(f.DotGit()) + c.Assert(err, IsNil) + + dstFs := f.DotGit() + dstSto, err := filesystem.NewStorage(dstFs) + c.Assert(err, IsNil) + prepareRepo(c, dstFs.Root()) + + url := dstFs.Root() + r := newRemote(sto, &config.RemoteConfig{ + Name: DefaultRemoteName, + URL: url, + }) + + rs := config.RefSpec(":refs/heads/branch") + err = r.Push(&PushOptions{ + RefSpecs: []config.RefSpec{rs}, + }) + c.Assert(err, IsNil) + + _, err = dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) + c.Assert(err, Equals, plumbing.ErrReferenceNotFound) +} + func (s *RemoteSuite) TestPushRejectNonFastForward(c *C) { f := fixtures.Basic().One() sto, err := filesystem.NewStorage(f.DotGit()) @@ -470,3 +498,22 @@ func (s *RemoteSuite) TestPushWrongRemoteName(c *C) { }) c.Assert(err, ErrorMatches, ".*remote names don't match.*") } + +const bareConfig = `[core] +repositoryformatversion = 0 +filemode = true +bare = true` + +func prepareRepo(c *C, path string) { + // git-receive-pack refuses to update refs/heads/master on non-bare repo + // so we ensure bare repo config. + config := filepath.Join(path, "config") + if _, err := os.Stat(config); err == nil { + f, err := os.OpenFile(config, os.O_TRUNC|os.O_WRONLY, 0) + c.Assert(err, IsNil) + content := strings.NewReader(bareConfig) + _, err = io.Copy(f, content) + c.Assert(err, IsNil) + c.Assert(f.Close(), IsNil) + } +} |