Skip to content

Commit

Permalink
git: enable fetch with unqualified references
Browse files Browse the repository at this point in the history
Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com>
  • Loading branch information
AriehSchneier committed May 14, 2023
1 parent 1dbd729 commit d2d18fa
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 57 deletions.
7 changes: 4 additions & 3 deletions plumbing/reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ const (
symrefPrefix = "ref: "
)

// 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.
// RefRevParseRules are a set of rules to parse references into short names, or expand into a full reference.
// These are the same rules as used by git in shorten_unambiguous_ref and expand_ref.
// See: https://github.com/git/git/blob/e0aaa1b6532cfce93d87af9bc813fb2e7a7ce9d7/refs.c#L417
var RefRevParseRules = []string{
"%s",
"refs/%s",
"refs/tags/%s",
"refs/heads/%s",
Expand Down Expand Up @@ -113,7 +114,7 @@ func (r ReferenceName) String() string {
func (r ReferenceName) Short() string {
s := string(r)
res := s
for _, format := range RefRevParseRules {
for _, format := range RefRevParseRules[1:] {
_, err := fmt.Sscanf(s, format, &res)
if err == nil {
continue
Expand Down
81 changes: 48 additions & 33 deletions remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen
return nil, err
}

refs, err := calculateRefs(o.RefSpecs, remoteRefs, o.Tags)
refs, specToRefs, err := calculateRefs(o.RefSpecs, remoteRefs, o.Tags)
if err != nil {
return nil, err
}
Expand All @@ -469,7 +469,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (sto storer.Referen
}
}

updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, o.Tags, o.Force)
updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, specToRefs, o.Tags, o.Force)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -919,42 +919,41 @@ func calculateRefs(
spec []config.RefSpec,
remoteRefs storer.ReferenceStorer,
tagMode TagMode,
) (memory.ReferenceStorage, error) {
) (memory.ReferenceStorage, [][]*plumbing.Reference, error) {
if tagMode == AllTags {
spec = append(spec, refspecAllTags)
}

refs := make(memory.ReferenceStorage)
for _, s := range spec {
if err := doCalculateRefs(s, remoteRefs, refs); err != nil {
return nil, err
// list of references matched for each spec
specToRefs := make([][]*plumbing.Reference, len(spec))
for i := range spec {
var err error
specToRefs[i], err = doCalculateRefs(spec[i], remoteRefs, refs)
if err != nil {
return nil, nil, err
}
}

return refs, nil
return refs, specToRefs, nil
}

func doCalculateRefs(
s config.RefSpec,
remoteRefs storer.ReferenceStorer,
refs memory.ReferenceStorage,
) error {
iter, err := remoteRefs.IterReferences()
if err != nil {
return err
}
) ([]*plumbing.Reference, error) {
var refList []*plumbing.Reference

if s.IsExactSHA1() {
ref := plumbing.NewHashReference(s.Dst(""), plumbing.NewHash(s.Src()))
return refs.SetReference(ref)

refList = append(refList, ref)
return refList, refs.SetReference(ref)
}

var matched bool
err = iter.ForEach(func(ref *plumbing.Reference) error {
if !s.Match(ref.Name()) {
return nil
}

onMatched := func(ref *plumbing.Reference) error {
if ref.Type() == plumbing.SymbolicReference {
target, err := storer.ResolveReference(remoteRefs, ref.Name())
if err != nil {
Expand All @@ -969,22 +968,37 @@ func doCalculateRefs(
}

matched = true
if err := refs.SetReference(ref); err != nil {
return err
}
refList = append(refList, ref)
return refs.SetReference(ref)
}

if !s.IsWildcard() {
return storer.ErrStop
var ret error
if s.IsWildcard() {
iter, err := remoteRefs.IterReferences()
if err != nil {
return nil, err
}
ret = iter.ForEach(func(ref *plumbing.Reference) error {
if !s.Match(ref.Name()) {
return nil
}

return nil
})
return onMatched(ref)
})
} else {
var resolvedRef *plumbing.Reference
src := s.Src()
resolvedRef, ret = expand_ref(remoteRefs, plumbing.ReferenceName(src))
if ret == nil {
ret = onMatched(resolvedRef)
}
}

if !matched && !s.IsWildcard() {
return NoMatchingRefSpecError{refSpec: s}
return nil, NoMatchingRefSpecError{refSpec: s}
}

return err
return refList, ret
}

func getWants(localStorer storage.Storer, refs memory.ReferenceStorage) ([]plumbing.Hash, error) {
Expand Down Expand Up @@ -1144,27 +1158,28 @@ func buildSidebandIfSupported(l *capability.List, reader io.Reader, p sideband.P
func (r *Remote) updateLocalReferenceStorage(
specs []config.RefSpec,
fetchedRefs, remoteRefs memory.ReferenceStorage,
specToRefs [][]*plumbing.Reference,
tagMode TagMode,
force bool,
) (updated bool, err error) {
isWildcard := true
forceNeeded := false

for _, spec := range specs {
for i, spec := range specs {
if !spec.IsWildcard() {
isWildcard = false
}

for _, ref := range fetchedRefs {
if !spec.Match(ref.Name()) && !spec.IsExactSHA1() {
continue
}

for _, ref := range specToRefs[i] {
if ref.Type() != plumbing.HashReference {
continue
}

localName := spec.Dst(ref.Name())
// If localName doesn't start with "refs/" then treat as a branch.
if !strings.HasPrefix(localName.String(), "refs/") {
localName = plumbing.NewBranchReferenceName(localName.String())
}
old, _ := storer.ResolveReference(r.s, localName)
new := plumbing.NewHashReference(localName, ref.Hash())

Expand Down
56 changes: 56 additions & 0 deletions remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,62 @@ func (s *RemoteSuite) TestFetch(c *C) {
})
}

func (s *RemoteSuite) TestFetchToNewBranch(c *C) {
r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
})

s.testFetch(c, r, &FetchOptions{
RefSpecs: []config.RefSpec{
// qualified branch to unqualified branch
"refs/heads/master:foo",
// unqualified branch to unqualified branch
"+master:bar",
// unqualified tag to unqualified branch
config.RefSpec("tree-tag:tree-tag"),
// unqualified tag to qualified tag
config.RefSpec("+commit-tag:refs/tags/renamed-tag"),
},
}, []*plumbing.Reference{
plumbing.NewReferenceFromStrings("refs/heads/foo", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
plumbing.NewReferenceFromStrings("refs/heads/bar", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
plumbing.NewReferenceFromStrings("refs/heads/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"),
plumbing.NewReferenceFromStrings("refs/tags/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"),
plumbing.NewReferenceFromStrings("refs/tags/renamed-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"),
plumbing.NewReferenceFromStrings("refs/tags/commit-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"),
})
}

func (s *RemoteSuite) TestFetchToNewBranchWithAllTags(c *C) {
r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
})

s.testFetch(c, r, &FetchOptions{
Tags: AllTags,
RefSpecs: []config.RefSpec{
// qualified branch to unqualified branch
"+refs/heads/master:foo",
// unqualified branch to unqualified branch
"+master:bar",
// unqualified tag to unqualified branch
config.RefSpec("+tree-tag:tree-tag"),
// unqualified tag to qualified tag
config.RefSpec("+commit-tag:refs/tags/renamed-tag"),
},
}, []*plumbing.Reference{
plumbing.NewReferenceFromStrings("refs/heads/foo", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
plumbing.NewReferenceFromStrings("refs/heads/bar", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
plumbing.NewReferenceFromStrings("refs/heads/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"),
plumbing.NewReferenceFromStrings("refs/tags/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70"),
plumbing.NewReferenceFromStrings("refs/tags/renamed-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"),
plumbing.NewReferenceFromStrings("refs/tags/commit-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc"),
plumbing.NewReferenceFromStrings("refs/tags/annotated-tag", "b742a2a9fa0afcfa9a6fad080980fbc26b007c69"),
plumbing.NewReferenceFromStrings("refs/tags/blob-tag", "fe6cb94756faa81e5ed9240f9191b833db5f40ae"),
plumbing.NewReferenceFromStrings("refs/tags/lightweight-tag", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f"),
})
}

func (s *RemoteSuite) TestFetchNonExistantReference(c *C) {
r := NewRemote(memory.NewStorage(), &config.RemoteConfig{
URLs: []string{s.GetLocalRepositoryURL(fixtures.ByTag("tags").One())},
Expand Down
43 changes: 22 additions & 21 deletions repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -1013,21 +1013,9 @@ func (r *Repository) fetchAndUpdateReferences(
return nil, err
}

var resolvedRef *plumbing.Reference
// return error from checking the raw ref passed in
var rawRefError error
for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) {
resolvedRef, err = storer.ResolveReference(remoteRefs, plumbing.ReferenceName(fmt.Sprintf(rule, ref)))

if err == nil {
break
} else if rawRefError == nil {
rawRefError = err
}
}

resolvedRef, err := expand_ref(remoteRefs, ref)
if err != nil {
return nil, rawRefError
return nil, err
}

refsUpdated, err := r.updateReferences(remote.c.Fetch, resolvedRef)
Expand Down Expand Up @@ -1473,6 +1461,23 @@ func (r *Repository) Worktree() (*Worktree, error) {
return &Worktree{r: r, Filesystem: r.wt}, nil
}

func expand_ref(s storer.ReferenceStorer, ref plumbing.ReferenceName) (*plumbing.Reference, error) {
// For improving troubleshooting, this preserves the error for the provided `ref`,
// and returns the error for that specific ref in case all parse rules fails.
var ret error
for _, rule := range plumbing.RefRevParseRules {
resolvedRef, err := storer.ResolveReference(s, plumbing.ReferenceName(fmt.Sprintf(rule, ref)))

if err == nil {
return resolvedRef, nil
} else if ret == nil {
ret = err
}
}

return nil, ret
}

// ResolveRevision resolves revision to corresponding hash. It will always
// resolve to a commit hash, not a tree or annotated tag.
//
Expand Down Expand Up @@ -1502,13 +1507,9 @@ func (r *Repository) ResolveRevision(in plumbing.Revision) (*plumbing.Hash, erro

tryHashes = append(tryHashes, r.resolveHashPrefix(string(revisionRef))...)

for _, rule := range append([]string{"%s"}, plumbing.RefRevParseRules...) {
ref, err := storer.ResolveReference(r.Storer, plumbing.ReferenceName(fmt.Sprintf(rule, revisionRef)))

if err == nil {
tryHashes = append(tryHashes, ref.Hash())
break
}
ref, err := expand_ref(r.Storer, plumbing.ReferenceName(revisionRef))
if err == nil {
tryHashes = append(tryHashes, ref.Hash())
}

// in ambiguous cases, `git rev-parse` will emit a warning, but
Expand Down

0 comments on commit d2d18fa

Please sign in to comment.