mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-17 04:48:58 +03:00
Merge pull request #962 from MichaelMure/select-completion
commands: generic "select" code, move bug completion in bugcmd
This commit is contained in:
commit
d11ea5c2ad
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
98
commands/bug/completion.go
Normal file
98
commands/bug/completion.go
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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 != "" {
|
||||
|
156
commands/select/select.go
Normal file
156
commands/select/select.go
Normal file
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user