| package dotgit |
| |
| import ( |
| "fmt" |
| "os" |
| |
| "gopkg.in/src-d/go-git.v4/plumbing" |
| "gopkg.in/src-d/go-git.v4/utils/ioutil" |
| |
| "gopkg.in/src-d/go-billy.v4" |
| ) |
| |
| func (d *DotGit) setRef(fileName, content string, old *plumbing.Reference) (err error) { |
| if billy.CapabilityCheck(d.fs, billy.ReadAndWriteCapability) { |
| return d.setRefRwfs(fileName, content, old) |
| } |
| |
| return d.setRefNorwfs(fileName, content, old) |
| } |
| |
| func (d *DotGit) setRefRwfs(fileName, content string, old *plumbing.Reference) (err error) { |
| // 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(fileName, 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 |
| } |
| |
| // There are some filesystems that don't support opening files in RDWD mode. |
| // In these filesystems the standard SetRef function can not be used as it |
| // reads the reference file to check that it's not modified before updating it. |
| // |
| // This version of the function writes the reference without extra checks |
| // making it compatible with these simple filesystems. This is usually not |
| // a problem as they should be accessed by only one process at a time. |
| func (d *DotGit) setRefNorwfs(fileName, content string, old *plumbing.Reference) error { |
| _, err := d.fs.Stat(fileName) |
| if err == nil && old != nil { |
| fRead, err := d.fs.Open(fileName) |
| if err != nil { |
| return err |
| } |
| |
| ref, err := d.readReferenceFrom(fRead, old.Name().String()) |
| fRead.Close() |
| |
| if err != nil { |
| return err |
| } |
| |
| if ref.Hash() != old.Hash() { |
| return fmt.Errorf("reference has changed concurrently") |
| } |
| } |
| |
| f, err := d.fs.Create(fileName) |
| if err != nil { |
| return err |
| } |
| |
| defer f.Close() |
| |
| _, err = f.Write([]byte(content)) |
| return err |
| } |