diff options
author | Michael Muré <batolettre@gmail.com> | 2018-08-31 13:18:03 +0200 |
---|---|---|
committer | Michael Muré <batolettre@gmail.com> | 2018-08-31 17:22:10 +0200 |
commit | 7397c94d993541b33e555b758ebdb8f61ff33c6c (patch) | |
tree | dbdc52bb6efa03791e5ca84bc8695da5103524d2 /cache/repo_cache.go | |
parent | 116a94401f0d3fbf79f7e20716b1c7b739e33246 (diff) | |
download | git-bug-7397c94d993541b33e555b758ebdb8f61ff33c6c.tar.gz |
make CLI commands use the cache to lock the repo properly
Diffstat (limited to 'cache/repo_cache.go')
-rw-r--r-- | cache/repo_cache.go | 100 |
1 files changed, 99 insertions, 1 deletions
diff --git a/cache/repo_cache.go b/cache/repo_cache.go index c73dbe9f..3d98806c 100644 --- a/cache/repo_cache.go +++ b/cache/repo_cache.go @@ -5,8 +5,10 @@ import ( "encoding/gob" "fmt" "io" + "io/ioutil" "os" "path" + "strconv" "strings" "github.com/MichaelMure/git-bug/bug" @@ -27,8 +29,12 @@ func NewRepoCache(r repository.Repo) (*RepoCache, error) { bugs: make(map[string]*BugCache), } - err := c.loadExcerpts() + err := c.lock() + if err != nil { + return &RepoCache{}, err + } + err = c.loadExcerpts() if err == nil { return c, nil } @@ -42,6 +48,33 @@ func (c *RepoCache) Repository() repository.Repo { return c.repo } +func (c *RepoCache) lock() error { + lockPath := repoLockFilePath(c.repo) + + err := repoIsAvailable(c.repo) + if err != nil { + return err + } + + f, err := os.Create(lockPath) + if err != nil { + return err + } + + pid := fmt.Sprintf("%d", os.Getpid()) + _, err = f.WriteString(pid) + if err != nil { + return err + } + + return f.Close() +} + +func (c *RepoCache) Close() error { + lockPath := repoLockFilePath(c.repo) + return os.Remove(lockPath) +} + // bugUpdated is a callback to trigger when the excerpt of a bug changed, // that is each time a bug is updated func (c *RepoCache) bugUpdated(id string) error { @@ -217,3 +250,68 @@ func (c *RepoCache) Pull(remote string, out io.Writer) error { func (c *RepoCache) Push(remote string) (string, error) { return bug.Push(c.repo, remote) } + +func repoLockFilePath(repo repository.Repo) string { + return path.Join(repo.GetPath(), ".git", "git-bug", lockfile) +} + +// repoIsAvailable check is the given repository is locked by a Cache. +// Note: this is a smart function that will cleanup the lock file if the +// corresponding process is not there anymore. +// If no error is returned, the repo is free to edit. +// @Deprecated +func repoIsAvailable(repo repository.Repo) error { + lockPath := repoLockFilePath(repo) + + // Todo: this leave way for a racey access to the repo between the test + // if the file exist and the actual write. It's probably not a problem in + // practice because using a repository will be done from user interaction + // or in a context where a single instance of git-bug is already guaranteed + // (say, a server with the web UI running). But still, that might be nice to + // have a mutex or something to guard that. + + // Todo: this will fail if somehow the filesystem is shared with another + // computer. Should add a configuration that prevent the cleaning of the + // lock file + + f, err := os.Open(lockPath) + + if err != nil && !os.IsNotExist(err) { + return err + } + + if err == nil { + // lock file already exist + buf, err := ioutil.ReadAll(io.LimitReader(f, 10)) + if err != nil { + return err + } + if len(buf) == 10 { + return fmt.Errorf("The lock file should be < 10 bytes") + } + + pid, err := strconv.Atoi(string(buf)) + if err != nil { + return err + } + + if util.ProcessIsRunning(pid) { + return fmt.Errorf("The repository you want to access is already locked by the process pid %d", pid) + } + + // The lock file is just laying there after a crash, clean it + + fmt.Println("A lock file is present but the corresponding process is not, removing it.") + err = f.Close() + if err != nil { + return err + } + + os.Remove(lockPath) + if err != nil { + return err + } + } + + return nil +} |