aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bug/bug_test.go4
-rw-r--r--cache/repo_cache.go31
-rw-r--r--cache/repo_cache_bug.go10
-rw-r--r--cache/repo_cache_common.go24
-rw-r--r--cache/repo_cache_identity.go11
-rw-r--r--cache/repo_cache_test.go4
-rw-r--r--commands/env.go2
-rw-r--r--commands/select/select.go22
-rw-r--r--doc/gen_docs.go5
-rw-r--r--go.sum1
-rw-r--r--input/input.go36
-rw-r--r--misc/gen_completion.go14
-rw-r--r--misc/random_bugs/cmd/main.go2
-rw-r--r--repository/git.go9
-rw-r--r--repository/gogit.go152
-rw-r--r--repository/gogit_config.go6
-rw-r--r--repository/gogit_test.go4
-rw-r--r--repository/keyring.go4
-rw-r--r--repository/repo.go5
-rw-r--r--util/lamport/persisted_clock.go29
-rw-r--r--util/lamport/persisted_clock_test.go8
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 {
diff --git a/go.sum b/go.sum
index fe78e7ea..fc9e2392 100644
--- a/go.sum
+++ b/go.sum
@@ -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)