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

Commit a7537ef

Browse files
authored
Merge pull request #2 from keybase/taruti/490-fetch-force-K
Adapted non-force fetching with locking - cherry picked
2 parents adb810a + 04852d2 commit a7537ef

File tree

8 files changed

+121
-17
lines changed

8 files changed

+121
-17
lines changed

options.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ type PullOptions struct {
9595
// stored, if nil nothing is stored and the capability (if supported)
9696
// no-progress, is sent to the server to avoid send this information.
9797
Progress sideband.Progress
98+
// Force allows the pull to update a local branch even when the remote
99+
// branch does not descend from it.
100+
Force bool
98101
}
99102

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

148154
// Validate validates the fields and sets the default values.

plumbing/storer/reference.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ var ErrMaxResolveRecursion = errors.New("max. recursion level reached")
1616
// ReferenceStorer is a generic storage of references.
1717
type ReferenceStorer interface {
1818
SetReference(*plumbing.Reference) error
19+
CheckAndSetReference(new, old *plumbing.Reference) error
1920
Reference(plumbing.ReferenceName) (*plumbing.Reference, error)
2021
IterReferences() (ReferenceIter, error)
2122
RemoveReference(plumbing.ReferenceName) error

remote.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
var (
2626
NoErrAlreadyUpToDate = errors.New("already up-to-date")
2727
ErrDeleteRefNotSupported = errors.New("server does not support delete-refs")
28+
ErrForceNeeded = errors.New("some refs were not updated")
2829
)
2930

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

304-
updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, o.Tags)
305+
updated, err := r.updateLocalReferenceStorage(o.RefSpecs, refs, remoteRefs, o.Tags, o.Force)
305306
if err != nil {
306307
return nil, err
307308
}
@@ -698,8 +699,11 @@ func (r *Remote) updateLocalReferenceStorage(
698699
specs []config.RefSpec,
699700
fetchedRefs, remoteRefs memory.ReferenceStorage,
700701
tagMode TagMode,
702+
force bool,
701703
) (updated bool, err error) {
702704
isWildcard := true
705+
forceNeeded := false
706+
703707
for _, spec := range specs {
704708
if !spec.IsWildcard() {
705709
isWildcard = false
@@ -714,9 +718,25 @@ func (r *Remote) updateLocalReferenceStorage(
714718
continue
715719
}
716720

717-
new := plumbing.NewHashReference(spec.Dst(ref.Name()), ref.Hash())
721+
localName := spec.Dst(ref.Name())
722+
old, _ := storer.ResolveReference(r.s, localName)
723+
new := plumbing.NewHashReference(localName, ref.Hash())
724+
725+
// If the ref exists locally as a branch and force is not specified,
726+
// only update if the new ref is an ancestor of the old
727+
if old != nil && old.Name().IsBranch() && !force && !spec.IsForceUpdate() {
728+
ff, err := isFastForward(r.s, old.Hash(), new.Hash())
729+
if err != nil {
730+
return updated, err
731+
}
732+
733+
if !ff {
734+
forceNeeded = true
735+
continue
736+
}
737+
}
718738

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

767+
if err == nil && forceNeeded {
768+
err = ErrForceNeeded
769+
}
770+
747771
return
748772
}
749773

repository.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -625,17 +625,17 @@ func (r *Repository) calculateRemoteHeadReference(spec []config.RefSpec,
625625
return refs
626626
}
627627

628-
func updateReferenceStorerIfNeeded(
629-
s storer.ReferenceStorer, r *plumbing.Reference) (updated bool, err error) {
630-
628+
func checkAndUpdateReferenceStorerIfNeeded(
629+
s storer.ReferenceStorer, r, old *plumbing.Reference) (
630+
updated bool, err error) {
631631
p, err := s.Reference(r.Name())
632632
if err != nil && err != plumbing.ErrReferenceNotFound {
633633
return false, err
634634
}
635635

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

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

648+
func updateReferenceStorerIfNeeded(
649+
s storer.ReferenceStorer, r *plumbing.Reference) (updated bool, err error) {
650+
return checkAndUpdateReferenceStorerIfNeeded(s, r, nil)
651+
}
652+
648653
// Fetch fetches references along with the objects necessary to complete
649654
// their histories, from the remote named as FetchOptions.RemoteName.
650655
//

storage/filesystem/internal/dotgit/dotgit.go

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"bufio"
66
"errors"
77
"fmt"
8+
"io"
89
stdioutil "io/ioutil"
910
"os"
1011
"strings"
@@ -242,7 +243,39 @@ func (d *DotGit) Object(h plumbing.Hash) (billy.File, error) {
242243
return d.fs.Open(file)
243244
}
244245

245-
func (d *DotGit) SetRef(r *plumbing.Reference) error {
246+
func (d *DotGit) readReferenceFrom(rd io.Reader, name string) (ref *plumbing.Reference, err error) {
247+
b, err := stdioutil.ReadAll(rd)
248+
if err != nil {
249+
return nil, err
250+
}
251+
252+
line := strings.TrimSpace(string(b))
253+
return plumbing.NewReferenceFromStrings(name, line), nil
254+
}
255+
256+
func (d *DotGit) checkReferenceAndTruncate(f billy.File, old *plumbing.Reference) error {
257+
if old == nil {
258+
return nil
259+
}
260+
ref, err := d.readReferenceFrom(f, old.Name().String())
261+
if err != nil {
262+
return err
263+
}
264+
if ref.Hash() != old.Hash() {
265+
return fmt.Errorf("reference has changed concurrently")
266+
}
267+
_, err = f.Seek(0, io.SeekStart)
268+
if err != nil {
269+
return err
270+
}
271+
err = f.Truncate(0)
272+
if err != nil {
273+
return err
274+
}
275+
return nil
276+
}
277+
278+
func (d *DotGit) SetRef(r, old *plumbing.Reference) error {
246279
var content string
247280
switch r.Type() {
248281
case plumbing.SymbolicReference:
@@ -251,13 +284,34 @@ func (d *DotGit) SetRef(r *plumbing.Reference) error {
251284
content = fmt.Sprintln(r.Hash().String())
252285
}
253286

254-
f, err := d.fs.Create(r.Name().String())
287+
// If we are not checking an old ref, just truncate the file.
288+
mode := os.O_RDWR | os.O_CREATE
289+
if old == nil {
290+
mode |= os.O_TRUNC
291+
}
292+
293+
f, err := d.fs.OpenFile(r.Name().String(), mode, 0666)
255294
if err != nil {
256295
return err
257296
}
258297

259298
defer ioutil.CheckClose(f, &err)
260299

300+
// Lock is unlocked by the deferred Close above. This is because Unlock
301+
// does not imply a fsync and thus there would be a race between
302+
// Unlock+Close and other concurrent writers. Adding Sync to go-billy
303+
// could work, but this is better (and avoids superfluous syncs).
304+
err = f.Lock()
305+
if err != nil {
306+
return err
307+
}
308+
309+
// this is a no-op to call even when old is nil.
310+
err = d.checkReferenceAndTruncate(f, old)
311+
if err != nil {
312+
return err
313+
}
314+
261315
_, err = f.Write([]byte(content))
262316
return err
263317
}
@@ -512,13 +566,7 @@ func (d *DotGit) readReferenceFile(path, name string) (ref *plumbing.Reference,
512566
}
513567
defer ioutil.CheckClose(f, &err)
514568

515-
b, err := stdioutil.ReadAll(f)
516-
if err != nil {
517-
return nil, err
518-
}
519-
520-
line := strings.TrimSpace(string(b))
521-
return plumbing.NewReferenceFromStrings(name, line), nil
569+
return d.readReferenceFrom(f, name)
522570
}
523571

524572
// Module return a billy.Filesystem poiting to the module folder

storage/filesystem/reference.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ type ReferenceStorage struct {
1111
}
1212

1313
func (r *ReferenceStorage) SetReference(ref *plumbing.Reference) error {
14-
return r.dir.SetRef(ref)
14+
return r.dir.SetRef(ref, nil)
15+
}
16+
17+
func (r *ReferenceStorage) CheckAndSetReference(ref, old *plumbing.Reference) error {
18+
return r.dir.SetRef(ref, old)
1519
}
1620

1721
func (r *ReferenceStorage) Reference(n plumbing.ReferenceName) (*plumbing.Reference, error) {

storage/memory/storage.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
)
1313

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

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

206+
func (r ReferenceStorage) CheckAndSetReference(ref, old *plumbing.Reference) error {
207+
if ref != nil {
208+
if old != nil {
209+
tmp := r[ref.Name()]
210+
if tmp != nil && tmp.Hash() != old.Hash() {
211+
return ErrRefHasChanged
212+
}
213+
}
214+
r[ref.Name()] = ref
215+
}
216+
217+
return nil
218+
}
219+
205220
func (r ReferenceStorage) Reference(n plumbing.ReferenceName) (*plumbing.Reference, error) {
206221
ref, ok := r[n]
207222
if !ok {

worktree.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func (w *Worktree) PullContext(ctx context.Context, o *PullOptions) error {
6969
Depth: o.Depth,
7070
Auth: o.Auth,
7171
Progress: o.Progress,
72+
Force: o.Force,
7273
})
7374

7475
updated := true

0 commit comments

Comments
 (0)