2018-07-25 22:26:25 +03:00
|
|
|
package cache
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2018-08-12 22:09:30 +03:00
|
|
|
"io"
|
2018-08-21 19:46:53 +03:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"strconv"
|
2018-07-25 22:26:25 +03:00
|
|
|
|
|
|
|
"github.com/MichaelMure/git-bug/repository"
|
2018-08-03 00:37:49 +03:00
|
|
|
"github.com/MichaelMure/git-bug/util"
|
2018-07-25 22:26:25 +03:00
|
|
|
)
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
const lockfile = "lock"
|
|
|
|
|
2018-07-27 02:58:38 +03:00
|
|
|
type Cacher interface {
|
2018-08-21 19:46:53 +03:00
|
|
|
// 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
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
// ResolveRepo retrieve a repository by name
|
2018-07-27 02:58:38 +03:00
|
|
|
ResolveRepo(ref string) (RepoCacher, error)
|
2018-08-21 19:46:53 +03:00
|
|
|
// DefaultRepo retrieve the default repository
|
2018-07-27 02:58:38 +03:00
|
|
|
DefaultRepo() (RepoCacher, error)
|
2018-08-02 15:56:50 +03:00
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
// Close will do anything that is needed to close the cache properly
|
2018-08-02 17:35:13 +03:00
|
|
|
Close() error
|
2018-07-25 22:26:25 +03:00
|
|
|
}
|
|
|
|
|
2018-07-27 02:58:38 +03:00
|
|
|
type RootCache struct {
|
|
|
|
repos map[string]RepoCacher
|
2018-07-25 22:26:25 +03:00
|
|
|
}
|
|
|
|
|
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),
|
2018-07-25 22:26:25 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
// 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)
|
2018-07-25 22:26:25 +03:00
|
|
|
if err != nil {
|
2018-08-21 19:46:53 +03:00
|
|
|
return err
|
2018-07-25 22:26:25 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
c.repos[ref] = NewRepoCache(repo)
|
|
|
|
return nil
|
2018-07-25 22:26:25 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
// RegisterDefaultRepository register a unnamed repository. Use this for mono-repo setup
|
|
|
|
func (c *RootCache) RegisterDefaultRepository(repo repository.Repo) error {
|
|
|
|
err := c.lockRepository(repo)
|
2018-07-25 22:26:25 +03:00
|
|
|
if err != nil {
|
2018-08-21 19:46:53 +03:00
|
|
|
return err
|
2018-07-25 22:26:25 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
c.repos[""] = NewRepoCache(repo)
|
|
|
|
return nil
|
2018-07-25 22:26:25 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
func (c *RootCache) lockRepository(repo repository.Repo) error {
|
|
|
|
lockPath := repoLockFilePath(repo)
|
2018-08-03 00:37:49 +03:00
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
err := RepoIsAvailable(repo)
|
2018-07-30 02:08:45 +03:00
|
|
|
if err != nil {
|
2018-08-21 19:46:53 +03:00
|
|
|
return err
|
2018-07-30 02:08:45 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
f, err := os.Create(lockPath)
|
2018-07-30 02:08:45 +03:00
|
|
|
if err != nil {
|
2018-08-21 19:46:53 +03:00
|
|
|
return err
|
2018-07-30 02:08:45 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
pid := fmt.Sprintf("%d", os.Getpid())
|
|
|
|
_, err = f.WriteString(pid)
|
2018-07-30 02:08:45 +03:00
|
|
|
if err != nil {
|
2018-08-21 19:46:53 +03:00
|
|
|
return err
|
2018-07-30 02:08:45 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
return f.Close()
|
2018-08-12 22:09:30 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +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
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
for _, r := range c.repos {
|
|
|
|
return r, nil
|
2018-08-01 22:57:12 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
panic("unreachable")
|
2018-08-03 00:37:49 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
// 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")
|
2018-08-01 22:57:12 +03:00
|
|
|
}
|
2018-08-21 19:46:53 +03:00
|
|
|
return r, nil
|
2018-08-01 22:57:12 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
func (c *RootCache) Close() error {
|
|
|
|
for _, cachedRepo := range c.repos {
|
|
|
|
lockPath := repoLockFilePath(cachedRepo.Repository())
|
|
|
|
err := os.Remove(lockPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-08-01 22:57:12 +03:00
|
|
|
}
|
2018-08-02 17:35:13 +03:00
|
|
|
return nil
|
2018-08-01 22:57:12 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
func RepoIsAvailable(repo repository.Repo) error {
|
|
|
|
lockPath := repoLockFilePath(repo)
|
2018-08-01 22:57:12 +03:00
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
// 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.
|
2018-08-01 22:57:12 +03:00
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
// 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
|
2018-08-01 22:57:12 +03:00
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
f, err := os.Open(lockPath)
|
2018-08-01 22:57:12 +03:00
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
if err != nil && !os.IsNotExist(err) {
|
2018-08-02 17:35:13 +03:00
|
|
|
return err
|
2018-08-01 22:57:12 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
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")
|
|
|
|
}
|
2018-08-01 22:57:12 +03:00
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
pid, err := strconv.Atoi(string(buf))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-08-01 22:57:12 +03:00
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
if util.ProcessIsRunning(pid) {
|
|
|
|
return fmt.Errorf("The repository you want to access is already locked by the process pid %d", pid)
|
|
|
|
}
|
2018-08-01 22:57:12 +03:00
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
// The lock file is just laying there after a crash, clean it
|
2018-08-01 22:57:12 +03:00
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
fmt.Println("A lock file is present but the corresponding process is not, removing it.")
|
|
|
|
err = f.Close()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-08-01 22:57:12 +03:00
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
os.Remove(lockPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2018-07-25 22:26:25 +03:00
|
|
|
|
2018-08-02 17:35:13 +03:00
|
|
|
return nil
|
2018-07-25 22:26:25 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 19:46:53 +03:00
|
|
|
func repoLockFilePath(repo repository.Repo) string {
|
|
|
|
return path.Join(repo.GetPath(), ".git", "git-bug", lockfile)
|
2018-08-09 15:45:02 +03:00
|
|
|
}
|