diff options
32 files changed, 619 insertions, 154 deletions
diff --git a/cache/bug_subcache.go b/cache/bug_subcache.go index 920fe1dc..21c9a6d2 100644 --- a/cache/bug_subcache.go +++ b/cache/bug_subcache.go @@ -38,6 +38,7 @@ func NewRepoCacheBug(repo repository.ClockedRepo, ReadWithResolver: bug.ReadWithResolver, ReadAllWithResolver: bug.ReadAllWithResolver, Remove: bug.Remove, + RemoveAll: bug.RemoveAll, MergeAll: bug.MergeAll, } diff --git a/cache/identity_subcache.go b/cache/identity_subcache.go index f862ca8b..05a91358 100644 --- a/cache/identity_subcache.go +++ b/cache/identity_subcache.go @@ -39,7 +39,8 @@ func NewRepoCacheIdentity(repo repository.ClockedRepo, ReadAllWithResolver: func(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan entity.StreamedEntity[*identity.Identity] { return identity.ReadAllLocal(repo) }, - Remove: identity.RemoveIdentity, + Remove: identity.Remove, + RemoveAll: identity.RemoveAll, MergeAll: func(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor identity.Interface) <-chan entity.MergeResult { return identity.MergeAll(repo, remote) }, diff --git a/cache/repo_cache.go b/cache/repo_cache.go index 99e9abbd..9e45d1f1 100644 --- a/cache/repo_cache.go +++ b/cache/repo_cache.go @@ -30,8 +30,9 @@ var _ repository.RepoKeyring = &RepoCache{} type cacheMgmt interface { Typename() string Load() error - Build() error + Build() <-chan BuildEvent SetCacheSize(size int) + RemoveAll() error MergeAll(remote string) <-chan entity.MergeResult GetNamespace() string Close() error @@ -212,6 +213,7 @@ const ( BuildEventCacheIsBuilt BuildEventRemoveLock BuildEventStarted + BuildEventProgress BuildEventFinished ) @@ -223,6 +225,10 @@ type BuildEvent struct { Typename string // Event is the type of the event. Event BuildEventType + // Total is the total number of element being built. Set if Event is BuildEventStarted. + Total int64 + // Progress is the current count of processed element. Set if Event is BuildEventProgress. + Progress int64 } func (c *RepoCache) buildCache(events chan BuildEvent) { @@ -233,23 +239,13 @@ func (c *RepoCache) buildCache(events chan BuildEvent) { wg.Add(1) go func(subcache cacheMgmt) { defer wg.Done() - events <- BuildEvent{ - Typename: subcache.Typename(), - Event: BuildEventStarted, - } - err := subcache.Build() - if err != nil { - events <- BuildEvent{ - Typename: subcache.Typename(), - Err: err, + buildEvents := subcache.Build() + for buildEvent := range buildEvents { + events <- buildEvent + if buildEvent.Err != nil { + return } - return - } - - events <- BuildEvent{ - Typename: subcache.Typename(), - Event: BuildEventFinished, } }(subcache) } diff --git a/cache/repo_cache_common.go b/cache/repo_cache_common.go index f768b8e2..759536bd 100644 --- a/cache/repo_cache_common.go +++ b/cache/repo_cache_common.go @@ -3,12 +3,12 @@ package cache import ( "sync" - "github.com/go-git/go-billy/v5" "github.com/pkg/errors" "github.com/MichaelMure/git-bug/entities/identity" "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/repository" + "github.com/MichaelMure/git-bug/util/multierr" ) func (c *RepoCache) Name() string { @@ -56,7 +56,7 @@ func (c *RepoCache) GetRemotes() (map[string]string, error) { } // LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug -func (c *RepoCache) LocalStorage() billy.Filesystem { +func (c *RepoCache) LocalStorage() repository.LocalStorage { return c.repo.LocalStorage() } @@ -82,6 +82,15 @@ func (c *RepoCache) Fetch(remote string) (string, error) { return c.repo.FetchRefs(remote, prefixes...) } +// RemoveAll deletes all entities from the cache and the disk. +func (c *RepoCache) RemoveAll() error { + var errWait multierr.ErrWaitGroup + for _, mgmt := range c.subcaches { + errWait.Go(mgmt.RemoveAll) + } + return errWait.Wait() +} + // MergeAll will merge all the available remote bug and identities func (c *RepoCache) MergeAll(remote string) <-chan entity.MergeResult { out := make(chan entity.MergeResult) @@ -163,6 +172,19 @@ func (c *RepoCache) SetUserIdentity(i *IdentityCache) error { return nil } +func (c *RepoCache) ClearUserIdentity() error { + c.muUserIdentity.Lock() + defer c.muUserIdentity.Unlock() + + err := identity.ClearUserIdentity(c.repo) + if err != nil { + return err + } + + c.userIdentityId = "" + return nil +} + func (c *RepoCache) GetUserIdentity() (*IdentityCache, error) { c.muUserIdentity.RLock() if c.userIdentityId != "" { diff --git a/cache/repo_cache_test.go b/cache/repo_cache_test.go index 07a3fee8..3c11220d 100644 --- a/cache/repo_cache_test.go +++ b/cache/repo_cache_test.go @@ -135,6 +135,39 @@ func TestCache(t *testing.T) { _, err = cache.Bugs().ResolvePrefix(bug1.Id().String()[:10]) require.NoError(t, err) + require.Len(t, cache.bugs.cached, 1) + require.Len(t, cache.bugs.excerpts, 2) + require.Len(t, cache.identities.cached, 1) + require.Len(t, cache.identities.excerpts, 2) + require.Equal(t, uint64(2), indexCount(t, identity.Namespace)) + require.Equal(t, uint64(2), indexCount(t, bug.Namespace)) + + // Remove + RemoveAll + err = cache.Identities().Remove(iden1.Id().String()[:10]) + require.NoError(t, err) + err = cache.Bugs().Remove(bug1.Id().String()[:10]) + require.NoError(t, err) + require.Len(t, cache.bugs.cached, 0) + require.Len(t, cache.bugs.excerpts, 1) + require.Len(t, cache.identities.cached, 0) + require.Len(t, cache.identities.excerpts, 1) + require.Equal(t, uint64(1), indexCount(t, identity.Namespace)) + require.Equal(t, uint64(1), indexCount(t, bug.Namespace)) + + _, err = cache.Identities().New("René Descartes", "rene@descartes.fr") + require.NoError(t, err) + _, _, err = cache.Bugs().NewRaw(iden2, time.Now().Unix(), "title", "message", nil, nil) + require.NoError(t, err) + + err = cache.RemoveAll() + require.NoError(t, err) + require.Len(t, cache.bugs.cached, 0) + require.Len(t, cache.bugs.excerpts, 0) + require.Len(t, cache.identities.cached, 0) + require.Len(t, cache.identities.excerpts, 0) + require.Equal(t, uint64(0), indexCount(t, identity.Namespace)) + require.Equal(t, uint64(0), indexCount(t, bug.Namespace)) + // Close require.NoError(t, cache.Close()) require.Empty(t, cache.bugs.cached) diff --git a/cache/subcache.go b/cache/subcache.go index 7b599b10..09e53c23 100644 --- a/cache/subcache.go +++ b/cache/subcache.go @@ -34,6 +34,7 @@ type Actions[EntityT entity.Interface] struct { ReadWithResolver func(repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (EntityT, error) ReadAllWithResolver func(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan entity.StreamedEntity[EntityT] Remove func(repo repository.ClockedRepo, id entity.Id) error + RemoveAll func(repo repository.ClockedRepo) error MergeAll func(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor identity.Interface) <-chan entity.MergeResult } @@ -185,51 +186,98 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) write() error { return f.Close() } -func (sc *SubCache[EntityT, ExcerptT, CacheT]) Build() error { - sc.excerpts = make(map[entity.Id]ExcerptT) +func (sc *SubCache[EntityT, ExcerptT, CacheT]) Build() <-chan BuildEvent { + out := make(chan BuildEvent) - allEntities := sc.actions.ReadAllWithResolver(sc.repo, sc.resolvers()) + go func() { + defer close(out) - index, err := sc.repo.GetIndex(sc.namespace) - if err != nil { - return err - } + out <- BuildEvent{ + Typename: sc.typename, + Event: BuildEventStarted, + } - // wipe the index just to be sure - err = index.Clear() - if err != nil { - return err - } + sc.excerpts = make(map[entity.Id]ExcerptT) - indexer, indexEnd := index.IndexBatch() + allEntities := sc.actions.ReadAllWithResolver(sc.repo, sc.resolvers()) - for e := range allEntities { - if e.Err != nil { - return e.Err + index, err := sc.repo.GetIndex(sc.namespace) + if err != nil { + out <- BuildEvent{ + Typename: sc.typename, + Err: err, + } + return + } + + // wipe the index just to be sure + err = index.Clear() + if err != nil { + out <- BuildEvent{ + Typename: sc.typename, + Err: err, + } + return } - cached := sc.makeCached(e.Entity, sc.entityUpdated) - sc.excerpts[e.Entity.Id()] = sc.makeExcerpt(cached) - // might as well keep them in memory - sc.cached[e.Entity.Id()] = cached + indexer, indexEnd := index.IndexBatch() + + for e := range allEntities { + if e.Err != nil { + out <- BuildEvent{ + Typename: sc.typename, + Err: e.Err, + } + return + } - indexData := sc.makeIndexData(cached) - if err := indexer(e.Entity.Id().String(), indexData); err != nil { - return err + cached := sc.makeCached(e.Entity, sc.entityUpdated) + sc.excerpts[e.Entity.Id()] = sc.makeExcerpt(cached) + // might as well keep them in memory + sc.cached[e.Entity.Id()] = cached + + indexData := sc.makeIndexData(cached) + if err := indexer(e.Entity.Id().String(), indexData); err != nil { + out <- BuildEvent{ + Typename: sc.typename, + Err: err, + } + return + } + + out <- BuildEvent{ + Typename: sc.typename, + Event: BuildEventProgress, + Progress: e.CurrentEntity, + Total: e.TotalEntities, + } } - } - err = indexEnd() - if err != nil { - return err - } + err = indexEnd() + if err != nil { + out <- BuildEvent{ + Typename: sc.typename, + Err: err, + } + return + } - err = sc.write() - if err != nil { - return err - } + err = sc.write() + if err != nil { + out <- BuildEvent{ + Typename: sc.typename, + Err: err, + } + return + } - return nil + out <- BuildEvent{ + Typename: sc.typename, + Event: BuildEventFinished, + } + }() + + return out } func (sc *SubCache[EntityT, ExcerptT, CacheT]) SetCacheSize(size int) { @@ -399,7 +447,49 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Remove(prefix string) error { delete(sc.excerpts, e.Id()) sc.lru.Remove(e.Id()) + index, err := sc.repo.GetIndex(sc.namespace) + if err != nil { + sc.mu.Unlock() + return err + } + + err = index.Remove(e.Id().String()) + sc.mu.Unlock() + if err != nil { + return err + } + + return sc.write() +} + +func (sc *SubCache[EntityT, ExcerptT, CacheT]) RemoveAll() error { + sc.mu.Lock() + + err := sc.actions.RemoveAll(sc.repo) + if err != nil { + sc.mu.Unlock() + return err + } + + for id, _ := range sc.cached { + delete(sc.cached, id) + sc.lru.Remove(id) + } + for id, _ := range sc.excerpts { + delete(sc.excerpts, id) + } + + index, err := sc.repo.GetIndex(sc.namespace) + if err != nil { + sc.mu.Unlock() + return err + } + + err = index.Clear() sc.mu.Unlock() + if err != nil { + return err + } return sc.write() } diff --git a/commands/execenv/env.go b/commands/execenv/env.go index 4be7c247..d2d1c301 100644 --- a/commands/execenv/env.go +++ b/commands/execenv/env.go @@ -7,6 +7,8 @@ import ( "os" "github.com/spf13/cobra" + "github.com/vbauerster/mpb/v8" + "github.com/vbauerster/mpb/v8/decor" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/entities/identity" @@ -50,6 +52,10 @@ type Out interface { // Reset clear what has been recorded as written in the output before. // This only works in test scenario. Reset() + + // Raw return the underlying io.Writer, or itself if not. + // This is useful if something need to access the raw file descriptor. + Raw() io.Writer } type out struct { @@ -89,6 +95,10 @@ func (o out) Reset() { panic("only work with a test env") } +func (o out) Raw() io.Writer { + return o.Writer +} + // LoadRepo is a pre-run function that load the repository for use in a command func LoadRepo(env *Env) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { @@ -143,18 +153,9 @@ func LoadBackend(env *Env) func(*cobra.Command, []string) error { var events chan cache.BuildEvent env.Backend, events = cache.NewRepoCache(env.Repo) - for event := range events { - if event.Err != nil { - return event.Err - } - switch event.Event { - case cache.BuildEventCacheIsBuilt: - env.Err.Println("Building cache... ") - case cache.BuildEventStarted: - env.Err.Printf("[%s] started\n", event.Typename) - case cache.BuildEventFinished: - env.Err.Printf("[%s] done\n", event.Typename) - } + err = CacheBuildProgressBar(env, events) + if err != nil { + return err } cleaner := func(env *Env) interrupt.CleanerFunc { @@ -213,3 +214,41 @@ func CloseBackend(env *Env, runE func(cmd *cobra.Command, args []string) error) return err } } + +func CacheBuildProgressBar(env *Env, events chan cache.BuildEvent) error { + var progress *mpb.Progress + var bars = make(map[string]*mpb.Bar) + + for event := range events { + if event.Err != nil { + return event.Err + } + + if progress == nil { + progress = mpb.New(mpb.WithOutput(env.Err.Raw())) + } + + switch event.Event { + case cache.BuildEventCacheIsBuilt: + env.Err.Println("Building cache... ") + case cache.BuildEventStarted: + bars[event.Typename] = progress.AddBar(-1, + mpb.BarRemoveOnComplete(), + mpb.PrependDecorators( + decor.Name(event.Typename, decor.WCSyncSpace), + decor.CountersNoUnit("%d / %d", decor.WCSyncSpace), + ), + mpb.AppendDecorators(decor.Percentage(decor.WCSyncSpace)), + ) + case cache.BuildEventProgress: + bars[event.Typename].SetTotal(event.Total, false) + bars[event.Typename].SetCurrent(event.Progress) + } + } + + if progress != nil { + progress.Shutdown() + } + + return nil +} diff --git a/commands/execenv/env_testing.go b/commands/execenv/env_testing.go index 34eafc9c..6eb9c69d 100644 --- a/commands/execenv/env_testing.go +++ b/commands/execenv/env_testing.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "io" "testing" "github.com/stretchr/testify/require" @@ -12,6 +13,8 @@ import ( "github.com/MichaelMure/git-bug/repository" ) +var _ Out = &TestOut{} + type TestOut struct { *bytes.Buffer } @@ -37,6 +40,10 @@ func (te *TestOut) PrintJSON(v interface{}) error { return nil } +func (te *TestOut) Raw() io.Writer { + return te.Buffer +} + func NewTestEnv(t *testing.T) *Env { t.Helper() diff --git a/commands/root.go b/commands/root.go index cb4fd686..0c854739 100644 --- a/commands/root.go +++ b/commands/root.go @@ -81,6 +81,7 @@ the same git remote you are already using to collaborate with other people. cmd.AddCommand(newCommandsCommand()) cmd.AddCommand(newVersionCommand()) + cmd.AddCommand(newWipeCommand()) return cmd } diff --git a/commands/webui.go b/commands/webui.go index 0b3b24a6..31313146 100644 --- a/commands/webui.go +++ b/commands/webui.go @@ -108,19 +108,10 @@ func runWebUI(env *execenv.Env, opts webUIOptions) error { mrc := cache.NewMultiRepoCache() _, events := mrc.RegisterDefaultRepository(env.Repo) - for event := range events { - if event.Err != nil { - env.Err.Printf("Cache building error [%s]: %v\n", event.Typename, event.Err) - continue - } - switch event.Event { - case cache.BuildEventCacheIsBuilt: - env.Err.Println("Building cache... ") - case cache.BuildEventStarted: - env.Err.Printf("[%s] started\n", event.Typename) - case cache.BuildEventFinished: - env.Err.Printf("[%s] done\n", event.Typename) - } + + err := execenv.CacheBuildProgressBar(env, events) + if err != nil { + return err } var errOut io.Writer diff --git a/commands/wipe.go b/commands/wipe.go new file mode 100644 index 00000000..0ec53f49 --- /dev/null +++ b/commands/wipe.go @@ -0,0 +1,58 @@ +package commands + +import ( + "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/commands/execenv" +) + +func newWipeCommand() *cobra.Command { + env := execenv.NewEnv() + + cmd := &cobra.Command{ + Use: "wipe", + Short: "Wipe git-bug from the git repository", + PreRunE: execenv.LoadBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runWipe(env) + }, + } + + return cmd +} + +func runWipe(env *execenv.Env) error { + env.Out.Println("cleaning entities...") + err := env.Backend.RemoveAll() + if err != nil { + _ = env.Backend.Close() + return err + } + + env.Out.Println("cleaning git config ...") + err = env.Backend.ClearUserIdentity() + if err != nil { + _ = env.Backend.Close() + return err + } + err = env.Backend.LocalConfig().RemoveAll("git-bug") + if err != nil { + _ = env.Backend.Close() + return err + } + + storage := env.Backend.LocalStorage() + + err = env.Backend.Close() + if err != nil { + return err + } + + env.Out.Println("cleaning caches ...") + err = storage.RemoveAll(".") + if err != nil { + return err + } + + return nil +} diff --git a/doc/man/git-bug-wipe.1 b/doc/man/git-bug-wipe.1 new file mode 100644 index 00000000..fc3178f6 --- /dev/null +++ b/doc/man/git-bug-wipe.1 @@ -0,0 +1,27 @@ +.nh +.TH "GIT-BUG" "1" "Apr 2019" "Generated from git-bug's source code" "" + +.SH NAME +.PP +git-bug-wipe - Wipe git-bug from the git repository + + +.SH SYNOPSIS +.PP +\fBgit-bug wipe [flags]\fP + + +.SH DESCRIPTION +.PP +Wipe git-bug from the git repository + + +.SH OPTIONS +.PP +\fB-h\fP, \fB--help\fP[=false] + help for wipe + + +.SH SEE ALSO +.PP +\fBgit-bug(1)\fP diff --git a/doc/man/git-bug.1 b/doc/man/git-bug.1 index 8b66d312..9bf59164 100644 --- a/doc/man/git-bug.1 +++ b/doc/man/git-bug.1 @@ -29,4 +29,4 @@ the same git remote you are already using to collaborate with other people. .SH SEE ALSO .PP -\fBgit-bug-bridge(1)\fP, \fBgit-bug-bug(1)\fP, \fBgit-bug-commands(1)\fP, \fBgit-bug-label(1)\fP, \fBgit-bug-pull(1)\fP, \fBgit-bug-push(1)\fP, \fBgit-bug-termui(1)\fP, \fBgit-bug-user(1)\fP, \fBgit-bug-version(1)\fP, \fBgit-bug-webui(1)\fP +\fBgit-bug-bridge(1)\fP, \fBgit-bug-bug(1)\fP, \fBgit-bug-commands(1)\fP, \fBgit-bug-label(1)\fP, \fBgit-bug-pull(1)\fP, \fBgit-bug-push(1)\fP, \fBgit-bug-termui(1)\fP, \fBgit-bug-user(1)\fP, \fBgit-bug-version(1)\fP, \fBgit-bug-webui(1)\fP, \fBgit-bug-wipe(1)\fP diff --git a/doc/md/git-bug.md b/doc/md/git-bug.md index b311aff5..a71d6dfb 100644 --- a/doc/md/git-bug.md +++ b/doc/md/git-bug.md @@ -34,4 +34,5 @@ git-bug [flags] * [git-bug user](git-bug_user.md) - List identities * [git-bug version](git-bug_version.md) - Show git-bug version information * [git-bug webui](git-bug_webui.md) - Launch the web UI +* [git-bug wipe](git-bug_wipe.md) - Wipe git-bug from the git repository diff --git a/doc/md/git-bug_wipe.md b/doc/md/git-bug_wipe.md new file mode 100644 index 00000000..be1a7246 --- /dev/null +++ b/doc/md/git-bug_wipe.md @@ -0,0 +1,18 @@ +## git-bug wipe + +Wipe git-bug from the git repository + +``` +git-bug wipe [flags] +``` + +### Options + +``` + -h, --help help for wipe +``` + +### SEE ALSO + +* [git-bug](git-bug.md) - A bug tracker embedded in Git + diff --git a/entities/bug/bug_actions.go b/entities/bug/bug_actions.go index 198e4ed0..651d24dc 100644 --- a/entities/bug/bug_actions.go +++ b/entities/bug/bug_actions.go @@ -37,3 +37,9 @@ func MergeAll(repo repository.ClockedRepo, resolvers entity.Resolvers, remote st func Remove(repo repository.ClockedRepo, id entity.Id) error { return dag.Remove(def, repo, id) } + +// RemoveAll will remove all local bugs. +// RemoveAll is idempotent. +func RemoveAll(repo repository.ClockedRepo) error { + return dag.RemoveAll(def, repo) +} diff --git a/entities/identity/identity.go b/entities/identity/identity.go index 22ce652c..91564ffa 100644 --- a/entities/identity/identity.go +++ b/entities/identity/identity.go @@ -164,56 +164,6 @@ func ListLocalIds(repo repository.Repo) ([]entity.Id, error) { return entity.RefsToIds(refs), nil } -// RemoveIdentity will remove a local identity from its entity.Id -func RemoveIdentity(repo repository.ClockedRepo, id entity.Id) error { - var fullMatches []string - - refs, err := repo.ListRefs(identityRefPattern + id.String()) - if err != nil { - return err - } - if len(refs) > 1 { - return entity.NewErrMultipleMatch(Typename, entity.RefsToIds(refs)) - } - if len(refs) == 1 { - // we have the identity locally - fullMatches = append(fullMatches, refs[0]) - } - - remotes, err := repo.GetRemotes() - if err != nil { - return err - } - - for remote := range remotes { - remotePrefix := fmt.Sprintf(identityRemoteRefPattern+id.String(), remote) - remoteRefs, err := repo.ListRefs(remotePrefix) - if err != nil { - return err - } - if len(remoteRefs) > 1 { - return entity.NewErrMultipleMatch(Typename, entity.RefsToIds(refs)) - } - if len(remoteRefs) == 1 { - // found the identity in a remote - fullMatches = append(fullMatches, remoteRefs[0]) - } - } - - if len(fullMatches) == 0 { - return entity.NewErrNotFound(Typename) - } - - for _, ref := range fullMatches { - err = repo.RemoveRef(ref) - if err != nil { - return err - } - } - - return nil -} - // ReadAllLocal read and parse all local Identity func ReadAllLocal(repo repository.ClockedRepo) <-chan entity.StreamedEntity[*Identity] { return readAll(repo, identityRefPattern) @@ -238,6 +188,9 @@ func readAll(repo repository.ClockedRepo, refPrefix string) <-chan entity.Stream return } + total := int64(len(refs)) + current := int64(1) + for _, ref := range refs { i, err := read(repo, ref) @@ -246,7 +199,12 @@ func readAll(repo repository.ClockedRepo, refPrefix string) <-chan entity.Stream return } - out <- entity.StreamedEntity[*Identity]{Entity: i} + out <- entity.StreamedEntity[*Identity]{ + Entity: i, + CurrentEntity: current, + TotalEntities: total, + } + current++ } }() diff --git a/entities/identity/identity_actions.go b/entities/identity/identity_actions.go index 13776078..07560dc0 100644 --- a/entities/identity/identity_actions.go +++ b/entities/identity/identity_actions.go @@ -123,3 +123,74 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeRes return out } + +// Remove will remove a local identity from its entity.Id. +// It is left as a responsibility to the caller to make sure that this identities is not +// linked from another entity, otherwise it would break it. +// Remove is idempotent. +func Remove(repo repository.ClockedRepo, id entity.Id) error { + var fullMatches []string + + refs, err := repo.ListRefs(identityRefPattern + id.String()) + if err != nil { + return err + } + if len(refs) > 1 { + return entity.NewErrMultipleMatch(Typename, entity.RefsToIds(refs)) + } + if len(refs) == 1 { + // we have the identity locally + fullMatches = append(fullMatches, refs[0]) + } + + remotes, err := repo.GetRemotes() + if err != nil { + return err + } + + for remote := range remotes { + remotePrefix := fmt.Sprintf(identityRemoteRefPattern+id.String(), remote) + remoteRefs, err := repo.ListRefs(remotePrefix) + if err != nil { + return err + } + if len(remoteRefs) > 1 { + return entity.NewErrMultipleMatch(Typename, entity.RefsToIds(refs)) + } + if len(remoteRefs) == 1 { + // found the identity in a remote + fullMatches = append(fullMatches, remoteRefs[0]) + } + } + + if len(fullMatches) == 0 { + return entity.NewErrNotFound(Typename) + } + + for _, ref := range fullMatches { + err = repo.RemoveRef(ref) + if err != nil { + return err + } + } + + return nil +} + +// RemoveAll will remove all local identities. +// It is left as a responsibility to the caller to make sure that those identities are not +// linked from another entity, otherwise it would break them. +// RemoveAll is idempotent. +func RemoveAll(repo repository.ClockedRepo) error { + localIds, err := ListLocalIds(repo) + if err != nil { + return err + } + for _, id := range localIds { + err = Remove(repo, id) + if err != nil { + return err + } + } + return nil +} diff --git a/entities/identity/identity_test.go b/entities/identity/identity_test.go index 0ecc8058..85d5385b 100644 --- a/entities/identity/identity_test.go +++ b/entities/identity/identity_test.go @@ -275,7 +275,7 @@ func TestIdentityRemove(t *testing.T) { _, err = Fetch(repo, "remoteB") require.NoError(t, err) - err = RemoveIdentity(repo, rene.Id()) + err = Remove(repo, rene.Id()) require.NoError(t, err) _, err = ReadLocal(repo, rene.Id()) diff --git a/entities/identity/identity_user.go b/entities/identity/identity_user.go index 7eb374d4..f9e39bb2 100644 --- a/entities/identity/identity_user.go +++ b/entities/identity/identity_user.go @@ -15,6 +15,10 @@ func SetUserIdentity(repo repository.RepoConfig, identity *Identity) error { return repo.LocalConfig().StoreString(identityConfigKey, identity.Id().String()) } +func ClearUserIdentity(repo repository.RepoConfig) error { + return repo.LocalConfig().RemoveAll(identityConfigKey) +} + // GetUserIdentity read the current user identity, set with a git config entry func GetUserIdentity(repo repository.Repo) (*Identity, error) { id, err := GetUserIdentityId(repo) diff --git a/entity/dag/entity.go b/entity/dag/entity.go index 2028e1b4..f8dbd53d 100644 --- a/entity/dag/entity.go +++ b/entity/dag/entity.go @@ -314,6 +314,9 @@ func ReadAll[EntityT entity.Interface](def Definition, wrapper func(e *Entity) E return } + total := int64(len(refs)) + current := int64(1) + for _, ref := range refs { e, err := read[EntityT](def, wrapper, repo, resolvers, ref) @@ -322,7 +325,12 @@ func ReadAll[EntityT entity.Interface](def Definition, wrapper func(e *Entity) E return } - out <- entity.StreamedEntity[EntityT]{Entity: e} + out <- entity.StreamedEntity[EntityT]{ + Entity: e, + CurrentEntity: current, + TotalEntities: total, + } + current++ } }() diff --git a/entity/dag/entity_actions.go b/entity/dag/entity_actions.go index 97a68c36..5f0abec3 100644 --- a/entity/dag/entity_actions.go +++ b/entity/dag/entity_actions.go @@ -258,3 +258,19 @@ func Remove(def Definition, repo repository.ClockedRepo, id entity.Id) error { return nil } + +// RemoveAll delete all Entity matching the Definition. +// RemoveAll is idempotent. +func RemoveAll(def Definition, repo repository.ClockedRepo) error { + localIds, err := ListLocalIds(def, repo) + if err != nil { + return err + } + for _, id := range localIds { + err = Remove(def, repo, id) + if err != nil { + return err + } + } + return nil +} diff --git a/entity/dag/entity_actions_test.go b/entity/dag/entity_actions_test.go index fd219644..6181614b 100644 --- a/entity/dag/entity_actions_test.go +++ b/entity/dag/entity_actions_test.go @@ -406,3 +406,34 @@ func TestRemove(t *testing.T) { err = Remove(def, repoA, e.Id()) require.NoError(t, err) } + +func TestRemoveAll(t *testing.T) { + repoA, _, _, id1, _, resolvers, def := makeTestContextRemote(t) + + var ids []entity.Id + + for i := 0; i < 10; i++ { + e := New(def) + e.Append(newOp1(id1, "foo")) + require.NoError(t, e.Commit(repoA)) + ids = append(ids, e.Id()) + } + + _, err := Push(def, repoA, "remote") + require.NoError(t, err) + + err = RemoveAll(def, repoA) + require.NoError(t, err) + + for _, id := range ids { + _, err = Read(def, wrapper, repoA, resolvers, id) + require.Error(t, err) + + _, err = readRemote(def, wrapper, repoA, resolvers, "remote", id) + require.Error(t, err) + } + + // Remove is idempotent + err = RemoveAll(def, repoA) + require.NoError(t, err) +} diff --git a/entity/streamed.go b/entity/streamed.go index 789224a3..33124ef0 100644 --- a/entity/streamed.go +++ b/entity/streamed.go @@ -1,6 +1,11 @@ package entity type StreamedEntity[EntityT Interface] struct { - Entity EntityT Err error + Entity EntityT + + // CurrentEntity is the index of the current entity being streamed, to express progress. + CurrentEntity int64 + // TotalEntities is the total count of expected entities, if known. + TotalEntities int64 } @@ -11,7 +11,7 @@ require ( github.com/awesome-gocui/gocui v1.1.0 github.com/blevesearch/bleve v1.0.14 github.com/cheekybits/genny v1.0.0 - github.com/dustin/go-humanize v1.0.0 + github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.13.0 github.com/go-git/go-billy/v5 v5.4.0 github.com/go-git/go-git/v5 v5.5.2 @@ -26,6 +26,7 @@ require ( github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.8.1 + github.com/vbauerster/mpb/v8 v8.1.4 github.com/vektah/gqlparser/v2 v2.5.1 github.com/xanzy/go-gitlab v0.77.0 golang.org/x/crypto v0.5.0 @@ -35,7 +36,12 @@ require ( golang.org/x/text v0.6.0 ) +// https://github.com/go-git/go-git/pull/659 +replace github.com/go-git/go-git/v5 => github.com/MichaelMure/go-git/v5 v5.1.1-0.20230114115943-17400561a81c + require ( + github.com/VividCortex/ewma v1.2.0 // indirect + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/cloudflare/circl v1.3.1 // indirect github.com/lithammer/dedent v1.1.0 // indirect github.com/owenrumney/go-sarif v1.0.11 // indirect @@ -86,14 +92,14 @@ require ( github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.0.3 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-runewidth v0.0.12 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/philhofer/fwd v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/praetorian-inc/gokart v0.5.1 - github.com/rivo/uniseg v0.1.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect @@ -10,6 +10,8 @@ github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= +github.com/MichaelMure/go-git/v5 v5.1.1-0.20230114115943-17400561a81c h1:JFFZbyq4cdKo+QrKNxXemMftPy08aS9gELrPTlPTaZU= +github.com/MichaelMure/go-git/v5 v5.1.1-0.20230114115943-17400561a81c/go.mod h1:BE5hUJ5yaV2YMxhmaP4l6RBQ08kMxKSPD4BlxtH7OjI= github.com/MichaelMure/go-term-text v0.3.1 h1:Kw9kZanyZWiCHOYu9v/8pWEgDQ6UVN9/ix2Vd2zzWf0= github.com/MichaelMure/go-term-text v0.3.1/go.mod h1:QgVjAEDUnRMlzpS6ky5CGblux7ebeiLnuy9dAaFZu8o= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= @@ -19,6 +21,10 @@ github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0A github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/RoaringBitmap/roaring v0.4.23 h1:gpyfd12QohbqhFO4NVDUdoPOCXsyahYRQhINmlHxKeo= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= @@ -94,8 +100,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -123,8 +129,6 @@ github.com/go-git/go-billy/v5 v5.4.0 h1:Vaw7LaSTRJOUric7pe4vnzBSgyuf2KrLsu2Y4ZpQ github.com/go-git/go-billy/v5 v5.4.0/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.5.2 h1:v8lgZa5k9ylUw+OR/roJHTxR4QItsNFI5nKtAXFuynw= -github.com/go-git/go-git/v5 v5.5.2/go.mod h1:BE5hUJ5yaV2YMxhmaP4l6RBQ08kMxKSPD4BlxtH7OjI= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -209,8 +213,9 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -243,8 +248,9 @@ github.com/praetorian-inc/gokart v0.5.1 h1:GYUM69qskrRibZUAEwKEm/pd/j/SFzlFnQnhx github.com/praetorian-inc/gokart v0.5.1/go.mod h1:GuA97YgdXwqOVsnHY6PCvV1t9t0Jsk3Zcd6sbTXj4uI= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -298,6 +304,8 @@ github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDW github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4= github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY= +github.com/vbauerster/mpb/v8 v8.1.4 h1:MOcLTIbbAA892wVjRiuFHa1nRlNvifQMDVh12Bq/xIs= +github.com/vbauerster/mpb/v8 v8.1.4/go.mod h1:2fRME8lCLU9gwJwghZb1bO9A3Plc8KPeQ/ayGj+Ek4I= github.com/vektah/gqlparser/v2 v2.5.1 h1:ZGu+bquAY23jsxDRcYpWjttRZrUz07LbiY77gUOHcr4= github.com/vektah/gqlparser/v2 v2.5.1/go.mod h1:mPgqFBu/woKTVYWyNk8cO3kh4S/f4aRFZrvOnp3hmCs= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= diff --git a/repository/gogit.go b/repository/gogit.go index 01c47d41..93806026 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -13,7 +13,6 @@ import ( "time" "github.com/ProtonMail/go-crypto/openpgp" - "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" @@ -48,7 +47,7 @@ type GoGitRepo struct { indexes map[string]Index keyring Keyring - localStorage billy.Filesystem + localStorage LocalStorage } // OpenGoGitRepo opens an already existing repo at the given path and @@ -77,7 +76,7 @@ func OpenGoGitRepo(path, namespace string, clockLoaders []ClockLoader) (*GoGitRe clocks: make(map[string]lamport.Clock), indexes: make(map[string]Index), keyring: k, - localStorage: osfs.New(filepath.Join(path, namespace)), + localStorage: billyLocalStorage{Filesystem: osfs.New(filepath.Join(path, namespace))}, } loaderToRun := make([]ClockLoader, 0, len(clockLoaders)) @@ -131,7 +130,7 @@ func InitGoGitRepo(path, namespace string) (*GoGitRepo, error) { clocks: make(map[string]lamport.Clock), indexes: make(map[string]Index), keyring: k, - localStorage: osfs.New(filepath.Join(path, ".git", namespace)), + localStorage: billyLocalStorage{Filesystem: osfs.New(filepath.Join(path, ".git", namespace))}, }, nil } @@ -156,7 +155,7 @@ func InitBareGoGitRepo(path, namespace string) (*GoGitRepo, error) { clocks: make(map[string]lamport.Clock), indexes: make(map[string]Index), keyring: k, - localStorage: osfs.New(filepath.Join(path, namespace)), + localStorage: billyLocalStorage{Filesystem: osfs.New(filepath.Join(path, namespace))}, }, nil } @@ -320,7 +319,7 @@ func (repo *GoGitRepo) GetRemotes() (map[string]string, error) { // LocalStorage returns a billy.Filesystem giving access to // $RepoPath/.git/$Namespace. -func (repo *GoGitRepo) LocalStorage() billy.Filesystem { +func (repo *GoGitRepo) LocalStorage() LocalStorage { return repo.localStorage } diff --git a/repository/index_bleve.go b/repository/index_bleve.go index aae41d5f..40170919 100644 --- a/repository/index_bleve.go +++ b/repository/index_bleve.go @@ -129,6 +129,13 @@ func (b *bleveIndex) DocCount() (uint64, error) { return b.index.DocCount() } +func (b *bleveIndex) Remove(id string) error { + b.mu.Lock() + defer b.mu.Unlock() + + return b.index.Delete(id) +} + func (b *bleveIndex) Clear() error { b.mu.Lock() defer b.mu.Unlock() diff --git a/repository/localstorage_billy.go b/repository/localstorage_billy.go new file mode 100644 index 00000000..ab3909c9 --- /dev/null +++ b/repository/localstorage_billy.go @@ -0,0 +1,16 @@ +package repository + +import ( + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/util" +) + +var _ LocalStorage = &billyLocalStorage{} + +type billyLocalStorage struct { + billy.Filesystem +} + +func (b billyLocalStorage) RemoveAll(path string) error { + return util.RemoveAll(b.Filesystem, path) +} diff --git a/repository/mock_repo.go b/repository/mock_repo.go index c2cef8ef..6ea5c71e 100644 --- a/repository/mock_repo.go +++ b/repository/mock_repo.go @@ -9,7 +9,6 @@ import ( "github.com/99designs/keyring" "github.com/ProtonMail/go-crypto/openpgp" - "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/memfs" "github.com/MichaelMure/git-bug/util/lamport" @@ -123,14 +122,14 @@ func (r *mockRepoCommon) GetRemotes() (map[string]string, error) { var _ RepoStorage = &mockRepoStorage{} type mockRepoStorage struct { - localFs billy.Filesystem + localFs LocalStorage } func NewMockRepoStorage() *mockRepoStorage { - return &mockRepoStorage{localFs: memfs.New()} + return &mockRepoStorage{localFs: billyLocalStorage{Filesystem: memfs.New()}} } -func (m *mockRepoStorage) LocalStorage() billy.Filesystem { +func (m *mockRepoStorage) LocalStorage() LocalStorage { return m.localFs } @@ -204,6 +203,11 @@ func (m *mockIndex) DocCount() (uint64, error) { return uint64(len(*m)), nil } +func (m *mockIndex) Remove(id string) error { + delete(*m, id) + return nil +} + func (m *mockIndex) Clear() error { for k, _ := range *m { delete(*m, k) diff --git a/repository/repo.go b/repository/repo.go index 66baec65..c39051d5 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -76,10 +76,15 @@ type RepoCommon interface { GetRemotes() (map[string]string, error) } +type LocalStorage interface { + billy.Filesystem + RemoveAll(path 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 + LocalStorage() LocalStorage } // RepoIndex gives access to full-text search indexes @@ -103,6 +108,9 @@ type Index interface { // DocCount returns the number of document in the index. DocCount() (uint64, error) + // Remove delete one document in the index. + Remove(id string) error + // Clear empty the index. Clear() error diff --git a/repository/repo_testing.go b/repository/repo_testing.go index 821eb762..3dd702f4 100644 --- a/repository/repo_testing.go +++ b/repository/repo_testing.go @@ -2,6 +2,7 @@ package repository import ( "math/rand" + "os" "testing" "github.com/ProtonMail/go-crypto/openpgp" @@ -10,8 +11,6 @@ import ( "github.com/MichaelMure/git-bug/util/lamport" ) -// TODO: add tests for RepoStorage - type RepoCreator func(t testing.TB, bare bool) TestedRepo // Test suite for a Repo implementation @@ -32,6 +31,10 @@ func RepoTest(t *testing.T, creator RepoCreator) { RepoConfigTest(t, repo) }) + t.Run("Storage", func(t *testing.T) { + RepoStorageTest(t, repo) + }) + t.Run("Index", func(t *testing.T) { RepoIndexTest(t, repo) }) @@ -48,6 +51,36 @@ func RepoConfigTest(t *testing.T, repo RepoConfig) { testConfig(t, repo.LocalConfig()) } +func RepoStorageTest(t *testing.T, repo RepoStorage) { + storage := repo.LocalStorage() + + err := storage.MkdirAll("foo/bar", 0755) + require.NoError(t, err) + + f, err := storage.Create("foo/bar/foofoo") + require.NoError(t, err) + + _, err = f.Write([]byte("hello")) + require.NoError(t, err) + + err = f.Close() + require.NoError(t, err) + + // remove all + err = storage.RemoveAll(".") + require.NoError(t, err) + + fi, err := storage.ReadDir(".") + // a real FS would remove the root directory with RemoveAll and subsequent call would fail + // a memory FS would still have a virtual root and subsequent call would succeed + // not ideal, but will do for now + if err == nil { + require.Empty(t, fi) + } else { + require.True(t, os.IsNotExist(err)) + } +} + func randomHash() Hash { var letterRunes = "abcdef0123456789" b := make([]byte, idLengthSHA256) |