Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Adapted non-force fetching with locking - cherry picked #2

Merged
merged 4 commits into from
Sep 19, 2017
Merged
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
6 changes: 6 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
@@ -95,6 +95,9 @@ type PullOptions 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
// Force allows the pull to update a local branch even when the remote
// branch does not descend from it.
Force bool
}

// Validate validates the fields and sets the default values.
@@ -143,6 +146,9 @@ type FetchOptions struct {
// by default is TagFollowing.
Tags TagMode
StatusChan plumbing.StatusChan
// Force allows the fetch to update a local branch even when the remote
// branch does not descend from it.
Force bool
}

// Validate validates the fields and sets the default values.
1 change: 1 addition & 0 deletions plumbing/storer/reference.go
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ var ErrMaxResolveRecursion = errors.New("max. recursion level reached")
// ReferenceStorer is a generic storage of references.
type ReferenceStorer interface {
SetReference(*plumbing.Reference) error
CheckAndSetReference(new, old *plumbing.Reference) error
Reference(plumbing.ReferenceName) (*plumbing.Reference, error)
IterReferences() (ReferenceIter, error)
RemoveReference(plumbing.ReferenceName) error
30 changes: 27 additions & 3 deletions remote.go
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ import (
var (
NoErrAlreadyUpToDate = errors.New("already up-to-date")
ErrDeleteRefNotSupported = errors.New("server does not support delete-refs")
ErrForceNeeded = errors.New("some refs were not updated")
)

// Remote represents a connection to a remote repository.
@@ -301,7 +302,7 @@ func (r *Remote) fetch(ctx context.Context, o *FetchOptions) (storer.ReferenceSt
}
}

updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, o.Tags)
updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, o.Tags, o.Force)
if err != nil {
return nil, err
}
@@ -698,8 +699,11 @@ func (r *Remote) updateLocalReferenceStorage(
specs []config.RefSpec,
fetchedRefs, remoteRefs memory.ReferenceStorage,
tagMode TagMode,
force bool,
) (updated bool, err error) {
isWildcard := true
forceNeeded := false

for _, spec := range specs {
if !spec.IsWildcard() {
isWildcard = false
@@ -714,9 +718,25 @@ func (r *Remote) updateLocalReferenceStorage(
continue
}

new := plumbing.NewHashReference(spec.Dst(ref.Name()), ref.Hash())
localName := spec.Dst(ref.Name())
old, _ := storer.ResolveReference(r.s, localName)
new := plumbing.NewHashReference(localName, ref.Hash())

// If the ref exists locally as a branch and force is not specified,
// only update if the new ref is an ancestor of the old
if old != nil && old.Name().IsBranch() && !force && !spec.IsForceUpdate() {
ff, err := isFastForward(r.s, old.Hash(), new.Hash())
if err != nil {
return updated, err
}

if !ff {
forceNeeded = true
continue
}
}

refUpdated, err := updateReferenceStorerIfNeeded(r.s, new)
refUpdated, err := checkAndUpdateReferenceStorerIfNeeded(r.s, new, old)
if err != nil {
return updated, err
}
@@ -744,6 +764,10 @@ func (r *Remote) updateLocalReferenceStorage(
updated = true
}

if err == nil && forceNeeded {
err = ErrForceNeeded
}

return
}

13 changes: 9 additions & 4 deletions repository.go
Original file line number Diff line number Diff line change
@@ -625,17 +625,17 @@ func (r *Repository) calculateRemoteHeadReference(spec []config.RefSpec,
return refs
}

func updateReferenceStorerIfNeeded(
s storer.ReferenceStorer, r *plumbing.Reference) (updated bool, err error) {

func checkAndUpdateReferenceStorerIfNeeded(
s storer.ReferenceStorer, r, old *plumbing.Reference) (
updated bool, err error) {
p, err := s.Reference(r.Name())
if err != nil && err != plumbing.ErrReferenceNotFound {
return false, err
}

// we use the string method to compare references, is the easiest way
if err == plumbing.ErrReferenceNotFound || r.String() != p.String() {
if err := s.SetReference(r); err != nil {
if err := s.CheckAndSetReference(r, old); err != nil {
return false, err
}

@@ -645,6 +645,11 @@ func updateReferenceStorerIfNeeded(
return false, nil
}

func updateReferenceStorerIfNeeded(
s storer.ReferenceStorer, r *plumbing.Reference) (updated bool, err error) {
return checkAndUpdateReferenceStorerIfNeeded(s, r, nil)
}

// Fetch fetches references along with the objects necessary to complete
// their histories, from the remote named as FetchOptions.RemoteName.
//
66 changes: 57 additions & 9 deletions storage/filesystem/internal/dotgit/dotgit.go
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import (
"bufio"
"errors"
"fmt"
"io"
stdioutil "io/ioutil"
"os"
"strings"
@@ -242,7 +243,39 @@ func (d *DotGit) Object(h plumbing.Hash) (billy.File, error) {
return d.fs.Open(file)
}

func (d *DotGit) SetRef(r *plumbing.Reference) error {
func (d *DotGit) readReferenceFrom(rd io.Reader, name string) (ref *plumbing.Reference, err error) {
b, err := stdioutil.ReadAll(rd)
if err != nil {
return nil, err
}

line := strings.TrimSpace(string(b))
return plumbing.NewReferenceFromStrings(name, line), nil
}

func (d *DotGit) checkReferenceAndTruncate(f billy.File, old *plumbing.Reference) error {
if old == nil {
return nil
}
ref, err := d.readReferenceFrom(f, old.Name().String())
if err != nil {
return err
}
if ref.Hash() != old.Hash() {
return fmt.Errorf("reference has changed concurrently")
}
_, err = f.Seek(0, io.SeekStart)
if err != nil {
return err
}
err = f.Truncate(0)
if err != nil {
return err
}
return nil
}

func (d *DotGit) SetRef(r, old *plumbing.Reference) error {
var content string
switch r.Type() {
case plumbing.SymbolicReference:
@@ -251,13 +284,34 @@ func (d *DotGit) SetRef(r *plumbing.Reference) error {
content = fmt.Sprintln(r.Hash().String())
}

f, err := d.fs.Create(r.Name().String())
// If we are not checking an old ref, just truncate the file.
mode := os.O_RDWR | os.O_CREATE
if old == nil {
mode |= os.O_TRUNC
}

f, err := d.fs.OpenFile(r.Name().String(), mode, 0666)
if err != nil {
return err
}

defer ioutil.CheckClose(f, &err)

// Lock is unlocked by the deferred Close above. This is because Unlock
// does not imply a fsync and thus there would be a race between
// Unlock+Close and other concurrent writers. Adding Sync to go-billy
// could work, but this is better (and avoids superfluous syncs).
err = f.Lock()
if err != nil {
return err
}

// this is a no-op to call even when old is nil.
err = d.checkReferenceAndTruncate(f, old)
if err != nil {
return err
}

_, err = f.Write([]byte(content))
return err
}
@@ -512,13 +566,7 @@ func (d *DotGit) readReferenceFile(path, name string) (ref *plumbing.Reference,
}
defer ioutil.CheckClose(f, &err)

b, err := stdioutil.ReadAll(f)
if err != nil {
return nil, err
}

line := strings.TrimSpace(string(b))
return plumbing.NewReferenceFromStrings(name, line), nil
return d.readReferenceFrom(f, name)
}

// Module return a billy.Filesystem poiting to the module folder
6 changes: 5 additions & 1 deletion storage/filesystem/reference.go
Original file line number Diff line number Diff line change
@@ -11,7 +11,11 @@ type ReferenceStorage struct {
}

func (r *ReferenceStorage) SetReference(ref *plumbing.Reference) error {
return r.dir.SetRef(ref)
return r.dir.SetRef(ref, nil)
}

func (r *ReferenceStorage) CheckAndSetReference(ref, old *plumbing.Reference) error {
return r.dir.SetRef(ref, old)
}

func (r *ReferenceStorage) Reference(n plumbing.ReferenceName) (*plumbing.Reference, error) {
15 changes: 15 additions & 0 deletions storage/memory/storage.go
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import (
)

var ErrUnsupportedObjectType = fmt.Errorf("unsupported object type")
var ErrRefHasChanged = fmt.Errorf("reference has changed concurrently")

// Storage is an implementation of git.Storer that stores data on memory, being
// ephemeral. The use of this storage should be done in controlled envoriments,
@@ -202,6 +203,20 @@ func (r ReferenceStorage) SetReference(ref *plumbing.Reference) error {
return nil
}

func (r ReferenceStorage) CheckAndSetReference(ref, old *plumbing.Reference) error {
if ref != nil {
if old != nil {
tmp := r[ref.Name()]
if tmp != nil && tmp.Hash() != old.Hash() {
return ErrRefHasChanged
}
}
r[ref.Name()] = ref
}

return nil
}

func (r ReferenceStorage) Reference(n plumbing.ReferenceName) (*plumbing.Reference, error) {
ref, ok := r[n]
if !ok {
1 change: 1 addition & 0 deletions worktree.go
Original file line number Diff line number Diff line change
@@ -69,6 +69,7 @@ func (w *Worktree) PullContext(ctx context.Context, o *PullOptions) error {
Depth: o.Depth,
Auth: o.Auth,
Progress: o.Progress,
Force: o.Force,
})

updated := true