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

Commit f79423e

Browse files
committed
Use optionally locking when updating refs
1 parent cec2b59 commit f79423e

6 files changed

Lines changed: 84 additions & 15 deletions

File tree

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ func (r *Remote) updateLocalReferenceStorage(
736736
}
737737
}
738738

739-
refUpdated, err := updateReferenceStorerIfNeeded(r.s, new)
739+
refUpdated, err := checkAndUpdateReferenceStorerIfNeeded(r.s, new, old)
740740
if err != nil {
741741
return updated, err
742742
}

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: 53 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,30 @@ 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+
err = f.Lock()
301+
if err != nil {
302+
return err
303+
}
304+
305+
// this is a no-op to call even when old is nil.
306+
err = d.checkReferenceAndTruncate(f, old)
307+
if err != nil {
308+
return err
309+
}
310+
261311
_, err = f.Write([]byte(content))
262312
return err
263313
}
@@ -512,13 +562,7 @@ func (d *DotGit) readReferenceFile(path, name string) (ref *plumbing.Reference,
512562
}
513563
defer ioutil.CheckClose(f, &err)
514564

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
565+
return d.readReferenceFrom(f, name)
522566
}
523567

524568
// 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 {

0 commit comments

Comments
 (0)