aboutsummaryrefslogtreecommitdiffstats
path: root/commands
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2022-12-27 19:40:40 +0100
committerMichael Muré <batolettre@gmail.com>2022-12-27 19:40:40 +0100
commite920987860dd9392fefc4b222aa4d2446b74f3d8 (patch)
tree7f67b211ed2a6342e4a3ca4e1011f3b91f0080bb /commands
parent8cea6c7515e42cb8821a03c9bfebb0a8f63a01be (diff)
downloadgit-bug-e920987860dd9392fefc4b222aa4d2446b74f3d8.tar.gz
commands: generic "select" code, move bug completion in bugcmd
Diffstat (limited to 'commands')
-rw-r--r--commands/bug/bug_comment.go6
-rw-r--r--commands/bug/bug_comment_add.go6
-rw-r--r--commands/bug/bug_deselect.go5
-rw-r--r--commands/bug/bug_label.go6
-rw-r--r--commands/bug/bug_label_new.go6
-rw-r--r--commands/bug/bug_label_rm.go6
-rw-r--r--commands/bug/bug_rm.go3
-rw-r--r--commands/bug/bug_select.go13
-rw-r--r--commands/bug/bug_show.go5
-rw-r--r--commands/bug/bug_status.go6
-rw-r--r--commands/bug/bug_status_close.go6
-rw-r--r--commands/bug/bug_status_open.go6
-rw-r--r--commands/bug/bug_title.go6
-rw-r--r--commands/bug/bug_title_edit.go6
-rw-r--r--commands/bug/completion.go98
-rw-r--r--commands/bug/select/select.go128
-rw-r--r--commands/completion/helper_completion.go120
-rw-r--r--commands/select/select.go156
-rw-r--r--commands/select/select_test.go (renamed from commands/bug/select/select_test.go)41
19 files changed, 327 insertions, 302 deletions
diff --git a/commands/bug/bug_comment.go b/commands/bug/bug_comment.go
index bc665f0d..4dc8dc1f 100644
--- a/commands/bug/bug_comment.go
+++ b/commands/bug/bug_comment.go
@@ -4,8 +4,6 @@ import (
text "github.com/MichaelMure/go-term-text"
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/commands/bug/select"
- "github.com/MichaelMure/git-bug/commands/completion"
"github.com/MichaelMure/git-bug/commands/execenv"
"github.com/MichaelMure/git-bug/util/colors"
)
@@ -20,7 +18,7 @@ func newBugCommentCommand() *cobra.Command {
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugComment(env, args)
}),
- ValidArgsFunction: completion.Bug(env),
+ ValidArgsFunction: BugCompletion(env),
}
cmd.AddCommand(newBugCommentNewCommand())
@@ -30,7 +28,7 @@ func newBugCommentCommand() *cobra.Command {
}
func runBugComment(env *execenv.Env, args []string) error {
- b, args, err := _select.ResolveBug(env.Backend, args)
+ b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_comment_add.go b/commands/bug/bug_comment_add.go
index b445ffad..ff406b4f 100644
--- a/commands/bug/bug_comment_add.go
+++ b/commands/bug/bug_comment_add.go
@@ -4,8 +4,6 @@ import (
"github.com/spf13/cobra"
buginput "github.com/MichaelMure/git-bug/commands/bug/input"
- "github.com/MichaelMure/git-bug/commands/bug/select"
- "github.com/MichaelMure/git-bug/commands/completion"
"github.com/MichaelMure/git-bug/commands/execenv"
"github.com/MichaelMure/git-bug/util/text"
)
@@ -27,7 +25,7 @@ func newBugCommentNewCommand() *cobra.Command {
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugCommentNew(env, options, args)
}),
- ValidArgsFunction: completion.Bug(env),
+ ValidArgsFunction: BugCompletion(env),
}
flags := cmd.Flags()
@@ -44,7 +42,7 @@ func newBugCommentNewCommand() *cobra.Command {
}
func runBugCommentNew(env *execenv.Env, opts bugCommentNewOptions, args []string) error {
- b, args, err := _select.ResolveBug(env.Backend, args)
+ b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_deselect.go b/commands/bug/bug_deselect.go
index 7e2a86c9..090a7bf2 100644
--- a/commands/bug/bug_deselect.go
+++ b/commands/bug/bug_deselect.go
@@ -3,8 +3,9 @@ package bugcmd
import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/commands/bug/select"
"github.com/MichaelMure/git-bug/commands/execenv"
+ _select "github.com/MichaelMure/git-bug/commands/select"
+ "github.com/MichaelMure/git-bug/entities/bug"
)
func newBugDeselectCommand() *cobra.Command {
@@ -28,7 +29,7 @@ git bug deselect
}
func runBugDeselect(env *execenv.Env) error {
- err := _select.Clear(env.Backend)
+ err := _select.Clear(env.Backend, bug.Namespace)
if err != nil {
return err
}
diff --git a/commands/bug/bug_label.go b/commands/bug/bug_label.go
index 657fa2ca..e6d0e603 100644
--- a/commands/bug/bug_label.go
+++ b/commands/bug/bug_label.go
@@ -3,8 +3,6 @@ package bugcmd
import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/commands/bug/select"
- "github.com/MichaelMure/git-bug/commands/completion"
"github.com/MichaelMure/git-bug/commands/execenv"
)
@@ -18,7 +16,7 @@ func newBugLabelCommand() *cobra.Command {
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugLabel(env, args)
}),
- ValidArgsFunction: completion.Bug(env),
+ ValidArgsFunction: BugCompletion(env),
}
cmd.AddCommand(newBugLabelNewCommand())
@@ -28,7 +26,7 @@ func newBugLabelCommand() *cobra.Command {
}
func runBugLabel(env *execenv.Env, args []string) error {
- b, args, err := _select.ResolveBug(env.Backend, args)
+ b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_label_new.go b/commands/bug/bug_label_new.go
index f94d3dc8..aa4f9463 100644
--- a/commands/bug/bug_label_new.go
+++ b/commands/bug/bug_label_new.go
@@ -3,8 +3,6 @@ package bugcmd
import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/commands/bug/select"
- "github.com/MichaelMure/git-bug/commands/completion"
"github.com/MichaelMure/git-bug/commands/execenv"
"github.com/MichaelMure/git-bug/util/text"
)
@@ -19,14 +17,14 @@ func newBugLabelNewCommand() *cobra.Command {
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugLabelNew(env, args)
}),
- ValidArgsFunction: completion.BugAndLabels(env, true),
+ ValidArgsFunction: BugAndLabelsCompletion(env, true),
}
return cmd
}
func runBugLabelNew(env *execenv.Env, args []string) error {
- b, args, err := _select.ResolveBug(env.Backend, args)
+ b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_label_rm.go b/commands/bug/bug_label_rm.go
index 13ce4b81..18510bbd 100644
--- a/commands/bug/bug_label_rm.go
+++ b/commands/bug/bug_label_rm.go
@@ -3,8 +3,6 @@ package bugcmd
import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/commands/bug/select"
- "github.com/MichaelMure/git-bug/commands/completion"
"github.com/MichaelMure/git-bug/commands/execenv"
"github.com/MichaelMure/git-bug/util/text"
)
@@ -19,14 +17,14 @@ func newBugLabelRmCommand() *cobra.Command {
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugLabelRm(env, args)
}),
- ValidArgsFunction: completion.BugAndLabels(env, false),
+ ValidArgsFunction: BugAndLabelsCompletion(env, false),
}
return cmd
}
func runBugLabelRm(env *execenv.Env, args []string) error {
- b, args, err := _select.ResolveBug(env.Backend, args)
+ b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_rm.go b/commands/bug/bug_rm.go
index 04881d54..386c57ec 100644
--- a/commands/bug/bug_rm.go
+++ b/commands/bug/bug_rm.go
@@ -5,7 +5,6 @@ import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/commands/completion"
"github.com/MichaelMure/git-bug/commands/execenv"
)
@@ -20,7 +19,7 @@ func newBugRmCommand() *cobra.Command {
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugRm(env, args)
}),
- ValidArgsFunction: completion.Bug(env),
+ ValidArgsFunction: BugCompletion(env),
}
flags := cmd.Flags()
diff --git a/commands/bug/bug_select.go b/commands/bug/bug_select.go
index 2a4d1201..bfad899d 100644
--- a/commands/bug/bug_select.go
+++ b/commands/bug/bug_select.go
@@ -5,11 +5,16 @@ import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/commands/bug/select"
- "github.com/MichaelMure/git-bug/commands/completion"
+ "github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/commands/execenv"
+ _select "github.com/MichaelMure/git-bug/commands/select"
+ "github.com/MichaelMure/git-bug/entities/bug"
)
+func ResolveSelected(repo *cache.RepoCache, args []string) (*cache.BugCache, []string, error) {
+ return _select.Resolve[*cache.BugCache](repo, bug.Typename, bug.Namespace, repo.Bugs(), args)
+}
+
func newBugSelectCommand() *cobra.Command {
env := execenv.NewEnv()
@@ -33,7 +38,7 @@ The complementary command is "git bug deselect" performing the opposite operatio
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugSelect(env, args)
}),
- ValidArgsFunction: completion.Bug(env),
+ ValidArgsFunction: BugCompletion(env),
}
return cmd
@@ -51,7 +56,7 @@ func runBugSelect(env *execenv.Env, args []string) error {
return err
}
- err = _select.Select(env.Backend, b.Id())
+ err = _select.Select(env.Backend, bug.Namespace, b.Id())
if err != nil {
return err
}
diff --git a/commands/bug/bug_show.go b/commands/bug/bug_show.go
index 105b1150..6cf50015 100644
--- a/commands/bug/bug_show.go
+++ b/commands/bug/bug_show.go
@@ -8,7 +8,6 @@ import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/commands/bug/select"
"github.com/MichaelMure/git-bug/commands/cmdjson"
"github.com/MichaelMure/git-bug/commands/completion"
"github.com/MichaelMure/git-bug/commands/execenv"
@@ -32,7 +31,7 @@ func newBugShowCommand() *cobra.Command {
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugShow(env, options, args)
}),
- ValidArgsFunction: completion.Bug(env),
+ ValidArgsFunction: BugCompletion(env),
}
flags := cmd.Flags()
@@ -50,7 +49,7 @@ func newBugShowCommand() *cobra.Command {
}
func runBugShow(env *execenv.Env, opts bugShowOptions, args []string) error {
- b, args, err := _select.ResolveBug(env.Backend, args)
+ b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_status.go b/commands/bug/bug_status.go
index b05f862c..807a9a60 100644
--- a/commands/bug/bug_status.go
+++ b/commands/bug/bug_status.go
@@ -3,8 +3,6 @@ package bugcmd
import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/commands/bug/select"
- "github.com/MichaelMure/git-bug/commands/completion"
"github.com/MichaelMure/git-bug/commands/execenv"
)
@@ -18,7 +16,7 @@ func newBugStatusCommand() *cobra.Command {
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugStatus(env, args)
}),
- ValidArgsFunction: completion.Bug(env),
+ ValidArgsFunction: BugCompletion(env),
}
cmd.AddCommand(newBugStatusCloseCommand())
@@ -28,7 +26,7 @@ func newBugStatusCommand() *cobra.Command {
}
func runBugStatus(env *execenv.Env, args []string) error {
- b, args, err := _select.ResolveBug(env.Backend, args)
+ b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_status_close.go b/commands/bug/bug_status_close.go
index fcd47922..e52959b2 100644
--- a/commands/bug/bug_status_close.go
+++ b/commands/bug/bug_status_close.go
@@ -3,8 +3,6 @@ package bugcmd
import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/commands/bug/select"
- "github.com/MichaelMure/git-bug/commands/completion"
"github.com/MichaelMure/git-bug/commands/execenv"
)
@@ -18,14 +16,14 @@ func newBugStatusCloseCommand() *cobra.Command {
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugStatusClose(env, args)
}),
- ValidArgsFunction: completion.Bug(env),
+ ValidArgsFunction: BugCompletion(env),
}
return cmd
}
func runBugStatusClose(env *execenv.Env, args []string) error {
- b, args, err := _select.ResolveBug(env.Backend, args)
+ b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_status_open.go b/commands/bug/bug_status_open.go
index e686add1..74177974 100644
--- a/commands/bug/bug_status_open.go
+++ b/commands/bug/bug_status_open.go
@@ -3,8 +3,6 @@ package bugcmd
import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/commands/bug/select"
- "github.com/MichaelMure/git-bug/commands/completion"
"github.com/MichaelMure/git-bug/commands/execenv"
)
@@ -18,14 +16,14 @@ func newBugStatusOpenCommand() *cobra.Command {
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugStatusOpen(env, args)
}),
- ValidArgsFunction: completion.Bug(env),
+ ValidArgsFunction: BugCompletion(env),
}
return cmd
}
func runBugStatusOpen(env *execenv.Env, args []string) error {
- b, args, err := _select.ResolveBug(env.Backend, args)
+ b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_title.go b/commands/bug/bug_title.go
index 98809b60..e59a1fdc 100644
--- a/commands/bug/bug_title.go
+++ b/commands/bug/bug_title.go
@@ -3,8 +3,6 @@ package bugcmd
import (
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/commands/bug/select"
- "github.com/MichaelMure/git-bug/commands/completion"
"github.com/MichaelMure/git-bug/commands/execenv"
)
@@ -18,7 +16,7 @@ func newBugTitleCommand() *cobra.Command {
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugTitle(env, args)
}),
- ValidArgsFunction: completion.Bug(env),
+ ValidArgsFunction: BugCompletion(env),
}
cmd.AddCommand(newBugTitleEditCommand())
@@ -27,7 +25,7 @@ func newBugTitleCommand() *cobra.Command {
}
func runBugTitle(env *execenv.Env, args []string) error {
- b, args, err := _select.ResolveBug(env.Backend, args)
+ b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/bug_title_edit.go b/commands/bug/bug_title_edit.go
index bf9f6375..59898530 100644
--- a/commands/bug/bug_title_edit.go
+++ b/commands/bug/bug_title_edit.go
@@ -4,8 +4,6 @@ import (
"github.com/spf13/cobra"
buginput "github.com/MichaelMure/git-bug/commands/bug/input"
- "github.com/MichaelMure/git-bug/commands/bug/select"
- "github.com/MichaelMure/git-bug/commands/completion"
"github.com/MichaelMure/git-bug/commands/execenv"
"github.com/MichaelMure/git-bug/util/text"
)
@@ -26,7 +24,7 @@ func newBugTitleEditCommand() *cobra.Command {
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugTitleEdit(env, options, args)
}),
- ValidArgsFunction: completion.Bug(env),
+ ValidArgsFunction: BugCompletion(env),
}
flags := cmd.Flags()
@@ -41,7 +39,7 @@ func newBugTitleEditCommand() *cobra.Command {
}
func runBugTitleEdit(env *execenv.Env, opts bugTitleEditOptions, args []string) error {
- b, args, err := _select.ResolveBug(env.Backend, args)
+ b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}
diff --git a/commands/bug/completion.go b/commands/bug/completion.go
new file mode 100644
index 00000000..4754f97d
--- /dev/null
+++ b/commands/bug/completion.go
@@ -0,0 +1,98 @@
+package bugcmd
+
+import (
+ "strings"
+
+ "github.com/spf13/cobra"
+
+ "github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/commands/completion"
+ "github.com/MichaelMure/git-bug/commands/execenv"
+ _select "github.com/MichaelMure/git-bug/commands/select"
+ "github.com/MichaelMure/git-bug/entities/bug"
+)
+
+// BugCompletion complete a bug id
+func BugCompletion(env *execenv.Env) completion.ValidArgsFunction {
+ return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
+ if err := execenv.LoadBackend(env)(cmd, args); err != nil {
+ return completion.HandleError(err)
+ }
+ defer func() {
+ _ = env.Backend.Close()
+ }()
+
+ return bugWithBackend(env.Backend, toComplete)
+ }
+}
+
+func bugWithBackend(backend *cache.RepoCache, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
+ for _, id := range backend.Bugs().AllIds() {
+ if strings.Contains(id.String(), strings.TrimSpace(toComplete)) {
+ excerpt, err := backend.Bugs().ResolveExcerpt(id)
+ if err != nil {
+ return completion.HandleError(err)
+ }
+ completions = append(completions, id.Human()+"\t"+excerpt.Title)
+ }
+ }
+
+ return completions, cobra.ShellCompDirectiveNoFileComp
+}
+
+// BugAndLabelsCompletion complete either a bug ID or a label if we know about the bug
+func BugAndLabelsCompletion(env *execenv.Env, addOrRemove bool) completion.ValidArgsFunction {
+ return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
+ if err := execenv.LoadBackend(env)(cmd, args); err != nil {
+ return completion.HandleError(err)
+ }
+ defer func() {
+ _ = env.Backend.Close()
+ }()
+
+ b, args, err := ResolveSelected(env.Backend, args)
+ if _select.IsErrNoValidId(err) {
+ // we need a bug first to complete labels
+ return bugWithBackend(env.Backend, toComplete)
+ }
+ if err != nil {
+ return completion.HandleError(err)
+ }
+
+ snap := b.Snapshot()
+
+ seenLabels := map[bug.Label]bool{}
+ for _, label := range args {
+ seenLabels[bug.Label(label)] = addOrRemove
+ }
+
+ var labels []bug.Label
+ if addOrRemove {
+ for _, label := range snap.Labels {
+ seenLabels[label] = true
+ }
+
+ allLabels := env.Backend.Bugs().ValidLabels()
+ labels = make([]bug.Label, 0, len(allLabels))
+ for _, label := range allLabels {
+ if !seenLabels[label] {
+ labels = append(labels, label)
+ }
+ }
+ } else {
+ labels = make([]bug.Label, 0, len(snap.Labels))
+ for _, label := range snap.Labels {
+ if seenLabels[label] {
+ labels = append(labels, label)
+ }
+ }
+ }
+
+ completions = make([]string, len(labels))
+ for i, label := range labels {
+ completions[i] = string(label) + "\t" + "Label"
+ }
+
+ return completions, cobra.ShellCompDirectiveNoFileComp
+ }
+}
diff --git a/commands/bug/select/select.go b/commands/bug/select/select.go
deleted file mode 100644
index 7096dde4..00000000
--- a/commands/bug/select/select.go
+++ /dev/null
@@ -1,128 +0,0 @@
-package _select
-
-import (
- "fmt"
- "io"
- "io/ioutil"
- "os"
-
- "github.com/pkg/errors"
-
- "github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/entity"
-)
-
-const selectFile = "select"
-
-var ErrNoValidId = errors.New("you must provide a bug id or use the \"select\" command first")
-
-// ResolveBug first try to resolve a bug using the first argument of the command
-// line. If it fails, it falls back to the select mechanism.
-//
-// Returns:
-// - the bug if any
-// - the new list of command line arguments with the bug prefix removed if it
-// has been used
-// - an error if the process failed
-func ResolveBug(repo *cache.RepoCache, args []string) (*cache.BugCache, []string, error) {
- // At first, try to use the first argument as a bug prefix
- if len(args) > 0 {
- b, err := repo.Bugs().ResolvePrefix(args[0])
-
- if err == nil {
- return b, args[1:], nil
- }
-
- if !entity.IsErrNotFound(err) {
- return nil, nil, err
- }
- }
-
- // first arg is not a valid bug prefix, we can safely use the preselected bug if any
-
- b, err := selected(repo)
-
- // selected bug is invalid
- if entity.IsErrNotFound(err) {
- // we clear the selected bug
- err = Clear(repo)
- if err != nil {
- return nil, nil, err
- }
- return nil, nil, ErrNoValidId
- }
-
- // another error when reading the bug
- if err != nil {
- return nil, nil, err
- }
-
- // bug is successfully retrieved
- if b != nil {
- return b, args, nil
- }
-
- // no selected bug and no valid first argument
- return nil, nil, ErrNoValidId
-}
-
-// Select will select a bug for future use
-func Select(repo *cache.RepoCache, id entity.Id) error {
- f, err := repo.LocalStorage().OpenFile(selectFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
- if err != nil {
- return err
- }
-
- _, err = f.Write([]byte(id.String()))
- if err != nil {
- return err
- }
-
- return f.Close()
-}
-
-// Clear will clear the selected bug, if any
-func Clear(repo *cache.RepoCache) error {
- return repo.LocalStorage().Remove(selectFile)
-}
-
-func selected(repo *cache.RepoCache) (*cache.BugCache, error) {
- f, err := repo.LocalStorage().Open(selectFile)
- if err != nil {
- if os.IsNotExist(err) {
- return nil, nil
- } else {
- return nil, err
- }
- }
-
- buf, err := ioutil.ReadAll(io.LimitReader(f, 100))
- if err != nil {
- return nil, err
- }
- if len(buf) == 100 {
- return nil, fmt.Errorf("the select file should be < 100 bytes")
- }
-
- id := entity.Id(buf)
- if err := id.Validate(); err != nil {
- err = repo.LocalStorage().Remove(selectFile)
- if err != nil {
- return nil, errors.Wrap(err, "error while removing invalid select file")
- }
-
- return nil, fmt.Errorf("select file in invalid, removing it")
- }
-
- b, err := repo.Bugs().Resolve(id)
- if err != nil {
- return nil, err
- }
-
- err = f.Close()
- if err != nil {
- return nil, err
- }
-
- return b, nil
-}
diff --git a/commands/completion/helper_completion.go b/commands/completion/helper_completion.go
index 691f0895..db6d8969 100644
--- a/commands/completion/helper_completion.go
+++ b/commands/completion/helper_completion.go
@@ -9,22 +9,19 @@ import (
"github.com/MichaelMure/git-bug/bridge"
"github.com/MichaelMure/git-bug/bridge/core/auth"
- "github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/commands/bug/select"
"github.com/MichaelMure/git-bug/commands/execenv"
- "github.com/MichaelMure/git-bug/entities/bug"
)
type ValidArgsFunction func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective)
-func handleError(err error) (completions []string, directives cobra.ShellCompDirective) {
+func HandleError(err error) (completions []string, directives cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveError
}
func Bridge(env *execenv.Env) ValidArgsFunction {
return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
if err := execenv.LoadBackend(env)(cmd, args); err != nil {
- return handleError(err)
+ return HandleError(err)
}
defer func() {
_ = env.Backend.Close()
@@ -32,7 +29,7 @@ func Bridge(env *execenv.Env) ValidArgsFunction {
bridges, err := bridge.ConfiguredBridges(env.Backend)
if err != nil {
- return handleError(err)
+ return HandleError(err)
}
completions = make([]string, len(bridges))
@@ -47,7 +44,7 @@ func Bridge(env *execenv.Env) ValidArgsFunction {
func BridgeAuth(env *execenv.Env) ValidArgsFunction {
return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
if err := execenv.LoadBackend(env)(cmd, args); err != nil {
- return handleError(err)
+ return HandleError(err)
}
defer func() {
_ = env.Backend.Close()
@@ -55,7 +52,7 @@ func BridgeAuth(env *execenv.Env) ValidArgsFunction {
creds, err := auth.List(env.Backend)
if err != nil {
- return handleError(err)
+ return HandleError(err)
}
completions = make([]string, len(creds))
@@ -74,95 +71,6 @@ func BridgeAuth(env *execenv.Env) ValidArgsFunction {
}
}
-func Bug(env *execenv.Env) ValidArgsFunction {
- return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
- if err := execenv.LoadBackend(env)(cmd, args); err != nil {
- return handleError(err)
- }
- defer func() {
- _ = env.Backend.Close()
- }()
-
- return bugWithBackend(env.Backend, toComplete)
- }
-}
-
-func bugWithBackend(backend *cache.RepoCache, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
- allIds := backend.Bugs().AllIds()
- bugExcerpt := make([]*cache.BugExcerpt, len(allIds))
- for i, id := range allIds {
- var err error
- bugExcerpt[i], err = backend.Bugs().ResolveExcerpt(id)
- if err != nil {
- return handleError(err)
- }
- }
-
- for i, id := range allIds {
- if strings.Contains(id.String(), strings.TrimSpace(toComplete)) {
- completions = append(completions, id.Human()+"\t"+bugExcerpt[i].Title)
- }
- }
-
- return completions, cobra.ShellCompDirectiveNoFileComp
-}
-
-func BugAndLabels(env *execenv.Env, addOrRemove bool) ValidArgsFunction {
- return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
- if err := execenv.LoadBackend(env)(cmd, args); err != nil {
- return handleError(err)
- }
- defer func() {
- _ = env.Backend.Close()
- }()
-
- b, args, err := _select.ResolveBug(env.Backend, args)
- if err == _select.ErrNoValidId {
- // we need a bug first to complete labels
- return bugWithBackend(env.Backend, toComplete)
- }
- if err != nil {
- return handleError(err)
- }
-
- snap := b.Snapshot()
-
- seenLabels := map[bug.Label]bool{}
- for _, label := range args {
- seenLabels[bug.Label(label)] = addOrRemove
- }
-
- var labels []bug.Label
- if addOrRemove {
- for _, label := range snap.Labels {
- seenLabels[label] = true
- }
-
- allLabels := env.Backend.Bugs().ValidLabels()
- labels = make([]bug.Label, 0, len(allLabels))
- for _, label := range allLabels {
- if !seenLabels[label] {
- labels = append(labels, label)
- }
- }
- } else {
- labels = make([]bug.Label, 0, len(snap.Labels))
- for _, label := range snap.Labels {
- if seenLabels[label] {
- labels = append(labels, label)
- }
- }
- }
-
- completions = make([]string, len(labels))
- for i, label := range labels {
- completions[i] = string(label) + "\t" + "Label"
- }
-
- return completions, cobra.ShellCompDirectiveNoFileComp
- }
-}
-
func From(choices []string) ValidArgsFunction {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return choices, cobra.ShellCompDirectiveNoFileComp
@@ -172,7 +80,7 @@ func From(choices []string) ValidArgsFunction {
func GitRemote(env *execenv.Env) ValidArgsFunction {
return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
if err := execenv.LoadBackend(env)(cmd, args); err != nil {
- return handleError(err)
+ return HandleError(err)
}
defer func() {
_ = env.Backend.Close()
@@ -180,7 +88,7 @@ func GitRemote(env *execenv.Env) ValidArgsFunction {
remoteMap, err := env.Backend.GetRemotes()
if err != nil {
- return handleError(err)
+ return HandleError(err)
}
completions = make([]string, 0, len(remoteMap))
for remote, url := range remoteMap {
@@ -194,7 +102,7 @@ func GitRemote(env *execenv.Env) ValidArgsFunction {
func Label(env *execenv.Env) ValidArgsFunction {
return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
if err := execenv.LoadBackend(env)(cmd, args); err != nil {
- return handleError(err)
+ return HandleError(err)
}
defer func() {
_ = env.Backend.Close()
@@ -232,7 +140,7 @@ func Ls(env *execenv.Env) ValidArgsFunction {
if needBackend {
if err := execenv.LoadBackend(env)(cmd, args); err != nil {
- return handleError(err)
+ return HandleError(err)
}
defer func() {
_ = env.Backend.Close()
@@ -248,7 +156,7 @@ func Ls(env *execenv.Env) ValidArgsFunction {
for i, id := range ids {
user, err := env.Backend.Identities().ResolveExcerpt(id)
if err != nil {
- return handleError(err)
+ return HandleError(err)
}
var handle string
if user.Login != "" {
@@ -294,7 +202,7 @@ func Ls(env *execenv.Env) ValidArgsFunction {
func User(env *execenv.Env) ValidArgsFunction {
return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
if err := execenv.LoadBackend(env)(cmd, args); err != nil {
- return handleError(err)
+ return HandleError(err)
}
defer func() {
_ = env.Backend.Close()
@@ -305,7 +213,7 @@ func User(env *execenv.Env) ValidArgsFunction {
for i, id := range ids {
user, err := env.Backend.Identities().ResolveExcerpt(id)
if err != nil {
- return handleError(err)
+ return HandleError(err)
}
completions[i] = user.Id().Human() + "\t" + user.DisplayName()
}
@@ -316,7 +224,7 @@ func User(env *execenv.Env) ValidArgsFunction {
func UserForQuery(env *execenv.Env) ValidArgsFunction {
return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
if err := execenv.LoadBackend(env)(cmd, args); err != nil {
- return handleError(err)
+ return HandleError(err)
}
defer func() {
_ = env.Backend.Close()
@@ -327,7 +235,7 @@ func UserForQuery(env *execenv.Env) ValidArgsFunction {
for i, id := range ids {
user, err := env.Backend.Identities().ResolveExcerpt(id)
if err != nil {
- return handleError(err)
+ return HandleError(err)
}
var handle string
if user.Login != "" {
diff --git a/commands/select/select.go b/commands/select/select.go
new file mode 100644
index 00000000..b821ba59
--- /dev/null
+++ b/commands/select/select.go
@@ -0,0 +1,156 @@
+package _select
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/pkg/errors"
+
+ "github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/entity"
+)
+
+type ErrNoValidId struct {
+ typename string
+}
+
+func NewErrNoValidId(typename string) *ErrNoValidId {
+ return &ErrNoValidId{typename: typename}
+}
+
+func (e ErrNoValidId) Error() string {
+ return fmt.Sprintf("you must provide a %s id or use the \"select\" command first", e.typename)
+}
+
+func IsErrNoValidId(err error) bool {
+ _, ok := err.(*ErrNoValidId)
+ return ok
+}
+
+type Resolver[CacheT cache.CacheEntity] interface {
+ Resolve(id entity.Id) (CacheT, error)
+ ResolvePrefix(prefix string) (CacheT, error)
+}
+
+// Resolve first try to resolve an entity using the first argument of the command
+// line. If it fails, it falls back to the select mechanism.
+//
+// Returns:
+// - the entity if any
+// - the new list of command line arguments with the entity prefix removed if it
+// has been used
+// - an error if the process failed
+func Resolve[CacheT cache.CacheEntity](repo *cache.RepoCache,
+ typename string, namespace string, resolver Resolver[CacheT],
+ args []string) (CacheT, []string, error) {
+ // At first, try to use the first argument as an entity prefix
+ if len(args) > 0 {
+ cached, err := resolver.ResolvePrefix(args[0])
+
+ if err == nil {
+ return cached, args[1:], nil
+ }
+
+ if !entity.IsErrNotFound(err) {
+ return *new(CacheT), nil, err
+ }
+ }
+
+ // first arg is not a valid entity prefix, we can safely use the preselected entity if any
+
+ cached, err := selected(repo, resolver, namespace)
+
+ // selected entity is invalid
+ if entity.IsErrNotFound(err) {
+ // we clear the selected bug
+ err = Clear(repo, namespace)
+ if err != nil {
+ return *new(CacheT), nil, err
+ }
+ return *new(CacheT), nil, NewErrNoValidId(typename)
+ }
+
+ // another error when reading the entity
+ if err != nil {
+ return *new(CacheT), nil, err
+ }
+
+ // entity is successfully retrieved
+ if cached != nil {
+ return *cached, args, nil
+ }
+
+ // no selected bug and no valid first argument
+ return *new(CacheT), nil, NewErrNoValidId(typename)
+}
+
+func selectFileName(namespace string) string {
+ return filepath.Join("select", namespace)
+}
+
+// Select will select a bug for future use
+func Select(repo *cache.RepoCache, namespace string, id entity.Id) error {
+ filename := selectFileName(namespace)
+ f, err := repo.LocalStorage().OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
+ if err != nil {
+ return err
+ }
+
+ _, err = f.Write([]byte(id.String()))
+ if err != nil {
+ return err
+ }
+
+ return f.Close()
+}
+
+// Clear will clear the selected entity, if any
+func Clear(repo *cache.RepoCache, namespace string) error {
+ filename := selectFileName(namespace)
+ return repo.LocalStorage().Remove(filename)
+}
+
+func selected[CacheT cache.CacheEntity](repo *cache.RepoCache, resolver Resolver[CacheT], namespace string) (*CacheT, error) {
+ filename := selectFileName(namespace)
+ f, err := repo.LocalStorage().Open(filename)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, nil
+ } else {
+ return nil, err
+ }
+ }
+
+ buf, err := ioutil.ReadAll(io.LimitReader(f, 100))
+ if err != nil {
+ return nil, err
+ }
+ if len(buf) == 100 {
+ return nil, fmt.Errorf("the select file should be < 100 bytes")
+ }
+
+ id := entity.Id(buf)
+ if err := id.Validate(); err != nil {
+ err = repo.LocalStorage().Remove(filename)
+ if err != nil {
+ return nil, errors.Wrap(err, "error while removing invalid select file")
+ }
+
+ return nil, fmt.Errorf("select file in invalid, removing it")
+ }
+
+ cached, err := resolver.Resolve(id)
+ if err != nil {
+ return nil, err
+ }
+
+ err = f.Close()
+ if err != nil {
+ return nil, err
+ }
+
+ return &cached, nil
+}
diff --git a/commands/bug/select/select_test.go b/commands/select/select_test.go
index 83ca6643..4425c275 100644
--- a/commands/bug/select/select_test.go
+++ b/commands/select/select_test.go
@@ -13,67 +13,74 @@ import (
func TestSelect(t *testing.T) {
repo := repository.CreateGoGitTestRepo(t, false)
- repoCache, err := cache.NewRepoCacheNoEvents(repo)
+ backend, err := cache.NewRepoCacheNoEvents(repo)
require.NoError(t, err)
- _, _, err = ResolveBug(repoCache, []string{})
- require.Equal(t, ErrNoValidId, err)
+ const typename = "foo"
+ const namespace = "foos"
- err = Select(repoCache, "invalid")
+ resolve := func(args []string) (*cache.BugCache, []string, error) {
+ return Resolve[*cache.BugCache](backend, typename, namespace, backend.Bugs(), args)
+ }
+
+ _, _, err = resolve([]string{})
+ require.True(t, IsErrNoValidId(err))
+
+ err = Select(backend, namespace, "invalid")
require.NoError(t, err)
// Resolve without a pattern should fail when no bug is selected
- _, _, err = ResolveBug(repoCache, []string{})
+ _, _, err = resolve([]string{})
require.Error(t, err)
// generate a bunch of bugs
- rene, err := repoCache.Identities().New("René Descartes", "rene@descartes.fr")
+ rene, err := backend.Identities().New("René Descartes", "rene@descartes.fr")
require.NoError(t, err)
for i := 0; i < 10; i++ {
- _, _, err := repoCache.Bugs().NewRaw(rene, time.Now().Unix(), "title", "message", nil, nil)
+ _, _, err := backend.Bugs().NewRaw(rene, time.Now().Unix(), "title", "message", nil, nil)
require.NoError(t, err)
}
// and two more for testing
- b1, _, err := repoCache.Bugs().NewRaw(rene, time.Now().Unix(), "title", "message", nil, nil)
+ b1, _, err := backend.Bugs().NewRaw(rene, time.Now().Unix(), "title", "message", nil, nil)
require.NoError(t, err)
- b2, _, err := repoCache.Bugs().NewRaw(rene, time.Now().Unix(), "title", "message", nil, nil)
+ b2, _, err := backend.Bugs().NewRaw(rene, time.Now().Unix(), "title", "message", nil, nil)
require.NoError(t, err)
- err = Select(repoCache, b1.Id())
+ err = Select(backend, namespace, b1.Id())
require.NoError(t, err)
// normal select without args
- b3, _, err := ResolveBug(repoCache, []string{})
+ b3, _, err := resolve([]string{})
require.NoError(t, err)
require.Equal(t, b1.Id(), b3.Id())
// override selection with same id
- b4, _, err := ResolveBug(repoCache, []string{b1.Id().String()})
+ b4, _, err := resolve([]string{b1.Id().String()})
require.NoError(t, err)
require.Equal(t, b1.Id(), b4.Id())
// override selection with a prefix
- b5, _, err := ResolveBug(repoCache, []string{b1.Id().Human()})
+ b5, _, err := resolve([]string{b1.Id().Human()})
require.NoError(t, err)
require.Equal(t, b1.Id(), b5.Id())
// args that shouldn't override
- b6, _, err := ResolveBug(repoCache, []string{"arg"})
+ b6, _, err := resolve([]string{"arg"})
require.NoError(t, err)
require.Equal(t, b1.Id(), b6.Id())
// override with a different id
- b7, _, err := ResolveBug(repoCache, []string{b2.Id().String()})
+ b7, _, err := resolve([]string{b2.Id().String()})
require.NoError(t, err)
require.Equal(t, b2.Id(), b7.Id())
- err = Clear(repoCache)
+ err = Clear(backend, namespace)
require.NoError(t, err)
// Resolve without a pattern should error again after clearing the selected bug
- _, _, err = ResolveBug(repoCache, []string{})
+ _, _, err = resolve([]string{})
require.Error(t, err)
}