Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

remote: fetch, correct behavior on tags #485

Merged
merged 1 commit into from
Jul 17, 2017
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ func (o *PullOptions) Validate() error {
return nil
}

type TagFetchMode int

var (
// TagFollowing any tag that points into the histories being fetched is also
// fetched. TagFollowing requires a server with `include-tag` capability
// in order to fetch the annotated tags objects.
TagFollowing TagFetchMode = 0
// AllTags fetch all tags from the remote (i.e., fetch remote tags
// refs/tags/* into local tags with the same name)
AllTags TagFetchMode = 1
)

// FetchOptions describes how a fetch should be performed
type FetchOptions struct {
// Name of the remote to fetch from. Defaults to origin.
Expand All @@ -117,6 +129,9 @@ type FetchOptions struct {
// stored, if nil nothing is stored and the capability (if supported)
// no-progress, is sent to the server to avoid send this information.
Progress sideband.Progress
// Tags describe how the tags will be fetched from the remote repository,
// by default is TagFollowing.
Tags TagFetchMode
}

// Validate validates the fields and sets the default values.
Expand Down
139 changes: 83 additions & 56 deletions remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (r *Remote) Push(o *PushOptions) (err error) {
return rs.Error()
}

func (r *Remote) fetch(o *FetchOptions) (refs storer.ReferenceStorer, err error) {
func (r *Remote) fetch(o *FetchOptions) (storer.ReferenceStorer, error) {
if o.RemoteName == "" {
o.RemoteName = r.c.Name
}
Expand Down Expand Up @@ -169,7 +169,12 @@ func (r *Remote) fetch(o *FetchOptions) (refs storer.ReferenceStorer, err error)
return nil, err
}

req.Wants, err = getWants(o.RefSpecs, r.s, remoteRefs)
refs, err := calculateRefs(o.RefSpecs, remoteRefs, o.Tags)
if err != nil {
return nil, err
}

req.Wants, err = getWants(r.s, refs)
if len(req.Wants) > 0 {
req.Haves, err = getHaves(r.s)
if err != nil {
Expand All @@ -181,14 +186,15 @@ func (r *Remote) fetch(o *FetchOptions) (refs storer.ReferenceStorer, err error)
}
}

err = r.updateLocalReferenceStorage(o.RefSpecs, remoteRefs)
if err != nil && err != NoErrAlreadyUpToDate {
updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs)
if err != nil {
return nil, err
}

if len(req.Wants) == 0 {
return remoteRefs, err
if !updated {
return remoteRefs, NoErrAlreadyUpToDate
}

return remoteRefs, nil
}

Expand Down Expand Up @@ -372,56 +378,52 @@ func getHaves(localRefs storer.ReferenceStorer) ([]plumbing.Hash, error) {
return haves, nil
}

func getWants(
spec []config.RefSpec, localStorer storage.Storer, remoteRefs storer.ReferenceStorer,
) ([]plumbing.Hash, error) {
wantTags := true
for _, s := range spec {
if !s.IsWildcard() {
wantTags = false
break
}
}

func calculateRefs(spec []config.RefSpec,
remoteRefs storer.ReferenceStorer,
tags TagFetchMode,
) (memory.ReferenceStorage, error) {
iter, err := remoteRefs.IterReferences()
if err != nil {
return nil, err
}

wants := map[plumbing.Hash]bool{}
err = iter.ForEach(func(ref *plumbing.Reference) error {
refs := make(memory.ReferenceStorage, 0)
return refs, iter.ForEach(func(ref *plumbing.Reference) error {
if !config.MatchAny(spec, ref.Name()) {
if !ref.IsTag() || !wantTags {
if !ref.IsTag() || tags != AllTags {
return nil
}
}

if ref.Type() == plumbing.SymbolicReference {
ref, err = storer.ResolveReference(remoteRefs, ref.Name())
target, err := storer.ResolveReference(remoteRefs, ref.Name())
if err != nil {
return err
}

ref = plumbing.NewHashReference(ref.Name(), target.Hash())
}

if ref.Type() != plumbing.HashReference {
return nil
}

hash := ref.Hash()
return refs.SetReference(ref)
})
}

exists, err := objectExists(localStorer, hash)
func getWants(localStorer storage.Storer, refs memory.ReferenceStorage) ([]plumbing.Hash, error) {
wants := map[plumbing.Hash]bool{}
for _, ref := range refs {
hash := ref.Hash()
exists, err := objectExists(localStorer, ref.Hash())
if err != nil {
return err
return nil, err
}

if !exists {
wants[hash] = true
}

return nil
})
if err != nil {
return nil, err
}

var result []plumbing.Hash
Expand Down Expand Up @@ -503,6 +505,19 @@ func (r *Remote) newUploadPackRequest(o *FetchOptions,
}
}

isWildcard := true
for _, s := range o.RefSpecs {
if !s.IsWildcard() {
isWildcard = false
}
}

if isWildcard && o.Tags == TagFollowing && ar.Capabilities.Supports(capability.IncludeTag) {
if err := req.Capabilities.Set(capability.IncludeTag); err != nil {
return nil, err
}
}

return req, nil
}

Expand All @@ -524,10 +539,17 @@ func buildSidebandIfSupported(l *capability.List, reader io.Reader, p sideband.P
return d
}

func (r *Remote) updateLocalReferenceStorage(specs []config.RefSpec, refs memory.ReferenceStorage) error {
updated := false
func (r *Remote) updateLocalReferenceStorage(
specs []config.RefSpec,
fetchedRefs, remoteRefs memory.ReferenceStorage,
) (updated bool, err error) {
isWildcard := true
for _, spec := range specs {
for _, ref := range refs {
if !spec.IsWildcard() {
isWildcard = false
}

for _, ref := range fetchedRefs {
if !spec.Match(ref.Name()) {
continue
}
Expand All @@ -536,33 +558,36 @@ func (r *Remote) updateLocalReferenceStorage(specs []config.RefSpec, refs memory
continue
}

name := spec.Dst(ref.Name())
sref, err := r.s.Reference(name)
if err != nil && err != plumbing.ErrReferenceNotFound {
return err
new := plumbing.NewHashReference(spec.Dst(ref.Name()), ref.Hash())

refUpdated, err := updateReferenceStorerIfNeeded(r.s, new)
if err != nil {
return updated, err
}
if err == plumbing.ErrReferenceNotFound || sref.Hash() != ref.Hash() {
n := plumbing.NewHashReference(name, ref.Hash())
if err := r.s.SetReference(n); err != nil {
return err
}

if refUpdated {
updated = true
}
}
}

if err := r.buildFetchedTags(refs); err != nil {
return err
tags := fetchedRefs
if isWildcard {
tags = remoteRefs
}
tagUpdated, err := r.buildFetchedTags(tags)
if err != nil {
return updated, err
}

if !updated {
return NoErrAlreadyUpToDate
if tagUpdated {
updated = true
}
return nil

return
}

func (r *Remote) buildFetchedTags(refs memory.ReferenceStorage) error {
updated := false
func (r *Remote) buildFetchedTags(refs memory.ReferenceStorage) (updated bool, err error) {
for _, ref := range refs {
if !ref.IsTag() {
continue
Expand All @@ -574,18 +599,20 @@ func (r *Remote) buildFetchedTags(refs memory.ReferenceStorage) error {
}

if err != nil {
return err
return false, err
}

if err = r.s.SetReference(ref); err != nil {
return err
refUpdated, err := updateReferenceStorerIfNeeded(r.s, ref)
if err != nil {
return updated, err
}

if refUpdated {
updated = true
}
updated = true
}
if !updated {
return NoErrAlreadyUpToDate
}
return nil

return
}

func objectsToPush(commands []*packp.Command) ([]plumbing.Hash, error) {
Expand Down
Loading