aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bridge/gitlab/event.go4
-rw-r--r--bridge/gitlab/import.go3
-rw-r--r--cache/bug_subcache.go1
-rw-r--r--cache/identity_subcache.go3
-rw-r--r--cache/repo_cache.go28
-rw-r--r--cache/repo_cache_common.go26
-rw-r--r--cache/repo_cache_test.go33
-rw-r--r--cache/subcache.go156
-rw-r--r--commands/bridge/bridge.go14
-rw-r--r--commands/bridge/bridge_auth.go10
-rw-r--r--commands/bridge/bridge_auth_addtoken.go3
-rw-r--r--commands/bridge/bridge_auth_rm.go4
-rw-r--r--commands/bridge/bridge_auth_show.go4
-rw-r--r--commands/bridge/bridge_new.go3
-rw-r--r--commands/bridge/bridge_pull.go3
-rw-r--r--commands/bridge/bridge_push.go4
-rw-r--r--commands/bridge/bridge_rm.go4
-rw-r--r--commands/bug/bug.go151
-rw-r--r--commands/bug/bug_comment.go8
-rw-r--r--commands/bug/bug_comment_add.go3
-rw-r--r--commands/bug/bug_comment_edit.go3
-rw-r--r--commands/bug/bug_comment_test.go6
-rw-r--r--commands/bug/bug_deselect.go4
-rw-r--r--commands/bug/bug_label.go8
-rw-r--r--commands/bug/bug_label_new.go4
-rw-r--r--commands/bug/bug_label_rm.go4
-rw-r--r--commands/bug/bug_new.go3
-rw-r--r--commands/bug/bug_rm.go4
-rw-r--r--commands/bug/bug_select.go4
-rw-r--r--commands/bug/bug_show.go3
-rw-r--r--commands/bug/bug_status.go8
-rw-r--r--commands/bug/bug_status_close.go4
-rw-r--r--commands/bug/bug_status_open.go4
-rw-r--r--commands/bug/bug_test.go51
-rw-r--r--commands/bug/bug_title.go6
-rw-r--r--commands/bug/bug_title_edit.go3
-rw-r--r--commands/commands.go3
-rw-r--r--commands/execenv/env.go194
-rw-r--r--commands/execenv/env_test.go21
-rw-r--r--commands/execenv/env_testing.go50
-rw-r--r--commands/execenv/loading.go169
-rw-r--r--commands/label.go4
-rw-r--r--commands/pull.go4
-rw-r--r--commands/push.go4
-rw-r--r--commands/root.go23
-rw-r--r--commands/termui.go4
-rw-r--r--commands/user/user.go9
-rw-r--r--commands/user/user_adopt.go4
-rw-r--r--commands/user/user_new.go4
-rw-r--r--commands/user/user_show.go3
-rw-r--r--commands/version.go3
-rw-r--r--commands/webui.go20
-rw-r--r--commands/wipe.go56
-rw-r--r--doc/man/git-bug-bug.12
-rw-r--r--doc/man/git-bug-wipe.127
-rw-r--r--doc/man/git-bug.12
-rw-r--r--doc/md/git-bug.md1
-rw-r--r--doc/md/git-bug_bug.md2
-rw-r--r--doc/md/git-bug_wipe.md18
-rw-r--r--entities/bug/bug_actions.go6
-rw-r--r--entities/identity/identity.go60
-rw-r--r--entities/identity/identity_actions.go71
-rw-r--r--entities/identity/identity_test.go2
-rw-r--r--entities/identity/identity_user.go4
-rw-r--r--entity/dag/entity.go10
-rw-r--r--entity/dag/entity_actions.go16
-rw-r--r--entity/dag/entity_actions_test.go31
-rw-r--r--entity/id.go4
-rw-r--r--entity/id_interleaved.go2
-rw-r--r--entity/streamed.go7
-rw-r--r--go.mod14
-rw-r--r--go.sum20
-rw-r--r--repository/gogit.go11
-rw-r--r--repository/index_bleve.go7
-rw-r--r--repository/localstorage_billy.go16
-rw-r--r--repository/mock_repo.go12
-rw-r--r--repository/repo.go10
-rw-r--r--repository/repo_testing.go37
78 files changed, 1041 insertions, 510 deletions
diff --git a/bridge/gitlab/event.go b/bridge/gitlab/event.go
index 483af921..0653269d 100644
--- a/bridge/gitlab/event.go
+++ b/bridge/gitlab/event.go
@@ -40,6 +40,7 @@ const (
EventRemoveLabel
EventMentionedInIssue
EventMentionedInMergeRequest
+ EventMentionedInCommit
)
var _ Event = &NoteEvent{}
@@ -97,6 +98,9 @@ func (n NoteEvent) Kind() EventKind {
case strings.HasPrefix(n.Body, "mentioned in merge request"):
return EventMentionedInMergeRequest
+ case strings.HasPrefix(n.Body, "mentioned in commit"):
+ return EventMentionedInCommit
+
default:
return EventUnknown
}
diff --git a/bridge/gitlab/import.go b/bridge/gitlab/import.go
index 5947fb60..e4330b4c 100644
--- a/bridge/gitlab/import.go
+++ b/bridge/gitlab/import.go
@@ -324,7 +324,8 @@ func (gi *gitlabImporter) ensureIssueEvent(repo *cache.RepoCache, b *cache.BugCa
EventLocked,
EventUnlocked,
EventMentionedInIssue,
- EventMentionedInMergeRequest:
+ EventMentionedInMergeRequest,
+ EventMentionedInCommit:
return nil
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/bridge/bridge.go b/commands/bridge/bridge.go
index 980a38e2..9f7c5e50 100644
--- a/commands/bridge/bridge.go
+++ b/commands/bridge/bridge.go
@@ -7,9 +7,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
)
-func NewBridgeCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func NewBridgeCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "bridge",
Short: "List bridges to other bug trackers",
@@ -20,11 +18,11 @@ func NewBridgeCommand() *cobra.Command {
Args: cobra.NoArgs,
}
- cmd.AddCommand(newBridgeAuthCommand())
- cmd.AddCommand(newBridgeNewCommand())
- cmd.AddCommand(newBridgePullCommand())
- cmd.AddCommand(newBridgePushCommand())
- cmd.AddCommand(newBridgeRm())
+ cmd.AddCommand(newBridgeAuthCommand(env))
+ cmd.AddCommand(newBridgeNewCommand(env))
+ cmd.AddCommand(newBridgePullCommand(env))
+ cmd.AddCommand(newBridgePushCommand(env))
+ cmd.AddCommand(newBridgeRm(env))
return cmd
}
diff --git a/commands/bridge/bridge_auth.go b/commands/bridge/bridge_auth.go
index 52e063e6..f27004e0 100644
--- a/commands/bridge/bridge_auth.go
+++ b/commands/bridge/bridge_auth.go
@@ -12,9 +12,7 @@ import (
"github.com/MichaelMure/git-bug/util/colors"
)
-func newBridgeAuthCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBridgeAuthCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "auth",
Short: "List all known bridge authentication credentials",
@@ -25,9 +23,9 @@ func newBridgeAuthCommand() *cobra.Command {
Args: cobra.NoArgs,
}
- cmd.AddCommand(newBridgeAuthAddTokenCommand())
- cmd.AddCommand(newBridgeAuthRm())
- cmd.AddCommand(newBridgeAuthShow())
+ cmd.AddCommand(newBridgeAuthAddTokenCommand(env))
+ cmd.AddCommand(newBridgeAuthRm(env))
+ cmd.AddCommand(newBridgeAuthShow(env))
return cmd
}
diff --git a/commands/bridge/bridge_auth_addtoken.go b/commands/bridge/bridge_auth_addtoken.go
index 2992fa63..5af27728 100644
--- a/commands/bridge/bridge_auth_addtoken.go
+++ b/commands/bridge/bridge_auth_addtoken.go
@@ -24,8 +24,7 @@ type bridgeAuthAddTokenOptions struct {
user string
}
-func newBridgeAuthAddTokenCommand() *cobra.Command {
- env := execenv.NewEnv()
+func newBridgeAuthAddTokenCommand(env *execenv.Env) *cobra.Command {
options := bridgeAuthAddTokenOptions{}
cmd := &cobra.Command{
diff --git a/commands/bridge/bridge_auth_rm.go b/commands/bridge/bridge_auth_rm.go
index d58ca63e..33253e26 100644
--- a/commands/bridge/bridge_auth_rm.go
+++ b/commands/bridge/bridge_auth_rm.go
@@ -8,9 +8,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
)
-func newBridgeAuthRm() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBridgeAuthRm(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "rm BRIDGE_ID",
Short: "Remove a credential",
diff --git a/commands/bridge/bridge_auth_show.go b/commands/bridge/bridge_auth_show.go
index d373273d..25c517d3 100644
--- a/commands/bridge/bridge_auth_show.go
+++ b/commands/bridge/bridge_auth_show.go
@@ -13,9 +13,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
)
-func newBridgeAuthShow() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBridgeAuthShow(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "show",
Short: "Display an authentication credential",
diff --git a/commands/bridge/bridge_new.go b/commands/bridge/bridge_new.go
index 2c51d9ef..07a555da 100644
--- a/commands/bridge/bridge_new.go
+++ b/commands/bridge/bridge_new.go
@@ -26,8 +26,7 @@ type bridgeNewOptions struct {
nonInteractive bool
}
-func newBridgeNewCommand() *cobra.Command {
- env := execenv.NewEnv()
+func newBridgeNewCommand(env *execenv.Env) *cobra.Command {
options := bridgeNewOptions{}
cmd := &cobra.Command{
diff --git a/commands/bridge/bridge_pull.go b/commands/bridge/bridge_pull.go
index d1fc279a..03f4929a 100644
--- a/commands/bridge/bridge_pull.go
+++ b/commands/bridge/bridge_pull.go
@@ -23,8 +23,7 @@ type bridgePullOptions struct {
noResume bool
}
-func newBridgePullCommand() *cobra.Command {
- env := execenv.NewEnv()
+func newBridgePullCommand(env *execenv.Env) *cobra.Command {
options := bridgePullOptions{}
cmd := &cobra.Command{
diff --git a/commands/bridge/bridge_push.go b/commands/bridge/bridge_push.go
index 51baed4d..08f9b872 100644
--- a/commands/bridge/bridge_push.go
+++ b/commands/bridge/bridge_push.go
@@ -15,9 +15,7 @@ import (
"github.com/MichaelMure/git-bug/util/interrupt"
)
-func newBridgePushCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBridgePushCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "push [NAME]",
Short: "Push updates to remote bug tracker",
diff --git a/commands/bridge/bridge_rm.go b/commands/bridge/bridge_rm.go
index 5d8d23c5..f6279ade 100644
--- a/commands/bridge/bridge_rm.go
+++ b/commands/bridge/bridge_rm.go
@@ -8,9 +8,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
)
-func newBridgeRm() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBridgeRm(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "rm NAME",
Short: "Delete a configured bridge",
diff --git a/commands/bug/bug.go b/commands/bug/bug.go
index a5ce11ed..55a1fa59 100644
--- a/commands/bug/bug.go
+++ b/commands/bug/bug.go
@@ -15,26 +15,27 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
"github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entities/common"
+ "github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/query"
"github.com/MichaelMure/git-bug/util/colors"
)
type bugOptions struct {
- statusQuery []string
- authorQuery []string
- metadataQuery []string
- participantQuery []string
- actorQuery []string
- labelQuery []string
- titleQuery []string
- noQuery []string
- sortBy string
- sortDirection string
- outputFormat string
+ statusQuery []string
+ authorQuery []string
+ metadataQuery []string
+ participantQuery []string
+ actorQuery []string
+ labelQuery []string
+ titleQuery []string
+ noQuery []string
+ sortBy string
+ sortDirection string
+ outputFormat string
+ outputFormatChanged bool
}
-func NewBugCommand() *cobra.Command {
- env := execenv.NewEnv()
+func NewBugCommand(env *execenv.Env) *cobra.Command {
options := bugOptions{}
cmd := &cobra.Command{
@@ -57,6 +58,7 @@ git bug status:open --by creation "foo bar" baz
`,
PreRunE: execenv.LoadBackend(env),
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
+ options.outputFormatChanged = cmd.Flags().Changed("format")
return runBug(env, options, args)
}),
ValidArgsFunction: completion.Ls(env),
@@ -94,9 +96,9 @@ git bug status:open --by creation "foo bar" baz
"Select the sorting direction. Valid values are [asc,desc]")
cmd.RegisterFlagCompletionFunc("direction", completion.From([]string{"asc", "desc"}))
flags.StringVarP(&options.outputFormat, "format", "f", "default",
- "Select the output formatting style. Valid values are [default,plain,compact,id,json,org-mode]")
+ "Select the output formatting style. Valid values are [default,plain,id,json,org-mode]")
cmd.RegisterFlagCompletionFunc("format",
- completion.From([]string{"default", "plain", "compact", "id", "json", "org-mode"}))
+ completion.From([]string{"default", "plain", "id", "json", "org-mode"}))
const selectGroup = "select"
cmd.AddGroup(&cobra.Group{ID: selectGroup, Title: "Implicit selection"})
@@ -106,16 +108,16 @@ git bug status:open --by creation "foo bar" baz
child.GroupID = groupID
}
- addCmdWithGroup(newBugDeselectCommand(), selectGroup)
- addCmdWithGroup(newBugSelectCommand(), selectGroup)
+ addCmdWithGroup(newBugDeselectCommand(env), selectGroup)
+ addCmdWithGroup(newBugSelectCommand(env), selectGroup)
- cmd.AddCommand(newBugCommentCommand())
- cmd.AddCommand(newBugLabelCommand())
- cmd.AddCommand(newBugNewCommand())
- cmd.AddCommand(newBugRmCommand())
- cmd.AddCommand(newBugShowCommand())
- cmd.AddCommand(newBugStatusCommand())
- cmd.AddCommand(newBugTitleCommand())
+ cmd.AddCommand(newBugCommentCommand(env))
+ cmd.AddCommand(newBugLabelCommand(env))
+ cmd.AddCommand(newBugNewCommand(env))
+ cmd.AddCommand(newBugRmCommand(env))
+ cmd.AddCommand(newBugShowCommand(env))
+ cmd.AddCommand(newBugStatusCommand(env))
+ cmd.AddCommand(newBugTitleCommand(env))
return cmd
}
@@ -146,28 +148,33 @@ func runBug(env *execenv.Env, opts bugOptions, args []string) error {
return err
}
- bugExcerpt := make([]*cache.BugExcerpt, len(allIds))
+ excerpts := make([]*cache.BugExcerpt, len(allIds))
for i, id := range allIds {
b, err := env.Backend.Bugs().ResolveExcerpt(id)
if err != nil {
return err
}
- bugExcerpt[i] = b
+ excerpts[i] = b
}
switch opts.outputFormat {
- case "org-mode":
- return bugsOrgmodeFormatter(env, bugExcerpt)
+ case "default":
+ if opts.outputFormatChanged {
+ return bugsDefaultFormatter(env, excerpts)
+ }
+ if env.Out.IsTerminal() {
+ return bugsDefaultFormatter(env, excerpts)
+ } else {
+ return bugsPlainFormatter(env, excerpts)
+ }
+ case "id":
+ return bugsIDFormatter(env, excerpts)
case "plain":
- return bugsPlainFormatter(env, bugExcerpt)
+ return bugsPlainFormatter(env, excerpts)
case "json":
- return bugsJsonFormatter(env, bugExcerpt)
- case "compact":
- return bugsCompactFormatter(env, bugExcerpt)
- case "id":
- return bugsIDFormatter(env, bugExcerpt)
- case "default":
- return bugsDefaultFormatter(env, bugExcerpt)
+ return bugsJsonFormatter(env, excerpts)
+ case "org-mode":
+ return bugsOrgmodeFormatter(env, excerpts)
default:
return fmt.Errorf("unknown format %s", opts.outputFormat)
}
@@ -186,9 +193,9 @@ func repairQuery(args []string) string {
return strings.Join(args, " ")
}
-func bugsJsonFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
- jsonBugs := make([]cmdjson.BugExcerpt, len(bugExcerpts))
- for i, b := range bugExcerpts {
+func bugsJsonFormatter(env *execenv.Env, excerpts []*cache.BugExcerpt) error {
+ jsonBugs := make([]cmdjson.BugExcerpt, len(excerpts))
+ for i, b := range excerpts {
jsonBug, err := cmdjson.NewBugExcerpt(env.Backend, b)
if err != nil {
return err
@@ -198,42 +205,34 @@ func bugsJsonFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error
return env.Out.PrintJSON(jsonBugs)
}
-func bugsCompactFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
- for _, b := range bugExcerpts {
- author, err := env.Backend.Identities().ResolveExcerpt(b.AuthorId)
- if err != nil {
- return err
- }
-
- var labelsTxt strings.Builder
- for _, l := range b.Labels {
- lc256 := l.Color().Term256()
- labelsTxt.WriteString(lc256.Escape())
- labelsTxt.WriteString("◼")
- labelsTxt.WriteString(lc256.Unescape())
- }
-
- env.Out.Printf("%s %s %s %s %s\n",
- colors.Cyan(b.Id().Human()),
- colors.Yellow(b.Status),
- text.LeftPadMaxLine(strings.TrimSpace(b.Title), 40, 0),
- text.LeftPadMaxLine(labelsTxt.String(), 5, 0),
- colors.Magenta(text.TruncateMax(author.DisplayName(), 15)),
- )
+func bugsIDFormatter(env *execenv.Env, excerpts []*cache.BugExcerpt) error {
+ for _, b := range excerpts {
+ env.Out.Println(b.Id().String())
}
+
return nil
}
-func bugsIDFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
- for _, b := range bugExcerpts {
- env.Out.Println(b.Id().String())
+func bugsDefaultFormatter(env *execenv.Env, excerpts []*cache.BugExcerpt) error {
+ width := env.Out.Width()
+ widthId := entity.HumanIdLength
+ widthStatus := len("closed")
+ widthComment := 6
+
+ widthRemaining := width -
+ widthId - 1 -
+ widthStatus - 1 -
+ widthComment - 1
+
+ widthTitle := int(float32(widthRemaining-3) * 0.7)
+ if widthTitle < 0 {
+ widthTitle = 0
}
- return nil
-}
+ widthRemaining = widthRemaining - widthTitle - 3 - 2
+ widthAuthor := widthRemaining
-func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
- for _, b := range bugExcerpts {
+ for _, b := range excerpts {
author, err := env.Backend.Identities().ResolveExcerpt(b.AuthorId)
if err != nil {
return err
@@ -249,8 +248,8 @@ func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
// truncate + pad if needed
labelsFmt := text.TruncateMax(labelsTxt.String(), 10)
- titleFmt := text.LeftPadMaxLine(strings.TrimSpace(b.Title), 50-text.Len(labelsFmt), 0)
- authorFmt := text.LeftPadMaxLine(author.DisplayName(), 15, 0)
+ titleFmt := text.LeftPadMaxLine(strings.TrimSpace(b.Title), widthTitle-text.Len(labelsFmt), 0)
+ authorFmt := text.LeftPadMaxLine(author.DisplayName(), widthAuthor, 0)
comments := fmt.Sprintf("%3d 💬", b.LenComments-1)
if b.LenComments-1 <= 0 {
@@ -260,7 +259,7 @@ func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
comments = " ∞ 💬"
}
- env.Out.Printf("%s\t%s\t%s\t%s\t%s\n",
+ env.Out.Printf("%s\t%s\t%s %s %s\n",
colors.Cyan(b.Id().Human()),
colors.Yellow(b.Status),
titleFmt+labelsFmt,
@@ -271,14 +270,14 @@ func bugsDefaultFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
return nil
}
-func bugsPlainFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
- for _, b := range bugExcerpts {
- env.Out.Printf("%s [%s] %s\n", b.Id().Human(), b.Status, strings.TrimSpace(b.Title))
+func bugsPlainFormatter(env *execenv.Env, excerpts []*cache.BugExcerpt) error {
+ for _, b := range excerpts {
+ env.Out.Printf("%s\t%s\t%s\n", b.Id().Human(), b.Status, strings.TrimSpace(b.Title))
}
return nil
}
-func bugsOrgmodeFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) error {
+func bugsOrgmodeFormatter(env *execenv.Env, excerpts []*cache.BugExcerpt) error {
// see https://orgmode.org/manual/Tags.html
orgTagRe := regexp.MustCompile("[^[:alpha:]_@]")
formatTag := func(l bug.Label) string {
@@ -291,7 +290,7 @@ func bugsOrgmodeFormatter(env *execenv.Env, bugExcerpts []*cache.BugExcerpt) err
env.Out.Println("#+TODO: OPEN | CLOSED")
- for _, b := range bugExcerpts {
+ for _, b := range excerpts {
status := strings.ToUpper(b.Status.String())
var title string
diff --git a/commands/bug/bug_comment.go b/commands/bug/bug_comment.go
index 4dc8dc1f..5cb8ff17 100644
--- a/commands/bug/bug_comment.go
+++ b/commands/bug/bug_comment.go
@@ -8,9 +8,7 @@ import (
"github.com/MichaelMure/git-bug/util/colors"
)
-func newBugCommentCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBugCommentCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "comment [BUG_ID]",
Short: "List a bug's comments",
@@ -21,8 +19,8 @@ func newBugCommentCommand() *cobra.Command {
ValidArgsFunction: BugCompletion(env),
}
- cmd.AddCommand(newBugCommentNewCommand())
- cmd.AddCommand(newBugCommentEditCommand())
+ cmd.AddCommand(newBugCommentNewCommand(env))
+ cmd.AddCommand(newBugCommentEditCommand(env))
return cmd
}
diff --git a/commands/bug/bug_comment_add.go b/commands/bug/bug_comment_add.go
index ff406b4f..152a1893 100644
--- a/commands/bug/bug_comment_add.go
+++ b/commands/bug/bug_comment_add.go
@@ -14,8 +14,7 @@ type bugCommentNewOptions struct {
nonInteractive bool
}
-func newBugCommentNewCommand() *cobra.Command {
- env := execenv.NewEnv()
+func newBugCommentNewCommand(env *execenv.Env) *cobra.Command {
options := bugCommentNewOptions{}
cmd := &cobra.Command{
diff --git a/commands/bug/bug_comment_edit.go b/commands/bug/bug_comment_edit.go
index ded3d82a..e6f8d667 100644
--- a/commands/bug/bug_comment_edit.go
+++ b/commands/bug/bug_comment_edit.go
@@ -13,8 +13,7 @@ type bugCommentEditOptions struct {
nonInteractive bool
}
-func newBugCommentEditCommand() *cobra.Command {
- env := execenv.NewEnv()
+func newBugCommentEditCommand(env *execenv.Env) *cobra.Command {
options := bugCommentEditOptions{}
cmd := &cobra.Command{
diff --git a/commands/bug/bug_comment_test.go b/commands/bug/bug_comment_test.go
index c1dc9952..ecc1c5f6 100644
--- a/commands/bug/bug_comment_test.go
+++ b/commands/bug/bug_comment_test.go
@@ -2,7 +2,7 @@ package bugcmd
import (
"fmt"
- "io/ioutil"
+ "os"
"strings"
"testing"
"time"
@@ -143,7 +143,7 @@ func requireCommentsEqual(t *testing.T, golden string, env *execenv.Env) {
t.Log("Got here")
for i, comment := range comments {
fileName := fmt.Sprintf(goldenFilePattern, golden, i)
- require.NoError(t, ioutil.WriteFile(fileName, []byte(comment.message), 0644))
+ require.NoError(t, os.WriteFile(fileName, []byte(comment.message), 0644))
}
}
@@ -157,7 +157,7 @@ func requireCommentsEqual(t *testing.T, golden string, env *execenv.Env) {
require.Equal(t, date.Add(time.Duration(i)*time.Minute), comment.date)
fileName := fmt.Sprintf(goldenFilePattern, golden, i)
- exp, err := ioutil.ReadFile(fileName)
+ exp, err := os.ReadFile(fileName)
require.NoError(t, err)
require.Equal(t, strings.ReplaceAll(string(exp), "\r", ""), strings.ReplaceAll(comment.message, "\r", ""))
}
diff --git a/commands/bug/bug_deselect.go b/commands/bug/bug_deselect.go
index 090a7bf2..5e9acc60 100644
--- a/commands/bug/bug_deselect.go
+++ b/commands/bug/bug_deselect.go
@@ -8,9 +8,7 @@ import (
"github.com/MichaelMure/git-bug/entities/bug"
)
-func newBugDeselectCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBugDeselectCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "deselect",
Short: "Clear the implicitly selected bug",
diff --git a/commands/bug/bug_label.go b/commands/bug/bug_label.go
index e6d0e603..554496e3 100644
--- a/commands/bug/bug_label.go
+++ b/commands/bug/bug_label.go
@@ -6,9 +6,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
)
-func newBugLabelCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBugLabelCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "label [BUG_ID]",
Short: "Display labels of a bug",
@@ -19,8 +17,8 @@ func newBugLabelCommand() *cobra.Command {
ValidArgsFunction: BugCompletion(env),
}
- cmd.AddCommand(newBugLabelNewCommand())
- cmd.AddCommand(newBugLabelRmCommand())
+ cmd.AddCommand(newBugLabelNewCommand(env))
+ cmd.AddCommand(newBugLabelRmCommand(env))
return cmd
}
diff --git a/commands/bug/bug_label_new.go b/commands/bug/bug_label_new.go
index aa4f9463..1e1f2d4f 100644
--- a/commands/bug/bug_label_new.go
+++ b/commands/bug/bug_label_new.go
@@ -7,9 +7,7 @@ import (
"github.com/MichaelMure/git-bug/util/text"
)
-func newBugLabelNewCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBugLabelNewCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "new [BUG_ID] LABEL...",
Short: "Add a label to a bug",
diff --git a/commands/bug/bug_label_rm.go b/commands/bug/bug_label_rm.go
index 18510bbd..6dda007c 100644
--- a/commands/bug/bug_label_rm.go
+++ b/commands/bug/bug_label_rm.go
@@ -7,9 +7,7 @@ import (
"github.com/MichaelMure/git-bug/util/text"
)
-func newBugLabelRmCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBugLabelRmCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "rm [BUG_ID] LABEL...",
Short: "Remove a label from a bug",
diff --git a/commands/bug/bug_new.go b/commands/bug/bug_new.go
index 9ef288e9..e66967f9 100644
--- a/commands/bug/bug_new.go
+++ b/commands/bug/bug_new.go
@@ -15,8 +15,7 @@ type bugNewOptions struct {
nonInteractive bool
}
-func newBugNewCommand() *cobra.Command {
- env := execenv.NewEnv()
+func newBugNewCommand(env *execenv.Env) *cobra.Command {
options := bugNewOptions{}
cmd := &cobra.Command{
diff --git a/commands/bug/bug_rm.go b/commands/bug/bug_rm.go
index 386c57ec..b9d3d525 100644
--- a/commands/bug/bug_rm.go
+++ b/commands/bug/bug_rm.go
@@ -8,9 +8,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
)
-func newBugRmCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBugRmCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "rm BUG_ID",
Short: "Remove an existing bug",
diff --git a/commands/bug/bug_select.go b/commands/bug/bug_select.go
index bfad899d..652c61ea 100644
--- a/commands/bug/bug_select.go
+++ b/commands/bug/bug_select.go
@@ -15,9 +15,7 @@ func ResolveSelected(repo *cache.RepoCache, args []string) (*cache.BugCache, []s
return _select.Resolve[*cache.BugCache](repo, bug.Typename, bug.Namespace, repo.Bugs(), args)
}
-func newBugSelectCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBugSelectCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "select BUG_ID",
Short: "Select a bug for implicit use in future commands",
diff --git a/commands/bug/bug_show.go b/commands/bug/bug_show.go
index 9f80120c..9a03c9a3 100644
--- a/commands/bug/bug_show.go
+++ b/commands/bug/bug_show.go
@@ -19,8 +19,7 @@ type bugShowOptions struct {
format string
}
-func newBugShowCommand() *cobra.Command {
- env := execenv.NewEnv()
+func newBugShowCommand(env *execenv.Env) *cobra.Command {
options := bugShowOptions{}
cmd := &cobra.Command{
diff --git a/commands/bug/bug_status.go b/commands/bug/bug_status.go
index 807a9a60..59bef3fd 100644
--- a/commands/bug/bug_status.go
+++ b/commands/bug/bug_status.go
@@ -6,9 +6,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
)
-func newBugStatusCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBugStatusCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "status [BUG_ID]",
Short: "Display the status of a bug",
@@ -19,8 +17,8 @@ func newBugStatusCommand() *cobra.Command {
ValidArgsFunction: BugCompletion(env),
}
- cmd.AddCommand(newBugStatusCloseCommand())
- cmd.AddCommand(newBugStatusOpenCommand())
+ cmd.AddCommand(newBugStatusCloseCommand(env))
+ cmd.AddCommand(newBugStatusOpenCommand(env))
return cmd
}
diff --git a/commands/bug/bug_status_close.go b/commands/bug/bug_status_close.go
index e52959b2..1d06007b 100644
--- a/commands/bug/bug_status_close.go
+++ b/commands/bug/bug_status_close.go
@@ -6,9 +6,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
)
-func newBugStatusCloseCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBugStatusCloseCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "close [BUG_ID]",
Short: "Mark a bug as closed",
diff --git a/commands/bug/bug_status_open.go b/commands/bug/bug_status_open.go
index 74177974..e99d2db0 100644
--- a/commands/bug/bug_status_open.go
+++ b/commands/bug/bug_status_open.go
@@ -6,9 +6,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
)
-func newBugStatusOpenCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBugStatusOpenCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "open [BUG_ID]",
Short: "Mark a bug as open",
diff --git a/commands/bug/bug_test.go b/commands/bug/bug_test.go
index cb6a4373..6b0aa4e0 100644
--- a/commands/bug/bug_test.go
+++ b/commands/bug/bug_test.go
@@ -2,7 +2,6 @@ package bugcmd
import (
"encoding/json"
- "fmt"
"testing"
"github.com/stretchr/testify/require"
@@ -61,44 +60,34 @@ $`
format string
exp string
}{
- {"default", "^[0-9a-f]{7}\topen\tthis is a bug title \tJohn Doe \t\n$"},
- {"plain", "^[0-9a-f]{7} \\[open\\] this is a bug title\n$"},
- {"compact", "^[0-9a-f]{7} open this is a bug title John Doe\n$"},
+ {"default", "^[0-9a-f]{7}\topen\tthis is a bug title John Doe \n$"},
+ {"plain", "^[0-9a-f]{7}\topen\tthis is a bug title\n$"},
{"id", "^[0-9a-f]{64}\n$"},
{"org-mode", expOrgMode},
+ {"json", ".*"},
}
for _, testcase := range cases {
- opts := bugOptions{
- sortDirection: "asc",
- sortBy: "creation",
- outputFormat: testcase.format,
- }
-
- name := fmt.Sprintf("with %s format", testcase.format)
-
- t.Run(name, func(t *testing.T) {
+ t.Run(testcase.format, func(t *testing.T) {
env, _ := testenv.NewTestEnvAndBug(t)
+ opts := bugOptions{
+ sortDirection: "asc",
+ sortBy: "creation",
+ outputFormat: testcase.format,
+ outputFormatChanged: true, // disable auto-detect
+ }
+
require.NoError(t, runBug(env, opts, []string{}))
- require.Regexp(t, testcase.exp, env.Out.String())
+
+ switch testcase.format {
+ case "json":
+ var bugs []cmdjson.BugExcerpt
+ require.NoError(t, json.Unmarshal(env.Out.Bytes(), &bugs))
+ require.Len(t, bugs, 1)
+ default:
+ require.Regexp(t, testcase.exp, env.Out.String())
+ }
})
}
-
- t.Run("with JSON format", func(t *testing.T) {
- opts := bugOptions{
- sortDirection: "asc",
- sortBy: "creation",
- outputFormat: "json",
- }
-
- env, _ := testenv.NewTestEnvAndBug(t)
-
- require.NoError(t, runBug(env, opts, []string{}))
-
- var bugs []cmdjson.BugExcerpt
- require.NoError(t, json.Unmarshal(env.Out.Bytes(), &bugs))
-
- require.Len(t, bugs, 1)
- })
}
diff --git a/commands/bug/bug_title.go b/commands/bug/bug_title.go
index e59a1fdc..47603410 100644
--- a/commands/bug/bug_title.go
+++ b/commands/bug/bug_title.go
@@ -6,9 +6,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
)
-func newBugTitleCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newBugTitleCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "title [BUG_ID]",
Short: "Display the title of a bug",
@@ -19,7 +17,7 @@ func newBugTitleCommand() *cobra.Command {
ValidArgsFunction: BugCompletion(env),
}
- cmd.AddCommand(newBugTitleEditCommand())
+ cmd.AddCommand(newBugTitleEditCommand(env))
return cmd
}
diff --git a/commands/bug/bug_title_edit.go b/commands/bug/bug_title_edit.go
index 59898530..fc60824f 100644
--- a/commands/bug/bug_title_edit.go
+++ b/commands/bug/bug_title_edit.go
@@ -13,8 +13,7 @@ type bugTitleEditOptions struct {
nonInteractive bool
}
-func newBugTitleEditCommand() *cobra.Command {
- env := execenv.NewEnv()
+func newBugTitleEditCommand(env *execenv.Env) *cobra.Command {
options := bugTitleEditOptions{}
cmd := &cobra.Command{
diff --git a/commands/commands.go b/commands/commands.go
index 7d2fc37d..173a0904 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -12,8 +12,7 @@ type commandOptions struct {
desc bool
}
-func newCommandsCommand() *cobra.Command {
- env := execenv.NewEnv()
+func newCommandsCommand(env *execenv.Env) *cobra.Command {
options := commandOptions{}
cmd := &cobra.Command{
diff --git a/commands/execenv/env.go b/commands/execenv/env.go
index 4be7c247..e693807e 100644
--- a/commands/execenv/env.go
+++ b/commands/execenv/env.go
@@ -6,12 +6,11 @@ import (
"io"
"os"
- "github.com/spf13/cobra"
+ "github.com/mattn/go-isatty"
+ "golang.org/x/term"
"github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/repository"
- "github.com/MichaelMure/git-bug/util/interrupt"
)
const RootCommandName = "git-bug"
@@ -22,6 +21,7 @@ const gitBugNamespace = "git-bug"
type Env struct {
Repo repository.ClockedRepo
Backend *cache.RepoCache
+ In In
Out Out
Err Out
}
@@ -29,18 +29,42 @@ type Env struct {
func NewEnv() *Env {
return &Env{
Repo: nil,
+ In: in{Reader: os.Stdin},
Out: out{Writer: os.Stdout},
Err: out{Writer: os.Stderr},
}
}
+type In interface {
+ io.Reader
+
+ // IsTerminal tells if the input is a user terminal (rather than a buffer,
+ // a pipe ...), which tells if we can use interactive features.
+ IsTerminal() bool
+
+ // ForceIsTerminal allow to force the returned value of IsTerminal
+ // This only works in test scenario.
+ ForceIsTerminal(value bool)
+}
+
type Out interface {
io.Writer
+
Printf(format string, a ...interface{})
Print(a ...interface{})
Println(a ...interface{})
PrintJSON(v interface{}) error
+ // IsTerminal tells if the output is a user terminal (rather than a buffer,
+ // a pipe ...), which tells if we can use colors and other interactive features.
+ IsTerminal() bool
+ // Width return the width of the attached terminal, or a good enough value.
+ Width() int
+
+ // 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
+
// String returns what have been written in the output before, as a string.
// This only works in test scenario.
String() string
@@ -50,6 +74,24 @@ type Out interface {
// Reset clear what has been recorded as written in the output before.
// This only works in test scenario.
Reset()
+ // ForceIsTerminal allow to force the returned value of IsTerminal
+ // This only works in test scenario.
+ ForceIsTerminal(value bool)
+}
+
+type in struct {
+ io.Reader
+}
+
+func (i in) IsTerminal() bool {
+ if f, ok := i.Reader.(*os.File); ok {
+ return isTerminal(f)
+ }
+ return false
+}
+
+func (i in) ForceIsTerminal(_ bool) {
+ panic("only work with a test env")
}
type out struct {
@@ -77,139 +119,43 @@ func (o out) PrintJSON(v interface{}) error {
return nil
}
-func (o out) String() string {
- panic("only work with a test env")
-}
-
-func (o out) Bytes() []byte {
- panic("only work with a test env")
-}
-
-func (o out) Reset() {
- panic("only work with a test env")
-}
-
-// 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 {
- cwd, err := os.Getwd()
- if err != nil {
- return fmt.Errorf("unable to get the current working directory: %q", err)
- }
-
- // Note: we are not loading clocks here because we assume that LoadRepo is only used
- // when we don't manipulate entities, or as a child call of LoadBackend which will
- // read all clocks anyway.
- env.Repo, err = repository.OpenGoGitRepo(cwd, gitBugNamespace, nil)
- if err == repository.ErrNotARepo {
- return fmt.Errorf("%s must be run from within a git Repo", RootCommandName)
- }
- if err != nil {
- return err
- }
-
- return nil
+func (o out) IsTerminal() bool {
+ if f, ok := o.Writer.(*os.File); ok {
+ return isTerminal(f)
}
+ return false
}
-// LoadRepoEnsureUser is the same as LoadRepo, but also ensure that the user has configured
-// an identity. Use this pre-run function when an error after using the configured user won't
-// do.
-func LoadRepoEnsureUser(env *Env) func(*cobra.Command, []string) error {
- return func(cmd *cobra.Command, args []string) error {
- err := LoadRepo(env)(cmd, args)
- if err != nil {
- return err
- }
-
- _, err = identity.GetUserIdentity(env.Repo)
- if err != nil {
- return err
+func (o out) Width() int {
+ if f, ok := o.Raw().(*os.File); ok {
+ width, _, err := term.GetSize(int(f.Fd()))
+ if err == nil {
+ return width
}
-
- return nil
}
+ return 80
}
-// LoadBackend is a pre-run function that load the repository and the Backend for use in a command
-// When using this function you also need to use CloseBackend as a post-run
-func LoadBackend(env *Env) func(*cobra.Command, []string) error {
- return func(cmd *cobra.Command, args []string) error {
- err := LoadRepo(env)(cmd, args)
- if err != nil {
- return err
- }
-
- 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)
- }
- }
-
- cleaner := func(env *Env) interrupt.CleanerFunc {
- return func() error {
- if env.Backend != nil {
- err := env.Backend.Close()
- env.Backend = nil
- return err
- }
- return nil
- }
- }
-
- // Cleanup properly on interrupt
- interrupt.RegisterCleaner(cleaner(env))
- return nil
- }
+func (o out) Raw() io.Writer {
+ return o.Writer
}
-// LoadBackendEnsureUser is the same as LoadBackend, but also ensure that the user has configured
-// an identity. Use this pre-run function when an error after using the configured user won't
-// do.
-func LoadBackendEnsureUser(env *Env) func(*cobra.Command, []string) error {
- return func(cmd *cobra.Command, args []string) error {
- err := LoadBackend(env)(cmd, args)
- if err != nil {
- return err
- }
-
- _, err = identity.GetUserIdentity(env.Repo)
- if err != nil {
- return err
- }
+func (o out) String() string {
+ panic("only work with a test env")
+}
- return nil
- }
+func (o out) Bytes() []byte {
+ panic("only work with a test env")
}
-// CloseBackend is a wrapper for a RunE function that will close the Backend properly
-// if it has been opened.
-// This wrapper style is necessary because a Cobra PostE function does not run if RunE return an error.
-func CloseBackend(env *Env, runE func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error {
- return func(cmd *cobra.Command, args []string) error {
- errRun := runE(cmd, args)
+func (o out) Reset() {
+ panic("only work with a test env")
+}
- if env.Backend == nil {
- return nil
- }
- err := env.Backend.Close()
- env.Backend = nil
+func (o out) ForceIsTerminal(_ bool) {
+ panic("only work with a test env")
+}
- // prioritize the RunE error
- if errRun != nil {
- return errRun
- }
- return err
- }
+func isTerminal(file *os.File) bool {
+ return isatty.IsTerminal(file.Fd()) || isatty.IsCygwinTerminal(file.Fd())
}
diff --git a/commands/execenv/env_test.go b/commands/execenv/env_test.go
new file mode 100644
index 00000000..3fc6e581
--- /dev/null
+++ b/commands/execenv/env_test.go
@@ -0,0 +1,21 @@
+package execenv
+
+import (
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestIsTerminal(t *testing.T) {
+ // easy way to get a reader and a writer
+ r, w, err := os.Pipe()
+ require.NoError(t, err)
+
+ require.False(t, isTerminal(r))
+ require.False(t, isTerminal(w))
+
+ // golang's testing framework replaces os.Stdin and os.Stdout, so the following doesn't work here
+ // require.True(t, isTerminal(os.Stdin))
+ // require.True(t, isTerminal(os.Stdout))
+}
diff --git a/commands/execenv/env_testing.go b/commands/execenv/env_testing.go
index 34eafc9c..15d7b646 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,8 +13,26 @@ import (
"github.com/MichaelMure/git-bug/repository"
)
+var _ In = &TestIn{}
+
+type TestIn struct {
+ *bytes.Buffer
+ forceIsTerminal bool
+}
+
+func (t *TestIn) IsTerminal() bool {
+ return t.forceIsTerminal
+}
+
+func (t *TestIn) ForceIsTerminal(value bool) {
+ t.forceIsTerminal = value
+}
+
+var _ Out = &TestOut{}
+
type TestOut struct {
*bytes.Buffer
+ forceIsTerminal bool
}
func (te *TestOut) Printf(format string, a ...interface{}) {
@@ -37,12 +56,34 @@ func (te *TestOut) PrintJSON(v interface{}) error {
return nil
}
+func (te *TestOut) IsTerminal() bool {
+ return te.forceIsTerminal
+}
+
+func (te *TestOut) Width() int {
+ return 80
+}
+
+func (te *TestOut) Raw() io.Writer {
+ return te.Buffer
+}
+
+func (te *TestOut) ForceIsTerminal(value bool) {
+ te.forceIsTerminal = value
+}
+
func NewTestEnv(t *testing.T) *Env {
t.Helper()
+ return newTestEnv(t, false)
+}
- repo := repository.CreateGoGitTestRepo(t, false)
+func NewTestEnvTerminal(t *testing.T) *Env {
+ t.Helper()
+ return newTestEnv(t, true)
+}
- buf := new(bytes.Buffer)
+func newTestEnv(t *testing.T, isTerminal bool) *Env {
+ repo := repository.CreateGoGitTestRepo(t, false)
backend, err := cache.NewRepoCacheNoEvents(repo)
require.NoError(t, err)
@@ -54,7 +95,8 @@ func NewTestEnv(t *testing.T) *Env {
return &Env{
Repo: repo,
Backend: backend,
- Out: &TestOut{buf},
- Err: &TestOut{buf},
+ In: &TestIn{Buffer: &bytes.Buffer{}, forceIsTerminal: isTerminal},
+ Out: &TestOut{Buffer: &bytes.Buffer{}, forceIsTerminal: isTerminal},
+ Err: &TestOut{Buffer: &bytes.Buffer{}, forceIsTerminal: isTerminal},
}
}
diff --git a/commands/execenv/loading.go b/commands/execenv/loading.go
new file mode 100644
index 00000000..2263f700
--- /dev/null
+++ b/commands/execenv/loading.go
@@ -0,0 +1,169 @@
+package execenv
+
+import (
+ "fmt"
+ "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"
+ "github.com/MichaelMure/git-bug/repository"
+ "github.com/MichaelMure/git-bug/util/interrupt"
+)
+
+// 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 {
+ cwd, err := os.Getwd()
+ if err != nil {
+ return fmt.Errorf("unable to get the current working directory: %q", err)
+ }
+
+ // Note: we are not loading clocks here because we assume that LoadRepo is only used
+ // when we don't manipulate entities, or as a child call of LoadBackend which will
+ // read all clocks anyway.
+ env.Repo, err = repository.OpenGoGitRepo(cwd, gitBugNamespace, nil)
+ if err == repository.ErrNotARepo {
+ return fmt.Errorf("%s must be run from within a git Repo", RootCommandName)
+ }
+ if err != nil {
+ return err
+ }
+
+ return nil
+ }
+}
+
+// LoadRepoEnsureUser is the same as LoadRepo, but also ensure that the user has configured
+// an identity. Use this pre-run function when an error after using the configured user won't
+// do.
+func LoadRepoEnsureUser(env *Env) func(*cobra.Command, []string) error {
+ return func(cmd *cobra.Command, args []string) error {
+ err := LoadRepo(env)(cmd, args)
+ if err != nil {
+ return err
+ }
+
+ _, err = identity.GetUserIdentity(env.Repo)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ }
+}
+
+// LoadBackend is a pre-run function that load the repository and the Backend for use in a command
+// When using this function you also need to use CloseBackend as a post-run
+func LoadBackend(env *Env) func(*cobra.Command, []string) error {
+ return func(cmd *cobra.Command, args []string) error {
+ err := LoadRepo(env)(cmd, args)
+ if err != nil {
+ return err
+ }
+
+ var events chan cache.BuildEvent
+ env.Backend, events = cache.NewRepoCache(env.Repo)
+
+ err = CacheBuildProgressBar(env, events)
+ if err != nil {
+ return err
+ }
+
+ cleaner := func(env *Env) interrupt.CleanerFunc {
+ return func() error {
+ if env.Backend != nil {
+ err := env.Backend.Close()
+ env.Backend = nil
+ return err
+ }
+ return nil
+ }
+ }
+
+ // Cleanup properly on interrupt
+ interrupt.RegisterCleaner(cleaner(env))
+ return nil
+ }
+}
+
+// LoadBackendEnsureUser is the same as LoadBackend, but also ensure that the user has configured
+// an identity. Use this pre-run function when an error after using the configured user won't
+// do.
+func LoadBackendEnsureUser(env *Env) func(*cobra.Command, []string) error {
+ return func(cmd *cobra.Command, args []string) error {
+ err := LoadBackend(env)(cmd, args)
+ if err != nil {
+ return err
+ }
+
+ _, err = identity.GetUserIdentity(env.Repo)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ }
+}
+
+// CloseBackend is a wrapper for a RunE function that will close the Backend properly
+// if it has been opened.
+// This wrapper style is necessary because a Cobra PostE function does not run if RunE return an error.
+func CloseBackend(env *Env, runE func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error {
+ return func(cmd *cobra.Command, args []string) error {
+ errRun := runE(cmd, args)
+
+ if env.Backend == nil {
+ return nil
+ }
+ err := env.Backend.Close()
+ env.Backend = nil
+
+ // prioritize the RunE error
+ if errRun != nil {
+ return errRun
+ }
+ 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/label.go b/commands/label.go
index 08b9e31f..d59479b4 100644
--- a/commands/label.go
+++ b/commands/label.go
@@ -6,9 +6,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
)
-func newLabelCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newLabelCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "label",
Short: "List valid labels",
diff --git a/commands/pull.go b/commands/pull.go
index 2e2639e1..91eadcf8 100644
--- a/commands/pull.go
+++ b/commands/pull.go
@@ -10,9 +10,7 @@ import (
"github.com/MichaelMure/git-bug/entity"
)
-func newPullCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newPullCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "pull [REMOTE]",
Short: "Pull updates from a git remote",
diff --git a/commands/push.go b/commands/push.go
index d45e301a..254bbb27 100644
--- a/commands/push.go
+++ b/commands/push.go
@@ -9,9 +9,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
)
-func newPushCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newPushCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "push [REMOTE]",
Short: "Push updates to a git remote",
diff --git a/commands/root.go b/commands/root.go
index 1ccc3e56..01c817ee 100644
--- a/commands/root.go
+++ b/commands/root.go
@@ -62,19 +62,22 @@ the same git remote you are already using to collaborate with other people.
child.GroupID = groupID
}
- addCmdWithGroup(bugcmd.NewBugCommand(), entityGroup)
- addCmdWithGroup(usercmd.NewUserCommand(), entityGroup)
- addCmdWithGroup(newLabelCommand(), entityGroup)
+ env := execenv.NewEnv()
- addCmdWithGroup(newTermUICommand(), uiGroup)
- addCmdWithGroup(newWebUICommand(), uiGroup)
+ addCmdWithGroup(bugcmd.NewBugCommand(env), entityGroup)
+ addCmdWithGroup(usercmd.NewUserCommand(env), entityGroup)
+ addCmdWithGroup(newLabelCommand(env), entityGroup)
- addCmdWithGroup(newPullCommand(), remoteGroup)
- addCmdWithGroup(newPushCommand(), remoteGroup)
- addCmdWithGroup(bridgecmd.NewBridgeCommand(), remoteGroup)
+ addCmdWithGroup(newTermUICommand(env), uiGroup)
+ addCmdWithGroup(newWebUICommand(env), uiGroup)
- cmd.AddCommand(newCommandsCommand())
- cmd.AddCommand(newVersionCommand())
+ addCmdWithGroup(newPullCommand(env), remoteGroup)
+ addCmdWithGroup(newPushCommand(env), remoteGroup)
+ addCmdWithGroup(bridgecmd.NewBridgeCommand(env), remoteGroup)
+
+ cmd.AddCommand(newCommandsCommand(env))
+ cmd.AddCommand(newVersionCommand(env))
+ cmd.AddCommand(newWipeCommand(env))
return cmd
}
diff --git a/commands/termui.go b/commands/termui.go
index 1cfdd8f3..08eba1d6 100644
--- a/commands/termui.go
+++ b/commands/termui.go
@@ -7,9 +7,7 @@ import (
"github.com/MichaelMure/git-bug/termui"
)
-func newTermUICommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newTermUICommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "termui",
Aliases: []string{"tui"},
diff --git a/commands/user/user.go b/commands/user/user.go
index a9a45726..de5c1ccd 100644
--- a/commands/user/user.go
+++ b/commands/user/user.go
@@ -16,8 +16,7 @@ type userOptions struct {
format string
}
-func NewUserCommand() *cobra.Command {
- env := execenv.NewEnv()
+func NewUserCommand(env *execenv.Env) *cobra.Command {
options := userOptions{}
cmd := &cobra.Command{
@@ -29,9 +28,9 @@ func NewUserCommand() *cobra.Command {
}),
}
- cmd.AddCommand(newUserNewCommand())
- cmd.AddCommand(newUserShowCommand())
- cmd.AddCommand(newUserAdoptCommand())
+ cmd.AddCommand(newUserNewCommand(env))
+ cmd.AddCommand(newUserShowCommand(env))
+ cmd.AddCommand(newUserAdoptCommand(env))
flags := cmd.Flags()
flags.SortFlags = false
diff --git a/commands/user/user_adopt.go b/commands/user/user_adopt.go
index 30fdb442..47f0f04e 100644
--- a/commands/user/user_adopt.go
+++ b/commands/user/user_adopt.go
@@ -7,9 +7,7 @@ import (
"github.com/MichaelMure/git-bug/commands/execenv"
)
-func newUserAdoptCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newUserAdoptCommand(env *execenv.Env) *cobra.Command {
cmd := &cobra.Command{
Use: "adopt USER_ID",
Short: "Adopt an existing identity as your own",
diff --git a/commands/user/user_new.go b/commands/user/user_new.go
index 7b287492..ba4198f8 100644
--- a/commands/user/user_new.go
+++ b/commands/user/user_new.go
@@ -14,9 +14,7 @@ type userNewOptions struct {
nonInteractive bool
}
-func newUserNewCommand() *cobra.Command {
- env := execenv.NewEnv()
-
+func newUserNewCommand(env *execenv.Env) *cobra.Command {
options := userNewOptions{}
cmd := &cobra.Command{
Use: "new",
diff --git a/commands/user/user_show.go b/commands/user/user_show.go
index 225d0ef4..049eee93 100644
--- a/commands/user/user_show.go
+++ b/commands/user/user_show.go
@@ -16,8 +16,7 @@ type userShowOptions struct {
fields string
}
-func newUserShowCommand() *cobra.Command {
- env := execenv.NewEnv()
+func newUserShowCommand(env *execenv.Env) *cobra.Command {
options := userShowOptions{}
cmd := &cobra.Command{
diff --git a/commands/version.go b/commands/version.go
index 0e54bb92..957cc653 100644
--- a/commands/version.go
+++ b/commands/version.go
@@ -14,8 +14,7 @@ type versionOptions struct {
all bool
}
-func newVersionCommand() *cobra.Command {
- env := execenv.NewEnv()
+func newVersionCommand(env *execenv.Env) *cobra.Command {
options := versionOptions{}
cmd := &cobra.Command{
diff --git a/commands/webui.go b/commands/webui.go
index 0b3b24a6..3129a222 100644
--- a/commands/webui.go
+++ b/commands/webui.go
@@ -42,8 +42,7 @@ type webUIOptions struct {
query string
}
-func newWebUICommand() *cobra.Command {
- env := execenv.NewEnv()
+func newWebUICommand(env *execenv.Env) *cobra.Command {
options := webUIOptions{}
cmd := &cobra.Command{
@@ -108,19 +107,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..ce9da032
--- /dev/null
+++ b/commands/wipe.go
@@ -0,0 +1,56 @@
+package commands
+
+import (
+ "github.com/spf13/cobra"
+
+ "github.com/MichaelMure/git-bug/commands/execenv"
+)
+
+func newWipeCommand(env *execenv.Env) *cobra.Command {
+ 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-bug.1 b/doc/man/git-bug-bug.1
index 6ee62303..db26518e 100644
--- a/doc/man/git-bug-bug.1
+++ b/doc/man/git-bug-bug.1
@@ -62,7 +62,7 @@ You can pass an additional query to filter and order the list. This query can be
.PP
\fB-f\fP, \fB--format\fP="default"
- Select the output formatting style. Valid values are [default,plain,compact,id,json,org-mode]
+ Select the output formatting style. Valid values are [default,plain,id,json,org-mode]
.PP
\fB-h\fP, \fB--help\fP[=false]
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_bug.md b/doc/md/git-bug_bug.md
index c040cd16..0917f654 100644
--- a/doc/md/git-bug_bug.md
+++ b/doc/md/git-bug_bug.md
@@ -42,7 +42,7 @@ git bug status:open --by creation "foo bar" baz
-n, --no strings Filter by absence of something. Valid values are [label]
-b, --by string Sort the results by a characteristic. Valid values are [id,creation,edit] (default "creation")
-d, --direction string Select the sorting direction. Valid values are [asc,desc] (default "asc")
- -f, --format string Select the output formatting style. Valid values are [default,plain,compact,id,json,org-mode] (default "default")
+ -f, --format string Select the output formatting style. Valid values are [default,plain,id,json,org-mode] (default "default")
-h, --help help for bug
```
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/id.go b/entity/id.go
index 49398da8..0949bf92 100644
--- a/entity/id.go
+++ b/entity/id.go
@@ -11,7 +11,7 @@ import (
// sha-256
const idLength = 64
-const humanIdLength = 7
+const HumanIdLength = 7
const UnsetId = Id("unset")
@@ -34,7 +34,7 @@ func (i Id) String() string {
// Human return the identifier, shortened for human consumption
func (i Id) Human() string {
- format := fmt.Sprintf("%%.%ds", humanIdLength)
+ format := fmt.Sprintf("%%.%ds", HumanIdLength)
return fmt.Sprintf(format, i)
}
diff --git a/entity/id_interleaved.go b/entity/id_interleaved.go
index 28c59a42..7ae6d72e 100644
--- a/entity/id_interleaved.go
+++ b/entity/id_interleaved.go
@@ -22,7 +22,7 @@ func (ci CombinedId) String() string {
// Human return the identifier, shortened for human consumption
func (ci CombinedId) Human() string {
- format := fmt.Sprintf("%%.%ds", humanIdLength)
+ format := fmt.Sprintf("%%.%ds", HumanIdLength)
return fmt.Sprintf(format, ci)
}
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
}
diff --git a/go.mod b/go.mod
index 57896eb3..0008ef92 100644
--- a/go.mod
+++ b/go.mod
@@ -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
@@ -108,7 +114,7 @@ require (
go.etcd.io/bbolt v1.3.5 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.5.0 // indirect
- golang.org/x/term v0.4.0 // indirect
+ golang.org/x/term v0.4.0
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
golang.org/x/tools v0.4.0 // indirect
golang.org/x/vuln v0.0.0-20220908155419-5537ad2271a7
diff --git a/go.sum b/go.sum
index 980af4bb..6c735e9e 100644
--- a/go.sum
+++ b/go.sum
@@ -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)