diff options
-rw-r--r-- | bug/bug_test.go | 4 | ||||
-rw-r--r-- | cache/repo_cache.go | 31 | ||||
-rw-r--r-- | cache/repo_cache_bug.go | 10 | ||||
-rw-r--r-- | cache/repo_cache_common.go | 24 | ||||
-rw-r--r-- | cache/repo_cache_identity.go | 11 | ||||
-rw-r--r-- | cache/repo_cache_test.go | 4 | ||||
-rw-r--r-- | commands/env.go | 2 | ||||
-rw-r--r-- | commands/select/select.go | 22 | ||||
-rw-r--r-- | doc/gen_docs.go | 5 | ||||
-rw-r--r-- | go.sum | 1 | ||||
-rw-r--r-- | input/input.go | 36 | ||||
-rw-r--r-- | misc/gen_completion.go | 14 | ||||
-rw-r--r-- | misc/random_bugs/cmd/main.go | 2 | ||||
-rw-r--r-- | repository/git.go | 9 | ||||
-rw-r--r-- | repository/gogit.go | 152 | ||||
-rw-r--r-- | repository/gogit_config.go | 6 | ||||
-rw-r--r-- | repository/gogit_test.go | 4 | ||||
-rw-r--r-- | repository/keyring.go | 4 | ||||
-rw-r--r-- | repository/repo.go | 5 | ||||
-rw-r--r-- | util/lamport/persisted_clock.go | 29 | ||||
-rw-r--r-- | util/lamport/persisted_clock_test.go | 8 |
21 files changed, 191 insertions, 192 deletions
diff --git a/bug/bug_test.go b/bug/bug_test.go index 05f6e617..6363f4e9 100644 --- a/bug/bug_test.go +++ b/bug/bug_test.go @@ -130,10 +130,10 @@ func TestBugRemove(t *testing.T) { remoteB := repository.CreateGoGitTestRepo(true) defer repository.CleanupTestRepos(repo, remoteA, remoteB) - err := repo.AddRemote("remoteA", "file://"+remoteA.GetPath()) + err := repo.AddRemote("remoteA", remoteA.GetLocalRemote()) require.NoError(t, err) - err = repo.AddRemote("remoteB", "file://"+remoteB.GetPath()) + err = repo.AddRemote("remoteB", remoteB.GetLocalRemote()) require.NoError(t, err) // generate a bunch of bugs diff --git a/cache/repo_cache.go b/cache/repo_cache.go index 8bce3d8c..a08744cc 100644 --- a/cache/repo_cache.go +++ b/cache/repo_cache.go @@ -5,8 +5,6 @@ import ( "io" "io/ioutil" "os" - "path" - "path/filepath" "strconv" "sync" @@ -133,25 +131,18 @@ func (c *RepoCache) write() error { } func (c *RepoCache) lock() error { - lockPath := repoLockFilePath(c.repo) - err := repoIsAvailable(c.repo) if err != nil { return err } - err = os.MkdirAll(filepath.Dir(lockPath), 0777) - if err != nil { - return err - } - - f, err := os.Create(lockPath) + f, err := c.repo.LocalStorage().Create(lockfile) if err != nil { return err } pid := fmt.Sprintf("%d", os.Getpid()) - _, err = f.WriteString(pid) + _, err = f.Write([]byte(pid)) if err != nil { return err } @@ -175,11 +166,12 @@ func (c *RepoCache) Close() error { c.searchCache = nil } - lockPath := repoLockFilePath(c.repo) - return os.Remove(lockPath) + return c.repo.LocalStorage().Remove(lockfile) } func (c *RepoCache) buildCache() error { + // TODO: make that parallel + c.muBug.Lock() defer c.muBug.Unlock() c.muIdentity.Lock() @@ -230,17 +222,11 @@ func (c *RepoCache) buildCache() error { return nil } -func repoLockFilePath(repo repository.Repo) string { - return path.Join(repo.GetPath(), "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. -func repoIsAvailable(repo repository.Repo) error { - lockPath := repoLockFilePath(repo) - +func repoIsAvailable(repo repository.RepoStorage) error { // 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 @@ -252,8 +238,7 @@ func repoIsAvailable(repo repository.Repo) error { // computer. Should add a configuration that prevent the cleaning of the // lock file - f, err := os.Open(lockPath) - + f, err := repo.LocalStorage().Open(lockfile) if err != nil && !os.IsNotExist(err) { return err } @@ -285,7 +270,7 @@ func repoIsAvailable(repo repository.Repo) error { return err } - err = os.Remove(lockPath) + err = repo.LocalStorage().Remove(lockfile) if err != nil { return err } diff --git a/cache/repo_cache_bug.go b/cache/repo_cache_bug.go index d57e1bce..f540e51b 100644 --- a/cache/repo_cache_bug.go +++ b/cache/repo_cache_bug.go @@ -5,8 +5,6 @@ import ( "encoding/gob" "errors" "fmt" - "os" - "path" "sort" "strings" "time" @@ -25,10 +23,6 @@ const ( var errBugNotInCache = errors.New("bug missing from cache") -func bugCacheFilePath(repo repository.Repo) string { - return path.Join(repo.GetPath(), "git-bug", bugCacheFile) -} - func searchCacheDirPath(repo repository.Repo) string { return path.Join(repo.GetPath(), "git-bug", searchCacheDir) } @@ -65,7 +59,7 @@ func (c *RepoCache) loadBugCache() error { c.muBug.Lock() defer c.muBug.Unlock() - f, err := os.Open(bugCacheFilePath(c.repo)) + f, err := c.repo.LocalStorage().Open(bugCacheFile) if err != nil { return err } @@ -148,7 +142,7 @@ func (c *RepoCache) writeBugCache() error { return err } - f, err := os.Create(bugCacheFilePath(c.repo)) + f, err := c.repo.LocalStorage().Create(bugCacheFile) if err != nil { return err } diff --git a/cache/repo_cache_common.go b/cache/repo_cache_common.go index 95e2f7bb..5dc19d22 100644 --- a/cache/repo_cache_common.go +++ b/cache/repo_cache_common.go @@ -3,6 +3,7 @@ package cache import ( "fmt" + "github.com/go-git/go-billy/v5" "github.com/pkg/errors" "github.com/MichaelMure/git-bug/bug" @@ -30,13 +31,19 @@ func (c *RepoCache) AnyConfig() repository.ConfigRead { return c.repo.AnyConfig() } +// Keyring give access to a user-wide storage for secrets func (c *RepoCache) Keyring() repository.Keyring { return c.repo.Keyring() } -// GetPath returns the path to the repo. -func (c *RepoCache) GetPath() string { - return c.repo.GetPath() +// GetUserName returns the name the the user has used to configure git +func (c *RepoCache) GetUserName() (string, error) { + return c.repo.GetUserName() +} + +// GetUserEmail returns the email address that the user has used to configure git. +func (c *RepoCache) GetUserEmail() (string, error) { + return c.repo.GetUserEmail() } // GetCoreEditor returns the name of the editor that the user has used to configure git. @@ -49,14 +56,9 @@ func (c *RepoCache) GetRemotes() (map[string]string, error) { return c.repo.GetRemotes() } -// GetUserName returns the name the the user has used to configure git -func (c *RepoCache) GetUserName() (string, error) { - return c.repo.GetUserName() -} - -// GetUserEmail returns the email address that the user has used to configure git. -func (c *RepoCache) GetUserEmail() (string, error) { - return c.repo.GetUserEmail() +// LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug +func (c *RepoCache) LocalStorage() billy.Filesystem { + return c.repo.LocalStorage() } // ReadData will attempt to read arbitrary data from the given hash diff --git a/cache/repo_cache_identity.go b/cache/repo_cache_identity.go index 8957d4aa..8df5b810 100644 --- a/cache/repo_cache_identity.go +++ b/cache/repo_cache_identity.go @@ -4,20 +4,13 @@ import ( "bytes" "encoding/gob" "fmt" - "os" - "path" "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/repository" ) const identityCacheFile = "identity-cache" -func identityCacheFilePath(repo repository.Repo) string { - return path.Join(repo.GetPath(), "git-bug", identityCacheFile) -} - // identityUpdated is a callback to trigger when the excerpt of an identity // changed, that is each time an identity is updated func (c *RepoCache) identityUpdated(id entity.Id) error { @@ -41,7 +34,7 @@ func (c *RepoCache) loadIdentityCache() error { c.muIdentity.Lock() defer c.muIdentity.Unlock() - f, err := os.Open(identityCacheFilePath(c.repo)) + f, err := c.repo.LocalStorage().Open(identityCacheFile) if err != nil { return err } @@ -88,7 +81,7 @@ func (c *RepoCache) writeIdentityCache() error { return err } - f, err := os.Create(identityCacheFilePath(c.repo)) + f, err := c.repo.LocalStorage().Create(identityCacheFile) if err != nil { return err } diff --git a/cache/repo_cache_test.go b/cache/repo_cache_test.go index 0037c7bb..1c5c41d2 100644 --- a/cache/repo_cache_test.go +++ b/cache/repo_cache_test.go @@ -167,10 +167,10 @@ func TestRemove(t *testing.T) { remoteB := repository.CreateGoGitTestRepo(true) defer repository.CleanupTestRepos(repo, remoteA, remoteB) - err := repo.AddRemote("remoteA", "file://"+remoteA.GetPath()) + err := repo.AddRemote("remoteA", remoteA.GetLocalRemote()) require.NoError(t, err) - err = repo.AddRemote("remoteB", "file://"+remoteB.GetPath()) + err = repo.AddRemote("remoteB", remoteB.GetLocalRemote()) require.NoError(t, err) repoCache, err := NewRepoCache(repo) diff --git a/commands/env.go b/commands/env.go index 5658342d..8d12c5bf 100644 --- a/commands/env.go +++ b/commands/env.go @@ -54,7 +54,7 @@ func loadRepo(env *Env) func(*cobra.Command, []string) error { return fmt.Errorf("unable to get the current working directory: %q", err) } - env.repo, err = repository.NewGoGitRepo(cwd, []repository.ClockLoader{bug.ClockLoader}) + env.repo, err = repository.OpenGoGitRepo(cwd, []repository.ClockLoader{bug.ClockLoader}) if err == repository.ErrNotARepo { return fmt.Errorf("%s must be run from within a git repo", rootCommandName) } diff --git a/commands/select/select.go b/commands/select/select.go index cf861fcc..3df74387 100644 --- a/commands/select/select.go +++ b/commands/select/select.go @@ -5,14 +5,12 @@ import ( "io" "io/ioutil" "os" - "path" "github.com/pkg/errors" "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/entity" - "github.com/MichaelMure/git-bug/repository" ) const selectFile = "select" @@ -71,14 +69,12 @@ func ResolveBug(repo *cache.RepoCache, args []string) (*cache.BugCache, []string // Select will select a bug for future use func Select(repo *cache.RepoCache, id entity.Id) error { - selectPath := selectFilePath(repo) - - f, err := os.OpenFile(selectPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + f, err := repo.LocalStorage().OpenFile(selectFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { return err } - _, err = f.WriteString(id.String()) + _, err = f.Write([]byte(id.String())) if err != nil { return err } @@ -88,15 +84,11 @@ func Select(repo *cache.RepoCache, id entity.Id) error { // Clear will clear the selected bug, if any func Clear(repo *cache.RepoCache) error { - selectPath := selectFilePath(repo) - - return os.Remove(selectPath) + return repo.LocalStorage().Remove(selectFile) } func selected(repo *cache.RepoCache) (*cache.BugCache, error) { - selectPath := selectFilePath(repo) - - f, err := os.Open(selectPath) + f, err := repo.LocalStorage().Open(selectFile) if err != nil { if os.IsNotExist(err) { return nil, nil @@ -115,7 +107,7 @@ func selected(repo *cache.RepoCache) (*cache.BugCache, error) { id := entity.Id(buf) if err := id.Validate(); err != nil { - err = os.Remove(selectPath) + err = repo.LocalStorage().Remove(selectFile) if err != nil { return nil, errors.Wrap(err, "error while removing invalid select file") } @@ -135,7 +127,3 @@ func selected(repo *cache.RepoCache) (*cache.BugCache, error) { return b, nil } - -func selectFilePath(repo repository.RepoCommon) string { - return path.Join(repo.GetPath(), "git-bug", selectFile) -} diff --git a/doc/gen_docs.go b/doc/gen_docs.go index 8bb596fe..482abd8e 100644 --- a/doc/gen_docs.go +++ b/doc/gen_docs.go @@ -3,7 +3,6 @@ package main import ( "fmt" "os" - "path" "path/filepath" "sync" "time" @@ -42,7 +41,7 @@ func main() { func genManPage(root *cobra.Command) error { cwd, _ := os.Getwd() - dir := path.Join(cwd, "doc", "man") + dir := filepath.Join(cwd, "doc", "man") // fixed date to avoid having to commit each month date := time.Date(2019, 4, 1, 12, 0, 0, 0, time.UTC) @@ -69,7 +68,7 @@ func genManPage(root *cobra.Command) error { func genMarkdown(root *cobra.Command) error { cwd, _ := os.Getwd() - dir := path.Join(cwd, "doc", "md") + dir := filepath.Join(cwd, "doc", "md") files, err := filepath.Glob(dir + "/*.md") if err != nil { @@ -229,6 +229,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= diff --git a/input/input.go b/input/input.go index ca787ceb..ef76f53e 100644 --- a/input/input.go +++ b/input/input.go @@ -11,9 +11,11 @@ import ( "io/ioutil" "os" "os/exec" + "path/filepath" "strings" "github.com/MichaelMure/git-bug/repository" + "github.com/go-git/go-billy/v5/util" "github.com/pkg/errors" ) @@ -35,7 +37,7 @@ const bugTitleCommentTemplate = `%s%s // BugCreateEditorInput will open the default editor in the terminal with a // template for the user to fill. The file is then processed to extract title // and message. -func BugCreateEditorInput(repo repository.RepoCommon, preTitle string, preMessage string) (string, string, error) { +func BugCreateEditorInput(repo repository.RepoCommonStorage, preTitle string, preMessage string) (string, string, error) { if preMessage != "" { preMessage = "\n\n" + preMessage } @@ -101,10 +103,10 @@ const bugCommentTemplate = `%s // BugCommentEditorInput will open the default editor in the terminal with a // template for the user to fill. The file is then processed to extract a comment. -func BugCommentEditorInput(repo repository.RepoCommon, preMessage string) (string, error) { +func BugCommentEditorInput(repo repository.RepoCommonStorage, preMessage string) (string, error) { template := fmt.Sprintf(bugCommentTemplate, preMessage) - raw, err := launchEditorWithTemplate(repo, messageFilename, template) + raw, err := launchEditorWithTemplate(repo, messageFilename, template) if err != nil { return "", err } @@ -152,10 +154,10 @@ const bugTitleTemplate = `%s // BugTitleEditorInput will open the default editor in the terminal with a // template for the user to fill. The file is then processed to extract a title. -func BugTitleEditorInput(repo repository.RepoCommon, preTitle string) (string, error) { +func BugTitleEditorInput(repo repository.RepoCommonStorage, preTitle string) (string, error) { template := fmt.Sprintf(bugTitleTemplate, preTitle) - raw, err := launchEditorWithTemplate(repo, messageFilename, template) + raw, err := launchEditorWithTemplate(repo, messageFilename, template) if err != nil { return "", err } @@ -212,10 +214,10 @@ const queryTemplate = `%s // QueryEditorInput will open the default editor in the terminal with a // template for the user to fill. The file is then processed to extract a query. -func QueryEditorInput(repo repository.RepoCommon, preQuery string) (string, error) { +func QueryEditorInput(repo repository.RepoCommonStorage, preQuery string) (string, error) { template := fmt.Sprintf(queryTemplate, preQuery) - raw, err := launchEditorWithTemplate(repo, messageFilename, template) + raw, err := launchEditorWithTemplate(repo, messageFilename, template) if err != nil { return "", err } @@ -238,11 +240,8 @@ func QueryEditorInput(repo repository.RepoCommon, preQuery string) (string, erro // launchEditorWithTemplate will launch an editor as launchEditor do, but with a // provided template. -func launchEditorWithTemplate(repo repository.RepoCommon, fileName string, template string) (string, error) { - path := fmt.Sprintf("%s/%s", repo.GetPath(), fileName) - - err := ioutil.WriteFile(path, []byte(template), 0644) - +func launchEditorWithTemplate(repo repository.RepoCommonStorage, fileName string, template string) (string, error) { + err := util.WriteFile(repo.LocalStorage(), fileName, []byte(template), 0644) if err != nil { return "", err } @@ -254,20 +253,25 @@ func launchEditorWithTemplate(repo repository.RepoCommon, fileName string, templ // method blocks until the editor command has returned. // // The specified filename should be a temporary file and provided as a relative path -// from the repo (e.g. "FILENAME" will be converted to "[<reporoot>/].git/FILENAME"). This file +// from the repo (e.g. "FILENAME" will be converted to "[<reporoot>/].git/git-bug/FILENAME"). This file // will be deleted after the editor is closed and its contents have been read. // // This method returns the text that was read from the temporary file, or // an error if any step in the process failed. -func launchEditor(repo repository.RepoCommon, fileName string) (string, error) { - path := fmt.Sprintf("%s/%s", repo.GetPath(), fileName) - defer os.Remove(path) +func launchEditor(repo repository.RepoCommonStorage, fileName string) (string, error) { + defer repo.LocalStorage().Remove(fileName) editor, err := repo.GetCoreEditor() if err != nil { return "", fmt.Errorf("Unable to detect default git editor: %v\n", err) } + repo.LocalStorage().Root() + + // bypass the interface but that's ok: we need that because we are communicating + // the absolute path to an external program + path := filepath.Join(repo.LocalStorage().Root(), fileName) + cmd, err := startInlineCommand(editor, path) if err != nil { // Running the editor directly did not work. This might mean that diff --git a/misc/gen_completion.go b/misc/gen_completion.go index af00fa64..c073e67e 100644 --- a/misc/gen_completion.go +++ b/misc/gen_completion.go @@ -3,7 +3,7 @@ package main import ( "fmt" "os" - "path" + "path/filepath" "sync" "github.com/spf13/cobra" @@ -41,24 +41,24 @@ func main() { func genBash(root *cobra.Command) error { cwd, _ := os.Getwd() - dir := path.Join(cwd, "misc", "bash_completion", "git-bug") + dir := filepath.Join(cwd, "misc", "bash_completion", "git-bug") return root.GenBashCompletionFile(dir) } func genFish(root *cobra.Command) error { cwd, _ := os.Getwd() - dir := path.Join(cwd, "misc", "fish_completion", "git-bug") + dir := filepath.Join(cwd, "misc", "fish_completion", "git-bug") return root.GenFishCompletionFile(dir, true) } func genPowerShell(root *cobra.Command) error { cwd, _ := os.Getwd() - filepath := path.Join(cwd, "misc", "powershell_completion", "git-bug") - return root.GenPowerShellCompletionFile(filepath) + path := filepath.Join(cwd, "misc", "powershell_completion", "git-bug") + return root.GenPowerShellCompletionFile(path) } func genZsh(root *cobra.Command) error { cwd, _ := os.Getwd() - filepath := path.Join(cwd, "misc", "zsh_completion", "git-bug") - return root.GenZshCompletionFile(filepath) + path := filepath.Join(cwd, "misc", "zsh_completion", "git-bug") + return root.GenZshCompletionFile(path) } diff --git a/misc/random_bugs/cmd/main.go b/misc/random_bugs/cmd/main.go index 3127b4aa..010ae6d1 100644 --- a/misc/random_bugs/cmd/main.go +++ b/misc/random_bugs/cmd/main.go @@ -20,7 +20,7 @@ func main() { bug.ClockLoader, } - repo, err := repository.NewGoGitRepo(dir, loaders) + repo, err := repository.OpenGoGitRepo(dir, loaders) if err != nil { panic(err) } diff --git a/repository/git.go b/repository/git.go index 8c319285..e67e12f5 100644 --- a/repository/git.go +++ b/repository/git.go @@ -5,7 +5,6 @@ import ( "bytes" "fmt" "os" - "path" "path/filepath" "strings" "sync" @@ -379,9 +378,7 @@ func (repo *GitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { repo.clocksMutex.Lock() defer repo.clocksMutex.Unlock() - p := path.Join(repo.path, clockPath, name+"-clock") - - c, err = lamport.NewPersistedClock(p) + c, err = lamport.NewPersistedClock(repo.LocalStorage(), name+"-clock") if err != nil { return nil, err } @@ -398,9 +395,7 @@ func (repo *GitRepo) getClock(name string) (lamport.Clock, error) { return c, nil } - p := path.Join(repo.path, clockPath, name+"-clock") - - c, err := lamport.LoadPersistedClock(p) + c, err := lamport.LoadPersistedClock(repo.LocalStorage(), name+"-clock") if err == nil { repo.clocks[name] = c return c, nil diff --git a/repository/gogit.go b/repository/gogit.go index 874885db..65c2ab54 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -6,20 +6,22 @@ import ( "io/ioutil" "os" "os/exec" - stdpath "path" "path/filepath" "sort" "strings" "sync" "time" + "github.com/99designs/keyring" "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/osfs" gogit "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/storage/memory" "github.com/MichaelMure/git-bug/util/lamport" ) @@ -28,16 +30,18 @@ var _ ClockedRepo = &GoGitRepo{} var _ TestedRepo = &GoGitRepo{} type GoGitRepo struct { - r *gogit.Repository - path string + r *gogit.Repository + path string + isMemory bool clocksMutex sync.Mutex clocks map[string]lamport.Clock - keyring Keyring + keyring Keyring + localStorage billy.Filesystem } -func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { +func OpenGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { path, err := detectGitPath(path) if err != nil { return nil, err @@ -54,10 +58,12 @@ func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { } repo := &GoGitRepo{ - r: r, - path: path, - clocks: make(map[string]lamport.Clock), - keyring: k, + r: r, + path: path, + isMemory: false, + clocks: make(map[string]lamport.Clock), + keyring: k, + localStorage: osfs.New(filepath.Join(path, "git-bug")), } for _, loader := range clockLoaders { @@ -79,6 +85,69 @@ func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { return repo, nil } +// InitGoGitRepo create a new empty git repo at the given path +func InitGoGitRepo(path string) (*GoGitRepo, error) { + r, err := gogit.PlainInit(path, false) + if err != nil { + return nil, err + } + + k, err := defaultKeyring() + if err != nil { + return nil, err + } + + return &GoGitRepo{ + r: r, + path: filepath.Join(path, ".git"), + isMemory: false, + clocks: make(map[string]lamport.Clock), + keyring: k, + localStorage: osfs.New(filepath.Join(path, ".git", "git-bug")), + }, nil +} + +// InitBareGoGitRepo create a new --bare empty git repo at the given path +func InitBareGoGitRepo(path string) (*GoGitRepo, error) { + r, err := gogit.PlainInit(path, true) + if err != nil { + return nil, err + } + + k, err := defaultKeyring() + if err != nil { + return nil, err + } + + return &GoGitRepo{ + r: r, + path: path, + isMemory: false, + clocks: make(map[string]lamport.Clock), + keyring: k, + localStorage: osfs.New(filepath.Join(path, "git-bug")), + }, nil +} + +func InitMemoryGoGitRepo() (*GoGitRepo, error) { + r, err := gogit.Init(memory.NewStorage(), nil) + if err != nil { + return nil, err + } + + k := keyring.NewArrayKeyring(nil) + + repo := &GoGitRepo{ + r: r, + isMemory: true, + clocks: make(map[string]lamport.Clock), + keyring: k, + localStorage: memfs.New(), + } + + return repo, nil +} + func detectGitPath(path string) (string, error) { // normalize the path path, err := filepath.Abs(path) @@ -87,12 +156,12 @@ func detectGitPath(path string) (string, error) { } for { - fi, err := os.Stat(stdpath.Join(path, ".git")) + fi, err := os.Stat(filepath.Join(path, ".git")) if err == nil { if !fi.IsDir() { return "", fmt.Errorf(".git exist but is not a directory") } - return stdpath.Join(path, ".git"), nil + return filepath.Join(path, ".git"), nil } if !os.IsNotExist(err) { // unknown error @@ -120,7 +189,7 @@ func isGitDir(path string) (bool, error) { markers := []string{"HEAD", "objects", "refs"} for _, marker := range markers { - _, err := os.Stat(stdpath.Join(path, marker)) + _, err := os.Stat(filepath.Join(path, marker)) if err == nil { continue } @@ -135,46 +204,6 @@ func isGitDir(path string) (bool, error) { return true, nil } -// InitGoGitRepo create a new empty git repo at the given path -func InitGoGitRepo(path string) (*GoGitRepo, error) { - r, err := gogit.PlainInit(path, false) - if err != nil { - return nil, err - } - - k, err := defaultKeyring() - if err != nil { - return nil, err - } - - return &GoGitRepo{ - r: r, - path: path + "/.git", - clocks: make(map[string]lamport.Clock), - keyring: k, - }, nil -} - -// InitBareGoGitRepo create a new --bare empty git repo at the given path -func InitBareGoGitRepo(path string) (*GoGitRepo, error) { - r, err := gogit.PlainInit(path, true) - if err != nil { - return nil, err - } - - k, err := defaultKeyring() - if err != nil { - return nil, err - } - - return &GoGitRepo{ - r: r, - path: path, - clocks: make(map[string]lamport.Clock), - keyring: k, - }, nil -} - // LocalConfig give access to the repository scoped configuration func (repo *GoGitRepo) LocalConfig() Config { return newGoGitLocalConfig(repo.r) @@ -182,10 +211,7 @@ func (repo *GoGitRepo) LocalConfig() Config { // GlobalConfig give access to the global scoped configuration func (repo *GoGitRepo) GlobalConfig() Config { - // TODO: replace that with go-git native implementation once it's supported - // see: https://github.com/go-git/go-git - // see: https://github.com/src-d/go-git/issues/760 - return newGoGitGlobalConfig(repo.r) + return newGoGitGlobalConfig() } // AnyConfig give access to a merged local/global configuration @@ -270,7 +296,7 @@ func (repo *GoGitRepo) GetRemotes() (map[string]string, error) { // LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug func (repo *GoGitRepo) LocalStorage() billy.Filesystem { - return osfs.New(repo.path) + return repo.localStorage } // FetchRefs fetch git refs from a remote @@ -614,9 +640,7 @@ func (repo *GoGitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { repo.clocksMutex.Lock() defer repo.clocksMutex.Unlock() - p := stdpath.Join(repo.path, clockPath, name+"-clock") - - c, err = lamport.NewPersistedClock(p) + c, err = lamport.NewPersistedClock(repo.localStorage, name+"-clock") if err != nil { return nil, err } @@ -633,9 +657,7 @@ func (repo *GoGitRepo) getClock(name string) (lamport.Clock, error) { return c, nil } - p := stdpath.Join(repo.path, clockPath, name+"-clock") - - c, err := lamport.LoadPersistedClock(p) + c, err := lamport.LoadPersistedClock(repo.localStorage, name+"-clock") if err == nil { repo.clocks[name] = c return c, nil @@ -664,6 +686,10 @@ func (repo *GoGitRepo) GetLocalRemote() string { // EraseFromDisk delete this repository entirely from the disk func (repo *GoGitRepo) EraseFromDisk() error { + if repo.isMemory { + return nil + } + path := filepath.Clean(strings.TrimSuffix(repo.path, string(filepath.Separator)+".git")) // fmt.Println("Cleaning repo:", path) diff --git a/repository/gogit_config.go b/repository/gogit_config.go index 2f9a4cc3..ba61adca 100644 --- a/repository/gogit_config.go +++ b/repository/gogit_config.go @@ -24,7 +24,11 @@ func newGoGitLocalConfig(repo *gogit.Repository) *goGitConfig { } } -func newGoGitGlobalConfig(repo *gogit.Repository) *goGitConfig { +func newGoGitGlobalConfig() *goGitConfig { + // TODO: replace that with go-git native implementation once it's supported + // see: https://github.com/go-git/go-git + // see: https://github.com/src-d/go-git/issues/760 + return &goGitConfig{ ConfigRead: &goGitConfigReader{getConfig: func() (*config.Config, error) { return config.LoadConfig(config.GlobalScope) diff --git a/repository/gogit_test.go b/repository/gogit_test.go index a1f67664..a2bb49b9 100644 --- a/repository/gogit_test.go +++ b/repository/gogit_test.go @@ -19,7 +19,7 @@ func TestNewGoGitRepo(t *testing.T) { _, err = InitGoGitRepo(plainRoot) require.NoError(t, err) - plainGitDir := path.Join(plainRoot, ".git") + plainGitDir := filepath.Join(plainRoot, ".git") // Bare bareRoot, err := ioutil.TempDir("", "") @@ -52,7 +52,7 @@ func TestNewGoGitRepo(t *testing.T) { } for i, tc := range tests { - r, err := NewGoGitRepo(tc.inPath, nil) + r, err := OpenGoGitRepo(tc.inPath, nil) if tc.err { require.Error(t, err, i) diff --git a/repository/keyring.go b/repository/keyring.go index f690b0b3..4cb3c9ff 100644 --- a/repository/keyring.go +++ b/repository/keyring.go @@ -2,7 +2,7 @@ package repository import ( "os" - "path" + "path/filepath" "github.com/99designs/keyring" ) @@ -38,7 +38,7 @@ func defaultKeyring() (Keyring, error) { ServiceName: "git-bug", // Fallback encrypted file - FileDir: path.Join(ucd, "git-bug", "keyring"), + FileDir: filepath.Join(ucd, "git-bug", "keyring"), // As we write the file in the user's config directory, this file should already be protected by the OS against // other user's access. We actually don't terribly need to protect it further and a password prompt across all // UI's would be a pain. Therefore we use here a constant password so the file will be unreadable by generic file diff --git a/repository/repo.go b/repository/repo.go index d34995f9..d8fe44e6 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -25,6 +25,11 @@ type Repo interface { RepoStorage } +type RepoCommonStorage interface { + RepoCommon + RepoStorage +} + // ClockedRepo is a Repo that also has Lamport clocks type ClockedRepo interface { Repo diff --git a/util/lamport/persisted_clock.go b/util/lamport/persisted_clock.go index e70b01ef..b9246f73 100644 --- a/util/lamport/persisted_clock.go +++ b/util/lamport/persisted_clock.go @@ -5,30 +5,28 @@ import ( "fmt" "io/ioutil" "os" - "path/filepath" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/util" ) var ErrClockNotExist = errors.New("clock doesn't exist") type PersistedClock struct { *MemClock + root billy.Filesystem filePath string } // NewPersistedClock create a new persisted Lamport clock -func NewPersistedClock(filePath string) (*PersistedClock, error) { +func NewPersistedClock(root billy.Filesystem, filePath string) (*PersistedClock, error) { clock := &PersistedClock{ MemClock: NewMemClock(), + root: root, filePath: filePath, } - dir := filepath.Dir(filePath) - err := os.MkdirAll(dir, 0777) - if err != nil { - return nil, err - } - - err = clock.Write() + err := clock.Write() if err != nil { return nil, err } @@ -37,8 +35,9 @@ func NewPersistedClock(filePath string) (*PersistedClock, error) { } // LoadPersistedClock load a persisted Lamport clock from a file -func LoadPersistedClock(filePath string) (*PersistedClock, error) { +func LoadPersistedClock(root billy.Filesystem, filePath string) (*PersistedClock, error) { clock := &PersistedClock{ + root: root, filePath: filePath, } @@ -71,13 +70,19 @@ func (pc *PersistedClock) Witness(time Time) error { } func (pc *PersistedClock) read() error { - content, err := ioutil.ReadFile(pc.filePath) + f, err := pc.root.Open(pc.filePath) if os.IsNotExist(err) { return ErrClockNotExist } if err != nil { return err } + defer f.Close() + + content, err := ioutil.ReadAll(f) + if err != nil { + return err + } var value uint64 n, err := fmt.Sscanf(string(content), "%d", &value) @@ -96,5 +101,5 @@ func (pc *PersistedClock) read() error { func (pc *PersistedClock) Write() error { data := []byte(fmt.Sprintf("%d", pc.counter)) - return ioutil.WriteFile(pc.filePath, data, 0644) + return util.WriteFile(pc.root, pc.filePath, data, 0644) } diff --git a/util/lamport/persisted_clock_test.go b/util/lamport/persisted_clock_test.go index aacec3bf..9ef690da 100644 --- a/util/lamport/persisted_clock_test.go +++ b/util/lamport/persisted_clock_test.go @@ -1,18 +1,16 @@ package lamport import ( - "io/ioutil" - "path" "testing" + "github.com/go-git/go-billy/v5/memfs" "github.com/stretchr/testify/require" ) func TestPersistedClock(t *testing.T) { - dir, err := ioutil.TempDir("", "") - require.NoError(t, err) + root := memfs.New() - c, err := NewPersistedClock(path.Join(dir, "test-clock")) + c, err := NewPersistedClock(root, "test-clock") require.NoError(t, err) testClock(t, c) |