diff options
author | Michael Muré <batolettre@gmail.com> | 2020-12-08 15:17:22 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-08 15:17:22 +0100 |
commit | bf476f98d1656850e2f3fd349adea504007a8313 (patch) | |
tree | 595f3875590c89fe1c5a30e2e732f8aee9b35361 | |
parent | 54d123c6753d053df8400beea316e13690c851f4 (diff) | |
parent | 8128bb79b0db9023a98c356e4e173d846057c577 (diff) | |
download | git-bug-bf476f98d1656850e2f3fd349adea504007a8313.tar.gz |
Merge pull request #510 from MichaelMure/repo-rework
Repo rework
33 files changed, 620 insertions, 317 deletions
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 00000000..3ce22f3a --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,41 @@ +name: Go build and test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + build: + + strategy: + matrix: + go-version: [1.13.x, 1.14.x, 1.15.x] + platform: [ubuntu-latest, macos-latest, windows-latest] + + runs-on: ${{ matrix.platform }} + + steps: + + - name: Set up Go ${{ matrix.node-version }} + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + + - name: Check out code + uses: actions/checkout@v2 + + - name: Build + run: make + + - name: Test + run: make test + env: + GITHUB_TEST_USER: ${{ secrets._GITHUB_TEST_USER }} + GITHUB_TOKEN_ADMIN: ${{ secrets._GITHUB_TOKEN_ADMIN }} + GITHUB_TOKEN_PRIVATE: ${{ secrets._GITHUB_TOKEN_PRIVATE }} + GITHUB_TOKEN_PUBLIC: ${{ secrets._GITHUB_TOKEN_PUBLIC }} + GITLAB_API_TOKEN: ${{ secrets.GITLAB_API_TOKEN }} + GITLAB_PROJECT_ID: ${{ secrets.GITLAB_PROJECT_ID }} diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 00000000..d7a69c8e --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,38 @@ +name: Node.js build and test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +defaults: + run: + working-directory: webui + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x, 14.x] + + steps: + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + - name: Check out code + uses: actions/checkout@v2 + + - name: Install + run: make install + + - name: Build + run: make build + + - name: Test + run: make test diff --git a/api/graphql/resolvers/repo.go b/api/graphql/resolvers/repo.go index 5d96428e..c2163cbe 100644 --- a/api/graphql/resolvers/repo.go +++ b/api/graphql/resolvers/repo.go @@ -41,7 +41,10 @@ func (repoResolver) AllBugs(_ context.Context, obj *models.Repository, after *st } // Simply pass a []string with the ids to the pagination algorithm - source := obj.Repo.QueryBugs(q) + source, err := obj.Repo.QueryBugs(q) + if err != nil { + return nil, err + } // The edger create a custom edge holding just the id edger := func(id entity.Id, offset int) connections.Edge { diff --git a/bridge/github/config_test.go b/bridge/github/config_test.go index eecb1aa8..01907435 100644 --- a/bridge/github/config_test.go +++ b/bridge/github/config_test.go @@ -103,6 +103,9 @@ func TestValidateUsername(t *testing.T) { if env := os.Getenv("TRAVIS"); env == "true" { t.Skip("Travis environment: avoiding non authenticated requests") } + if _, has := os.LookupEnv("CI"); has { + t.Skip("Github action environment: avoiding non authenticated requests") + } tests := []struct { name string 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..b5b9ee54 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" @@ -15,7 +13,6 @@ import ( "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/process" - "github.com/blevesearch/bleve" ) // 1: original format @@ -57,8 +54,6 @@ type RepoCache struct { muBug sync.RWMutex // excerpt of bugs data for all bugs bugExcerpts map[entity.Id]*BugExcerpt - // searchable cache of all bugs - searchCache bleve.Index // bug loaded in memory bugs map[entity.Id]*BugCache // loadedBugs is an LRU cache that records which bugs the cache has loaded in @@ -133,25 +128,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 } @@ -170,16 +158,17 @@ func (c *RepoCache) Close() error { c.bugs = make(map[entity.Id]*BugCache) c.bugExcerpts = nil - if c.searchCache != nil { - c.searchCache.Close() - c.searchCache = nil + err := c.repo.Close() + if err != nil { + return err } - 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() @@ -207,9 +196,10 @@ func (c *RepoCache) buildCache() error { allBugs := bug.ReadAllLocal(c.repo) - err := c.createBleveIndex() + // wipe the index just to be sure + err := c.repo.ClearBleveIndex("bug") if err != nil { - return fmt.Errorf("Unable to create search cache. Error: %v", err) + return err } for b := range allBugs { @@ -230,17 +220,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 +236,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 +268,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..1701f66d 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,14 +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) -} - // 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 entity.Id) error { @@ -65,7 +55,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 } @@ -88,14 +78,13 @@ func (c *RepoCache) loadBugCache() error { c.bugExcerpts = aux.Excerpts - blevePath := searchCacheDirPath(c.repo) - searchCache, err := bleve.Open(blevePath) + index, err := c.repo.GetBleveIndex("bug") if err != nil { - return fmt.Errorf("Unable to open search cache. Error: %v", err) + return err } - c.searchCache = searchCache - count, err := c.searchCache.DocCount() + // simple heuristic to detect a mismatch between the index and the bugs + count, err := index.DocCount() if err != nil { return err } @@ -106,26 +95,6 @@ func (c *RepoCache) loadBugCache() error { return nil } -func (c *RepoCache) createBleveIndex() error { - blevePath := searchCacheDirPath(c.repo) - - _ = os.RemoveAll(blevePath) - - mapping := bleve.NewIndexMapping() - mapping.DefaultAnalyzer = "en" - - dir := searchCacheDirPath(c.repo) - - bleveIndex, err := bleve.New(dir, mapping) - if err != nil { - return err - } - - c.searchCache = bleveIndex - - return nil -} - // write will serialize on disk the bug cache file func (c *RepoCache) writeBugCache() error { c.muBug.RLock() @@ -148,7 +117,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 } @@ -293,12 +262,12 @@ func (c *RepoCache) resolveBugMatcher(f func(*BugExcerpt) bool) (entity.Id, erro } // QueryBugs return the id of all Bug matching the given Query -func (c *RepoCache) QueryBugs(q *query.Query) []entity.Id { +func (c *RepoCache) QueryBugs(q *query.Query) ([]entity.Id, error) { c.muBug.RLock() defer c.muBug.RUnlock() if q == nil { - return c.AllBugsIds() + return c.AllBugsIds(), nil } matcher := compileMatcher(q.Filters) @@ -319,9 +288,15 @@ func (c *RepoCache) QueryBugs(q *query.Query) []entity.Id { bleveQuery := bleve.NewQueryStringQuery(strings.Join(terms, " ")) bleveSearch := bleve.NewSearchRequest(bleveQuery) - searchResults, err := c.searchCache.Search(bleveSearch) + + index, err := c.repo.GetBleveIndex("bug") if err != nil { - panic("bleve search failed") + return nil, err + } + + searchResults, err := index.Search(bleveSearch) + if err != nil { + return nil, err } for _, hit := range searchResults.Hits { @@ -347,7 +322,7 @@ func (c *RepoCache) QueryBugs(q *query.Query) []entity.Id { case query.OrderByEdit: sorter = BugsByEditTime(filtered) default: - panic("missing sort type") + return nil, errors.New("missing sort type") } switch q.OrderDirection { @@ -356,7 +331,7 @@ func (c *RepoCache) QueryBugs(q *query.Query) []entity.Id { case query.OrderDescending: sorter = sort.Reverse(sorter) default: - panic("missing sort direction") + return nil, errors.New("missing sort direction") } sort.Sort(sorter) @@ -367,7 +342,7 @@ func (c *RepoCache) QueryBugs(q *query.Query) []entity.Id { result[i] = val.Id } - return result + return result, nil } // AllBugsIds return all known bug ids @@ -510,7 +485,12 @@ func (c *RepoCache) addBugToSearchIndex(snap *bug.Snapshot) error { searchableBug.Text = append(searchableBug.Text, snap.Title) - err := c.searchCache.Index(snap.Id().String(), searchableBug) + index, err := c.repo.GetBleveIndex("bug") + if err != nil { + return err + } + + err = index.Index(snap.Id().String(), searchableBug) 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..bd06e84d 100644 --- a/cache/repo_cache_test.go +++ b/cache/repo_cache_test.go @@ -73,7 +73,9 @@ func TestCache(t *testing.T) { // Querying q, err := query.Parse("status:open author:descartes sort:edit-asc") require.NoError(t, err) - require.Len(t, cache.QueryBugs(q), 2) + res, err := cache.QueryBugs(q) + require.NoError(t, err) + require.Len(t, res, 2) // Close require.NoError(t, cache.Close()) @@ -167,10 +169,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/ls.go b/commands/ls.go index f6d654b1..327fd37f 100644 --- a/commands/ls.go +++ b/commands/ls.go @@ -110,7 +110,10 @@ func runLs(env *Env, opts lsOptions, args []string) error { return err } - allIds := env.backend.QueryBugs(q) + allIds, err := env.backend.QueryBugs(q) + if err != nil { + return err + } bugExcerpt := make([]*cache.BugExcerpt, len(allIds)) for i, id := range allIds { 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 { @@ -20,6 +20,7 @@ require ( github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect github.com/fatih/color v1.10.0 + github.com/go-git/go-billy/v5 v5.0.0 github.com/go-git/go-git/v5 v5.2.0 github.com/golang/protobuf v1.4.3 // indirect github.com/gorilla/mux v1.8.0 @@ -50,15 +50,10 @@ github.com/RoaringBitmap/roaring v0.4.23 h1:gpyfd12QohbqhFO4NVDUdoPOCXsyahYRQhIN github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195 h1:c4mLfegoDw6OhSJXTd2jUEQgZUQuJWtocudb97Qn9EM= github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= @@ -66,14 +61,12 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986 h1:QvIfX96O11qjX1Zr3hKkG0dI12JBRBGABWffyZ1GI60= github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986/go.mod h1:1QikxFaPhe2frKeKvEwZEIGia3haiOxOUXKinrv17mA= github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0= github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= @@ -178,12 +171,10 @@ github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+ne github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= @@ -194,9 +185,10 @@ github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy v4.2.0+incompatible h1:Z6QtVXd5tjxUtcODLugkJg4WaZnGg13CD8qB9pr+7q0= +github.com/go-git/go-billy v4.2.0+incompatible/go.mod h1:hedUGslB3n31bx5SW9KMjV/t0CUKnrapjVG9fT7xKX4= github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI= github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs= @@ -286,7 +278,6 @@ github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= @@ -317,7 +308,6 @@ github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGzny github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= @@ -333,7 +323,6 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -343,7 +332,6 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -382,7 +370,6 @@ github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -728,7 +715,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -786,7 +772,6 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= diff --git a/identity/identity_test.go b/identity/identity_test.go index baf933c8..82e58b01 100644 --- a/identity/identity_test.go +++ b/identity/identity_test.go @@ -273,10 +273,10 @@ func TestIdentityRemove(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 an identity for testing 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 504cdd89..bc9d8772 100644 --- a/repository/git.go +++ b/repository/git.go @@ -4,10 +4,15 @@ package repository import ( "bytes" "fmt" - "path" + "os" + "path/filepath" "strings" "sync" + "github.com/blevesearch/bleve" + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/osfs" + "github.com/MichaelMure/git-bug/util/lamport" ) @@ -26,12 +31,15 @@ type GitRepo struct { clocksMutex sync.Mutex clocks map[string]lamport.Clock + indexesMutex sync.Mutex + indexes map[string]bleve.Index + keyring Keyring } -// NewGitRepo determines if the given working directory is inside of a git repository, +// OpenGitRepo determines if the given working directory is inside of a git repository, // and returns the corresponding GitRepo instance if it is. -func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) { +func OpenGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) { k, err := defaultKeyring() if err != nil { return nil, err @@ -41,6 +49,7 @@ func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) { gitCli: gitCli{path: path}, path: path, clocks: make(map[string]lamport.Clock), + indexes: make(map[string]bleve.Index), keyring: k, } @@ -80,9 +89,10 @@ func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) { // InitGitRepo create a new empty git repo at the given path func InitGitRepo(path string) (*GitRepo, error) { repo := &GitRepo{ - gitCli: gitCli{path: path}, - path: path + "/.git", - clocks: make(map[string]lamport.Clock), + gitCli: gitCli{path: path}, + path: path + "/.git", + clocks: make(map[string]lamport.Clock), + indexes: make(map[string]bleve.Index), } _, err := repo.runGitCommand("init", path) @@ -96,9 +106,10 @@ func InitGitRepo(path string) (*GitRepo, error) { // InitBareGitRepo create a new --bare empty git repo at the given path func InitBareGitRepo(path string) (*GitRepo, error) { repo := &GitRepo{ - gitCli: gitCli{path: path}, - path: path, - clocks: make(map[string]lamport.Clock), + gitCli: gitCli{path: path}, + path: path, + clocks: make(map[string]lamport.Clock), + indexes: make(map[string]bleve.Index), } _, err := repo.runGitCommand("init", "--bare", path) @@ -109,6 +120,17 @@ func InitBareGitRepo(path string) (*GitRepo, error) { return repo, nil } +func (repo *GitRepo) Close() error { + var firstErr error + for _, index := range repo.indexes { + err := index.Close() + if err != nil && firstErr == nil { + firstErr = err + } + } + return firstErr +} + // LocalConfig give access to the repository scoped configuration func (repo *GitRepo) LocalConfig() Config { return newGitConfig(repo.gitCli, false) @@ -174,6 +196,63 @@ func (repo *GitRepo) GetRemotes() (map[string]string, error) { return remotes, nil } +// LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug +func (repo *GitRepo) LocalStorage() billy.Filesystem { + return osfs.New(repo.path) +} + +// GetBleveIndex return a bleve.Index that can be used to index documents +func (repo *GitRepo) GetBleveIndex(name string) (bleve.Index, error) { + repo.indexesMutex.Lock() + defer repo.indexesMutex.Unlock() + + if index, ok := repo.indexes[name]; ok { + return index, nil + } + + path := filepath.Join(repo.path, "indexes", name) + + index, err := bleve.Open(path) + if err == nil { + repo.indexes[name] = index + return index, nil + } + + err = os.MkdirAll(path, os.ModeDir) + if err != nil { + return nil, err + } + + mapping := bleve.NewIndexMapping() + mapping.DefaultAnalyzer = "en" + + index, err = bleve.New(path, mapping) + if err != nil { + return nil, err + } + + repo.indexes[name] = index + + return index, nil +} + +// ClearBleveIndex will wipe the given index +func (repo *GitRepo) ClearBleveIndex(name string) error { + repo.indexesMutex.Lock() + defer repo.indexesMutex.Unlock() + + path := filepath.Join(repo.path, "indexes", name) + + err := os.RemoveAll(path) + if err != nil { + return err + } + + delete(repo.indexes, name) + + return nil +} + // FetchRefs fetch git refs from a remote func (repo *GitRepo) FetchRefs(remote, refSpec string) (string, error) { stdout, err := repo.runGitCommand("fetch", remote, refSpec) @@ -358,6 +437,9 @@ func (repo *GitRepo) GetTreeHash(commit Hash) (Hash, error) { // GetOrCreateClock return a Lamport clock stored in the Repo. // If the clock doesn't exist, it's created. func (repo *GitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { + repo.clocksMutex.Lock() + defer repo.clocksMutex.Unlock() + c, err := repo.getClock(name) if err == nil { return c, nil @@ -366,12 +448,7 @@ func (repo *GitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { return nil, err } - 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 } @@ -381,16 +458,11 @@ func (repo *GitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { } func (repo *GitRepo) getClock(name string) (lamport.Clock, error) { - repo.clocksMutex.Lock() - defer repo.clocksMutex.Unlock() - if c, ok := repo.clocks[name]; ok { 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 @@ -408,3 +480,21 @@ func (repo *GitRepo) AddRemote(name string, url string) error { return err } + +// GetLocalRemote return the URL to use to add this repo as a local remote +func (repo *GitRepo) GetLocalRemote() string { + return repo.path +} + +// EraseFromDisk delete this repository entirely from the disk +func (repo *GitRepo) EraseFromDisk() error { + err := repo.Close() + if err != nil { + return err + } + + path := filepath.Clean(strings.TrimSuffix(repo.path, string(filepath.Separator)+".git")) + + // fmt.Println("Cleaning repo:", path) + return os.RemoveAll(path) +} diff --git a/repository/git_testing.go b/repository/git_testing.go index 874cc86c..2168d53e 100644 --- a/repository/git_testing.go +++ b/repository/git_testing.go @@ -48,14 +48,12 @@ func SetupReposAndRemote() (repoA, repoB, remote TestedRepo) { repoB = CreateGoGitTestRepo(false) remote = CreateGoGitTestRepo(true) - remoteAddr := "file://" + remote.GetPath() - - err := repoA.AddRemote("origin", remoteAddr) + err := repoA.AddRemote("origin", remote.GetLocalRemote()) if err != nil { log.Fatal(err) } - err = repoB.AddRemote("origin", remoteAddr) + err = repoB.AddRemote("origin", remote.GetLocalRemote()) if err != nil { log.Fatal(err) } diff --git a/repository/gogit.go b/repository/gogit.go index c59409d4..bdac259d 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -6,13 +6,15 @@ import ( "io/ioutil" "os" "os/exec" - stdpath "path" "path/filepath" "sort" "strings" "sync" "time" + "github.com/blevesearch/bleve" + "github.com/go-git/go-billy/v5" + "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" @@ -23,6 +25,7 @@ import ( ) var _ ClockedRepo = &GoGitRepo{} +var _ TestedRepo = &GoGitRepo{} type GoGitRepo struct { r *gogit.Repository @@ -31,10 +34,15 @@ type GoGitRepo struct { clocksMutex sync.Mutex clocks map[string]lamport.Clock - keyring Keyring + indexesMutex sync.Mutex + indexes map[string]bleve.Index + + keyring Keyring + localStorage billy.Filesystem } -func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { +// OpenGoGitRepo open an already existing repo at the given path +func OpenGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { path, err := detectGitPath(path) if err != nil { return nil, err @@ -51,10 +59,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, + clocks: make(map[string]lamport.Clock), + indexes: make(map[string]bleve.Index), + keyring: k, + localStorage: osfs.New(filepath.Join(path, "git-bug")), } for _, loader := range clockLoaders { @@ -76,6 +86,50 @@ 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"), + clocks: make(map[string]lamport.Clock), + indexes: make(map[string]bleve.Index), + 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, + clocks: make(map[string]lamport.Clock), + indexes: make(map[string]bleve.Index), + keyring: k, + localStorage: osfs.New(filepath.Join(path, "git-bug")), + }, nil +} + func detectGitPath(path string) (string, error) { // normalize the path path, err := filepath.Abs(path) @@ -84,12 +138,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 @@ -117,7 +171,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 } @@ -132,44 +186,15 @@ 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 +func (repo *GoGitRepo) Close() error { + var firstErr error + for _, index := range repo.indexes { + err := index.Close() + if err != nil && firstErr == nil { + firstErr = err + } } - - return &GoGitRepo{ - r: r, - path: path, - clocks: make(map[string]lamport.Clock), - keyring: k, - }, nil + return firstErr } // LocalConfig give access to the repository scoped configuration @@ -179,10 +204,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 @@ -195,11 +217,6 @@ func (repo *GoGitRepo) Keyring() Keyring { return repo.keyring } -// GetPath returns the path to the repo. -func (repo *GoGitRepo) GetPath() string { - return repo.path -} - // GetUserName returns the name the the user has used to configure git func (repo *GoGitRepo) GetUserName() (string, error) { return repo.AnyConfig().ReadString("user.name") @@ -270,6 +287,69 @@ func (repo *GoGitRepo) GetRemotes() (map[string]string, error) { return result, nil } +// LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug +func (repo *GoGitRepo) LocalStorage() billy.Filesystem { + return repo.localStorage +} + +// GetBleveIndex return a bleve.Index that can be used to index documents +func (repo *GoGitRepo) GetBleveIndex(name string) (bleve.Index, error) { + repo.indexesMutex.Lock() + defer repo.indexesMutex.Unlock() + + if index, ok := repo.indexes[name]; ok { + return index, nil + } + + path := filepath.Join(repo.path, "git-bug", "indexes", name) + + index, err := bleve.Open(path) + if err == nil { + repo.indexes[name] = index + return index, nil + } + + err = os.MkdirAll(path, os.ModePerm) + if err != nil { + return nil, err + } + + mapping := bleve.NewIndexMapping() + mapping.DefaultAnalyzer = "en" + + index, err = bleve.New(path, mapping) + if err != nil { + return nil, err + } + + repo.indexes[name] = index + + return index, nil +} + +// ClearBleveIndex will wipe the given index +func (repo *GoGitRepo) ClearBleveIndex(name string) error { + repo.indexesMutex.Lock() + defer repo.indexesMutex.Unlock() + + path := filepath.Join(repo.path, "indexes", name) + + err := os.RemoveAll(path) + if err != nil { + return err + } + + if index, ok := repo.indexes[name]; ok { + err = index.Close() + if err != nil { + return err + } + delete(repo.indexes, name) + } + + return nil +} + // FetchRefs fetch git refs from a remote func (repo *GoGitRepo) FetchRefs(remote string, refSpec string) (string, error) { buf := bytes.NewBuffer(nil) @@ -600,6 +680,9 @@ func (repo *GoGitRepo) ListCommits(ref string) ([]Hash, error) { // GetOrCreateClock return a Lamport clock stored in the Repo. // If the clock doesn't exist, it's created. func (repo *GoGitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { + repo.clocksMutex.Lock() + defer repo.clocksMutex.Unlock() + c, err := repo.getClock(name) if err == nil { return c, nil @@ -608,12 +691,7 @@ func (repo *GoGitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { return nil, err } - 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 } @@ -623,16 +701,11 @@ func (repo *GoGitRepo) GetOrCreateClock(name string) (lamport.Clock, error) { } func (repo *GoGitRepo) getClock(name string) (lamport.Clock, error) { - repo.clocksMutex.Lock() - defer repo.clocksMutex.Unlock() - if c, ok := repo.clocks[name]; ok { 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 @@ -653,3 +726,21 @@ func (repo *GoGitRepo) AddRemote(name string, url string) error { return err } + +// GetLocalRemote return the URL to use to add this repo as a local remote +func (repo *GoGitRepo) GetLocalRemote() string { + return repo.path +} + +// EraseFromDisk delete this repository entirely from the disk +func (repo *GoGitRepo) EraseFromDisk() error { + err := repo.Close() + if err != nil { + return err + } + + path := filepath.Clean(strings.TrimSuffix(repo.path, string(filepath.Separator)+".git")) + + // fmt.Println("Cleaning repo:", path) + return os.RemoveAll(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 fba990d3..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,13 +52,13 @@ 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) } else { require.NoError(t, err, i) - assert.Equal(t, filepath.ToSlash(tc.outPath), filepath.ToSlash(r.GetPath()), i) + assert.Equal(t, filepath.ToSlash(tc.outPath), filepath.ToSlash(r.path), i) } } } diff --git a/repository/gogit_testing.go b/repository/gogit_testing.go index f20ff6be..a8bff41e 100644 --- a/repository/gogit_testing.go +++ b/repository/gogit_testing.go @@ -42,14 +42,12 @@ func SetupGoGitReposAndRemote() (repoA, repoB, remote TestedRepo) { repoB = CreateGoGitTestRepo(false) remote = CreateGoGitTestRepo(true) - remoteAddr := "file://" + remote.GetPath() - - err := repoA.AddRemote("origin", remoteAddr) + err := repoA.AddRemote("origin", remote.GetLocalRemote()) if err != nil { log.Fatal(err) } - err = repoB.AddRemote("origin", remoteAddr) + err = repoB.AddRemote("origin", remote.GetLocalRemote()) if err != nil { log.Fatal(err) } 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/mock_repo.go b/repository/mock_repo.go index 628939aa..8a1724ef 100644 --- a/repository/mock_repo.go +++ b/repository/mock_repo.go @@ -4,8 +4,12 @@ import ( "crypto/sha1" "fmt" "strings" + "sync" "github.com/99designs/keyring" + "github.com/blevesearch/bleve" + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/memfs" "github.com/MichaelMure/git-bug/util/lamport" ) @@ -18,15 +22,21 @@ type mockRepoForTest struct { *mockRepoConfig *mockRepoKeyring *mockRepoCommon + *mockRepoStorage + *mockRepoBleve *mockRepoData *mockRepoClock } +func (m *mockRepoForTest) Close() error { return nil } + func NewMockRepoForTest() *mockRepoForTest { return &mockRepoForTest{ mockRepoConfig: NewMockRepoConfig(), mockRepoKeyring: NewMockRepoKeyring(), mockRepoCommon: NewMockRepoCommon(), + mockRepoStorage: NewMockRepoStorage(), + mockRepoBleve: newMockRepoBleve(), mockRepoData: NewMockRepoData(), mockRepoClock: NewMockRepoClock(), } @@ -86,11 +96,6 @@ func NewMockRepoCommon() *mockRepoCommon { return &mockRepoCommon{} } -// GetPath returns the path to the repo. -func (r *mockRepoCommon) GetPath() string { - return "~/mockRepo/" -} - func (r *mockRepoCommon) GetUserName() (string, error) { return "René Descartes", nil } @@ -112,6 +117,62 @@ func (r *mockRepoCommon) GetRemotes() (map[string]string, error) { }, nil } +var _ RepoStorage = &mockRepoStorage{} + +type mockRepoStorage struct { + localFs billy.Filesystem +} + +func NewMockRepoStorage() *mockRepoStorage { + return &mockRepoStorage{localFs: memfs.New()} +} + +func (m *mockRepoStorage) LocalStorage() billy.Filesystem { + return m.localFs +} + +var _ RepoBleve = &mockRepoBleve{} + +type mockRepoBleve struct { + indexesMutex sync.Mutex + indexes map[string]bleve.Index +} + +func newMockRepoBleve() *mockRepoBleve { + return &mockRepoBleve{ + indexes: make(map[string]bleve.Index), + } +} + +func (m *mockRepoBleve) GetBleveIndex(name string) (bleve.Index, error) { + m.indexesMutex.Lock() + defer m.indexesMutex.Unlock() + + if index, ok := m.indexes[name]; ok { + return index, nil + } + + mapping := bleve.NewIndexMapping() + mapping.DefaultAnalyzer = "en" + + index, err := bleve.NewMemOnly(mapping) + if err != nil { + return nil, err + } + + m.indexes[name] = index + + return index, nil +} + +func (m *mockRepoBleve) ClearBleveIndex(name string) error { + m.indexesMutex.Lock() + defer m.indexesMutex.Unlock() + + delete(m.indexes, name) + return nil +} + var _ RepoData = &mockRepoData{} type commit struct { @@ -314,7 +375,17 @@ func (r *mockRepoData) AddRemote(name string, url string) error { panic("implement me") } +func (m mockRepoForTest) GetLocalRemote() string { + panic("implement me") +} + +func (m mockRepoForTest) EraseFromDisk() error { + // nothing to do + return nil +} + type mockRepoClock struct { + mu sync.Mutex clocks map[string]lamport.Clock } @@ -325,6 +396,9 @@ func NewMockRepoClock() *mockRepoClock { } func (r *mockRepoClock) GetOrCreateClock(name string) (lamport.Clock, error) { + r.mu.Lock() + defer r.mu.Unlock() + if c, ok := r.clocks[name]; ok { return c, nil } diff --git a/repository/repo.go b/repository/repo.go index 4b45a1c5..eb9296d4 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -4,6 +4,9 @@ package repository import ( "errors" + "github.com/blevesearch/bleve" + "github.com/go-git/go-billy/v5" + "github.com/MichaelMure/git-bug/util/lamport" ) @@ -20,6 +23,15 @@ type Repo interface { RepoKeyring RepoCommon RepoData + RepoStorage + RepoBleve + + Close() error +} + +type RepoCommonStorage interface { + RepoCommon + RepoStorage } // ClockedRepo is a Repo that also has Lamport clocks @@ -48,9 +60,6 @@ type RepoKeyring interface { // RepoCommon represent the common function the we want all the repo to implement type RepoCommon interface { - // GetPath returns the path to the repo. - GetPath() string - // GetUserName returns the name the the user has used to configure git GetUserName() (string, error) @@ -64,6 +73,21 @@ type RepoCommon interface { GetRemotes() (map[string]string, error) } +// RepoStorage give access to the filesystem +type RepoStorage interface { + // LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug + LocalStorage() billy.Filesystem +} + +// RepoBleve give access to Bleve to implement full-text search indexes. +type RepoBleve interface { + // GetBleveIndex return a bleve.Index that can be used to index documents + GetBleveIndex(name string) (bleve.Index, error) + + // ClearBleveIndex will wipe the given index + ClearBleveIndex(name string) error +} + // RepoData give access to the git data storage type RepoData interface { // FetchRefs fetch git refs from a remote @@ -145,4 +169,10 @@ type TestedRepo interface { type repoTest interface { // AddRemote add a new remote to the repository AddRemote(name string, url string) error + + // GetLocalRemote return the URL to use to add this repo as a local remote + GetLocalRemote() string + + // EraseFromDisk delete this repository entirely from the disk + EraseFromDisk() error } diff --git a/repository/repo_testing.go b/repository/repo_testing.go index 41b3609e..c0e1fa79 100644 --- a/repository/repo_testing.go +++ b/repository/repo_testing.go @@ -3,8 +3,6 @@ package repository import ( "log" "math/rand" - "os" - "strings" "testing" "github.com/stretchr/testify/require" @@ -15,25 +13,13 @@ import ( func CleanupTestRepos(repos ...Repo) { var firstErr error for _, repo := range repos { - path := repo.GetPath() - if strings.HasSuffix(path, "/.git") { - // for a normal repository (not --bare), we want to remove everything - // including the parent directory where files are checked out - path = strings.TrimSuffix(path, "/.git") - - // Testing non-bare repo should also check path is - // only .git (i.e. ./.git), but doing so, we should - // try to remove the current directory and hav some - // trouble. In the present case, this case should not - // occur. - // TODO consider warning or error when path == ".git" - } - // fmt.Println("Cleaning repo:", path) - err := os.RemoveAll(path) - if err != nil { - log.Println(err) - if firstErr == nil { - firstErr = err + if repo, ok := repo.(TestedRepo); ok { + err := repo.EraseFromDisk() + if err != nil { + log.Println(err) + if firstErr == nil { + firstErr = err + } } } } diff --git a/termui/bug_table.go b/termui/bug_table.go index d3a187db..94185a29 100644 --- a/termui/bug_table.go +++ b/termui/bug_table.go @@ -237,7 +237,11 @@ func (bt *bugTable) disable(g *gocui.Gui) error { } func (bt *bugTable) paginate(max int) error { - bt.allIds = bt.repo.QueryBugs(bt.query) + var err error + bt.allIds, err = bt.repo.QueryBugs(bt.query) + if err != nil { + return err + } return bt.doPaginate(max) } 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) |