git-bug/cache/cache.go

178 lines
4.0 KiB
Go
Raw Normal View History

package cache
import (
"fmt"
2018-08-12 22:09:30 +03:00
"io"
"io/ioutil"
"os"
"path"
"strconv"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util"
)
const lockfile = "lock"
2018-07-27 02:58:38 +03:00
type Cacher interface {
// RegisterRepository register a named repository. Use this for multi-repo setup
RegisterRepository(ref string, repo repository.Repo) error
// RegisterDefaultRepository register a unnamed repository. Use this for mono-repo setup
RegisterDefaultRepository(repo repository.Repo) error
2018-07-25 22:49:32 +03:00
// ResolveRepo retrieve a repository by name
2018-07-27 02:58:38 +03:00
ResolveRepo(ref string) (RepoCacher, error)
// DefaultRepo retrieve the default repository
2018-07-27 02:58:38 +03:00
DefaultRepo() (RepoCacher, error)
2018-08-02 15:56:50 +03:00
// Close will do anything that is needed to close the cache properly
Close() error
}
2018-07-27 02:58:38 +03:00
type RootCache struct {
repos map[string]RepoCacher
}
2018-07-29 19:11:33 +03:00
func NewCache() RootCache {
return RootCache{
2018-07-27 02:58:38 +03:00
repos: make(map[string]RepoCacher),
}
}
// RegisterRepository register a named repository. Use this for multi-repo setup
func (c *RootCache) RegisterRepository(ref string, repo repository.Repo) error {
err := c.lockRepository(repo)
if err != nil {
return err
}
c.repos[ref] = NewRepoCache(repo)
return nil
}
// RegisterDefaultRepository register a unnamed repository. Use this for mono-repo setup
func (c *RootCache) RegisterDefaultRepository(repo repository.Repo) error {
err := c.lockRepository(repo)
if err != nil {
return err
}
c.repos[""] = NewRepoCache(repo)
return nil
}
func (c *RootCache) lockRepository(repo repository.Repo) error {
lockPath := repoLockFilePath(repo)
err := RepoIsAvailable(repo)
2018-07-30 02:08:45 +03:00
if err != nil {
return err
2018-07-30 02:08:45 +03:00
}
f, err := os.Create(lockPath)
2018-07-30 02:08:45 +03:00
if err != nil {
return err
2018-07-30 02:08:45 +03:00
}
pid := fmt.Sprintf("%d", os.Getpid())
_, err = f.WriteString(pid)
2018-07-30 02:08:45 +03:00
if err != nil {
return err
2018-07-30 02:08:45 +03:00
}
return f.Close()
2018-08-12 22:09:30 +03:00
}
// ResolveRepo retrieve a repository by name
func (c *RootCache) DefaultRepo() (RepoCacher, error) {
if len(c.repos) != 1 {
return nil, fmt.Errorf("repository is not unique")
2018-08-02 15:56:50 +03:00
}
for _, r := range c.repos {
return r, nil
}
panic("unreachable")
}
// DefaultRepo retrieve the default repository
func (c *RootCache) ResolveRepo(ref string) (RepoCacher, error) {
r, ok := c.repos[ref]
if !ok {
return nil, fmt.Errorf("unknown repo")
}
return r, nil
}
func (c *RootCache) Close() error {
for _, cachedRepo := range c.repos {
lockPath := repoLockFilePath(cachedRepo.Repository())
err := os.Remove(lockPath)
if err != nil {
return err
}
}
return nil
}
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
}
func repoLockFilePath(repo repository.Repo) string {
return path.Join(repo.GetPath(), ".git", "git-bug", lockfile)
}