aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArieh Schneier <15041913+AriehSchneier@users.noreply.github.com>2023-05-25 08:56:17 +1000
committerArieh Schneier <15041913+AriehSchneier@users.noreply.github.com>2023-05-25 08:56:17 +1000
commit65a5c716353f81b20c70f4a2c6560590d9472b6e (patch)
treeb5a0c837eb3afe02cb8f24d85846c6464616f655
parentb98b813a17d32f4fa29a3ef2e9f4c38c5a97b440 (diff)
downloadgo-git-65a5c716353f81b20c70f4a2c6560590d9472b6e.tar.gz
git: enable fetch with unqualified references
Signed-off-by: Arieh Schneier <15041913+AriehSchneier@users.noreply.github.com>
-rw-r--r--plumbing/reference.go7
-rw-r--r--remote.go81
-rw-r--r--remote_test.go56
-rw-r--r--repository.go43
4 files changed, 130 insertions, 57 deletions
diff --git a/plumbing/reference.go b/plumbing/reference.go
index aeb4227..5a67f69 100644
--- a/plumbing/reference.go
+++ b/plumbing/reference.go
@@ -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",
@@ -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
diff --git a/remote.go b/remote.go
index 0e72aad..6ea275c 100644
--- a/remote.go
+++ b/remote.go
@@ -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
}
@@ -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
}
@@ -929,42 +929,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 {
@@ -979,22 +978,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) {
@@ -1154,27 +1168,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())
diff --git a/remote_test.go b/remote_test.go
index f8a0bdb..ca5f261 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -140,6 +140,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())},
diff --git a/repository.go b/repository.go
index f3540c6..17fa5dd 100644
--- a/repository.go
+++ b/repository.go
@@ -1029,21 +1029,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)
@@ -1489,6 +1477,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.
//
@@ -1518,13 +1523,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