From acc9a6f3a6df2961c3ae44352216d915cb9b5315 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sat, 10 Sep 2022 11:09:19 +0200 Subject: commands: reorg into different packages --- commands/bridge/bridge.go | 43 ++++++ commands/bridge/bridge_auth.go | 67 +++++++++ commands/bridge/bridge_auth_addtoken.go | 133 ++++++++++++++++++ commands/bridge/bridge_auth_rm.go | 41 ++++++ commands/bridge/bridge_auth_show.go | 60 ++++++++ commands/bridge/bridge_new.go | 234 ++++++++++++++++++++++++++++++++ commands/bridge/bridge_pull.go | 155 +++++++++++++++++++++ commands/bridge/bridge_push.go | 99 ++++++++++++++ commands/bridge/bridge_rm.go | 36 +++++ 9 files changed, 868 insertions(+) create mode 100644 commands/bridge/bridge.go create mode 100644 commands/bridge/bridge_auth.go create mode 100644 commands/bridge/bridge_auth_addtoken.go create mode 100644 commands/bridge/bridge_auth_rm.go create mode 100644 commands/bridge/bridge_auth_show.go create mode 100644 commands/bridge/bridge_new.go create mode 100644 commands/bridge/bridge_pull.go create mode 100644 commands/bridge/bridge_push.go create mode 100644 commands/bridge/bridge_rm.go (limited to 'commands/bridge') diff --git a/commands/bridge/bridge.go b/commands/bridge/bridge.go new file mode 100644 index 00000000..980a38e2 --- /dev/null +++ b/commands/bridge/bridge.go @@ -0,0 +1,43 @@ +package bridgecmd + +import ( + "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/bridge" + "github.com/MichaelMure/git-bug/commands/execenv" +) + +func NewBridgeCommand() *cobra.Command { + env := execenv.NewEnv() + + cmd := &cobra.Command{ + Use: "bridge", + Short: "List bridges to other bug trackers", + PreRunE: execenv.LoadBackend(env), + RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error { + return runBridge(env) + }), + Args: cobra.NoArgs, + } + + cmd.AddCommand(newBridgeAuthCommand()) + cmd.AddCommand(newBridgeNewCommand()) + cmd.AddCommand(newBridgePullCommand()) + cmd.AddCommand(newBridgePushCommand()) + cmd.AddCommand(newBridgeRm()) + + return cmd +} + +func runBridge(env *execenv.Env) error { + configured, err := bridge.ConfiguredBridges(env.Backend) + if err != nil { + return err + } + + for _, c := range configured { + env.Out.Println(c) + } + + return nil +} diff --git a/commands/bridge/bridge_auth.go b/commands/bridge/bridge_auth.go new file mode 100644 index 00000000..52e063e6 --- /dev/null +++ b/commands/bridge/bridge_auth.go @@ -0,0 +1,67 @@ +package bridgecmd + +import ( + "sort" + "strings" + + text "github.com/MichaelMure/go-term-text" + "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/bridge/core/auth" + "github.com/MichaelMure/git-bug/commands/execenv" + "github.com/MichaelMure/git-bug/util/colors" +) + +func newBridgeAuthCommand() *cobra.Command { + env := execenv.NewEnv() + + cmd := &cobra.Command{ + Use: "auth", + Short: "List all known bridge authentication credentials", + PreRunE: execenv.LoadBackend(env), + RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error { + return runBridgeAuth(env) + }), + Args: cobra.NoArgs, + } + + cmd.AddCommand(newBridgeAuthAddTokenCommand()) + cmd.AddCommand(newBridgeAuthRm()) + cmd.AddCommand(newBridgeAuthShow()) + + return cmd +} + +func runBridgeAuth(env *execenv.Env) error { + creds, err := auth.List(env.Backend) + if err != nil { + return err + } + + for _, cred := range creds { + targetFmt := text.LeftPadMaxLine(cred.Target(), 10, 0) + + var value string + switch cred := cred.(type) { + case *auth.Token: + value = cred.Value + } + + meta := make([]string, 0, len(cred.Metadata())) + for k, v := range cred.Metadata() { + meta = append(meta, k+":"+v) + } + sort.Strings(meta) + metaFmt := strings.Join(meta, ",") + + env.Out.Printf("%s %s %s %s %s\n", + colors.Cyan(cred.ID().Human()), + colors.Yellow(targetFmt), + colors.Magenta(cred.Kind()), + value, + metaFmt, + ) + } + + return nil +} diff --git a/commands/bridge/bridge_auth_addtoken.go b/commands/bridge/bridge_auth_addtoken.go new file mode 100644 index 00000000..bcab7fc3 --- /dev/null +++ b/commands/bridge/bridge_auth_addtoken.go @@ -0,0 +1,133 @@ +package bridgecmd + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/mattn/go-isatty" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/bridge" + "github.com/MichaelMure/git-bug/bridge/core" + "github.com/MichaelMure/git-bug/bridge/core/auth" + "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/commands/completion" + "github.com/MichaelMure/git-bug/commands/execenv" +) + +type bridgeAuthAddTokenOptions struct { + target string + login string + user string +} + +func newBridgeAuthAddTokenCommand() *cobra.Command { + env := execenv.NewEnv() + options := bridgeAuthAddTokenOptions{} + + cmd := &cobra.Command{ + Use: "add-token [TOKEN]", + Short: "Store a new token", + PreRunE: execenv.LoadBackendEnsureUser(env), + RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error { + return runBridgeAuthAddToken(env, options, args) + }), + Args: cobra.MaximumNArgs(1), + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVarP(&options.target, "target", "t", "", + fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ","))) + cmd.RegisterFlagCompletionFunc("target", completion.From(bridge.Targets())) + flags.StringVarP(&options.login, + "login", "l", "", "The login in the remote bug-tracker") + flags.StringVarP(&options.user, + "user", "u", "", "The user to add the token to. Default is the current user") + cmd.RegisterFlagCompletionFunc("user", completion.User(env)) + + return cmd +} + +func runBridgeAuthAddToken(env *execenv.Env, opts bridgeAuthAddTokenOptions, args []string) error { + // Note: as bridgeAuthAddTokenLogin is not checked against the remote bug-tracker, + // it's possible to register a credential with an incorrect login (including bad case). + // The consequence is that it will not get picked later by the bridge. I find that + // checking it would require a cumbersome UX (need to provide a base URL for some bridges, ...) + // so it's probably not worth it, unless we refactor that entirely. + + if opts.target == "" { + return fmt.Errorf("flag --target is required") + } + if opts.login == "" { + return fmt.Errorf("flag --login is required") + } + + if !core.TargetExist(opts.target) { + return fmt.Errorf("unknown target") + } + + var value string + + if len(args) == 1 { + value = args[0] + } else { + // Read from Stdin + if isatty.IsTerminal(os.Stdin.Fd()) { + env.Err.Println("Enter the token:") + } + reader := bufio.NewReader(os.Stdin) + raw, err := reader.ReadString('\n') + if err != nil { + return fmt.Errorf("reading from stdin: %v", err) + } + value = strings.TrimSuffix(raw, "\n") + } + + var user *cache.IdentityCache + var err error + + if opts.user == "" { + user, err = env.Backend.GetUserIdentity() + } else { + user, err = env.Backend.ResolveIdentityPrefix(opts.user) + } + if err != nil { + return err + } + + metaKey, _ := bridge.LoginMetaKey(opts.target) + login, ok := user.ImmutableMetadata()[metaKey] + + switch { + case ok && login == opts.login: + // nothing to do + case ok && login != opts.login: + return fmt.Errorf("this user is already tagged with a different %s login", opts.target) + default: + user.SetMetadata(metaKey, opts.login) + err = user.Commit() + if err != nil { + return err + } + } + + token := auth.NewToken(opts.target, value) + token.SetMetadata(auth.MetaKeyLogin, opts.login) + + if err := token.Validate(); err != nil { + return errors.Wrap(err, "invalid token") + } + + err = auth.Store(env.Repo, token) + if err != nil { + return err + } + + env.Out.Printf("token %s added\n", token.ID()) + return nil +} diff --git a/commands/bridge/bridge_auth_rm.go b/commands/bridge/bridge_auth_rm.go new file mode 100644 index 00000000..d58ca63e --- /dev/null +++ b/commands/bridge/bridge_auth_rm.go @@ -0,0 +1,41 @@ +package bridgecmd + +import ( + "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/bridge/core/auth" + "github.com/MichaelMure/git-bug/commands/completion" + "github.com/MichaelMure/git-bug/commands/execenv" +) + +func newBridgeAuthRm() *cobra.Command { + env := execenv.NewEnv() + + cmd := &cobra.Command{ + Use: "rm BRIDGE_ID", + Short: "Remove a credential", + PreRunE: execenv.LoadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridgeAuthRm(env, args) + }, + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.BridgeAuth(env), + } + + return cmd +} + +func runBridgeAuthRm(env *execenv.Env, args []string) error { + cred, err := auth.LoadWithPrefix(env.Repo, args[0]) + if err != nil { + return err + } + + err = auth.Remove(env.Repo, cred.ID()) + if err != nil { + return err + } + + env.Out.Printf("credential %s removed\n", cred.ID()) + return nil +} diff --git a/commands/bridge/bridge_auth_show.go b/commands/bridge/bridge_auth_show.go new file mode 100644 index 00000000..d373273d --- /dev/null +++ b/commands/bridge/bridge_auth_show.go @@ -0,0 +1,60 @@ +package bridgecmd + +import ( + "fmt" + "sort" + "strings" + "time" + + "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/bridge/core/auth" + "github.com/MichaelMure/git-bug/commands/completion" + "github.com/MichaelMure/git-bug/commands/execenv" +) + +func newBridgeAuthShow() *cobra.Command { + env := execenv.NewEnv() + + cmd := &cobra.Command{ + Use: "show", + Short: "Display an authentication credential", + PreRunE: execenv.LoadBackend(env), + RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error { + return runBridgeAuthShow(env, args) + }), + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.BridgeAuth(env), + } + + return cmd +} + +func runBridgeAuthShow(env *execenv.Env, args []string) error { + cred, err := auth.LoadWithPrefix(env.Repo, args[0]) + if err != nil { + return err + } + + env.Out.Printf("Id: %s\n", cred.ID()) + env.Out.Printf("Target: %s\n", cred.Target()) + env.Out.Printf("Kind: %s\n", cred.Kind()) + env.Out.Printf("Creation: %s\n", cred.CreateTime().Format(time.RFC822)) + + switch cred := cred.(type) { + case *auth.Token: + env.Out.Printf("Value: %s\n", cred.Value) + } + + env.Out.Println("Metadata:") + + meta := make([]string, 0, len(cred.Metadata())) + for key, value := range cred.Metadata() { + meta = append(meta, fmt.Sprintf(" %s --> %s\n", key, value)) + } + sort.Strings(meta) + + env.Out.Print(strings.Join(meta, "")) + + return nil +} diff --git a/commands/bridge/bridge_new.go b/commands/bridge/bridge_new.go new file mode 100644 index 00000000..4cfc903d --- /dev/null +++ b/commands/bridge/bridge_new.go @@ -0,0 +1,234 @@ +package bridgecmd + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" + + "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/bridge" + "github.com/MichaelMure/git-bug/bridge/core" + "github.com/MichaelMure/git-bug/bridge/core/auth" + "github.com/MichaelMure/git-bug/commands/completion" + "github.com/MichaelMure/git-bug/commands/execenv" + "github.com/MichaelMure/git-bug/repository" +) + +type bridgeNewOptions struct { + name string + target string + params core.BridgeParams + token string + tokenStdin bool + nonInteractive bool +} + +func newBridgeNewCommand() *cobra.Command { + env := execenv.NewEnv() + options := bridgeNewOptions{} + + cmd := &cobra.Command{ + Use: "new", + Short: "Configure a new bridge", + Long: ` Configure a new bridge by passing flags or/and using interactive terminal prompts. You can avoid all the terminal prompts by passing all the necessary flags to configure your bridge.`, + Example: `# Interactive example +[1]: github +[2]: gitlab +[3]: jira +[4]: launchpad-preview + +target: 1 +name [default]: default + +Detected projects: +[1]: github.com/a-hilaly/git-bug +[2]: github.com/MichaelMure/git-bug + +[0]: Another project + +Select option: 1 + +[1]: user provided token +[2]: interactive token creation +Select option: 1 + +You can generate a new token by visiting https://github.com/settings/tokens. +Choose 'Generate new token' and set the necessary access scope for your repository. + +The access scope depend on the type of repository. +Public: + - 'public_repo': to be able to read public repositories +Private: + - 'repo' : to be able to read private repositories + +Enter token: 87cf5c03b64029f18ea5f9ca5679daa08ccbd700 +Successfully configured bridge: default + +# For GitHub +git bug bridge new \ + --name=default \ + --target=github \ + --owner=$(OWNER) \ + --project=$(PROJECT) \ + --token=$(TOKEN) + +# For Launchpad +git bug bridge new \ + --name=default \ + --target=launchpad-preview \ + --url=https://bugs.launchpad.net/ubuntu/ + +# For Gitlab +git bug bridge new \ + --name=default \ + --target=github \ + --url=https://github.com/michaelmure/git-bug \ + --token=$(TOKEN)`, + PreRunE: execenv.LoadBackend(env), + RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error { + return runBridgeNew(env, options) + }), + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVarP(&options.name, "name", "n", "", "A distinctive name to identify the bridge") + flags.StringVarP(&options.target, "target", "t", "", + fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ","))) + cmd.RegisterFlagCompletionFunc("target", completion.From(bridge.Targets())) + flags.StringVarP(&options.params.URL, "url", "u", "", "The URL of the remote repository") + flags.StringVarP(&options.params.BaseURL, "base-url", "b", "", "The base URL of your remote issue tracker") + flags.StringVarP(&options.params.Login, "login", "l", "", "The login on your remote issue tracker") + flags.StringVarP(&options.params.CredPrefix, "credential", "c", "", "The identifier or prefix of an already known credential for your remote issue tracker (see \"git-bug bridge auth\")") + flags.StringVar(&options.token, "token", "", "A raw authentication token for the remote issue tracker") + flags.BoolVar(&options.tokenStdin, "token-stdin", false, "Will read the token from stdin and ignore --token") + flags.StringVarP(&options.params.Owner, "owner", "o", "", "The owner of the remote repository") + flags.StringVarP(&options.params.Project, "project", "p", "", "The name of the remote repository") + flags.BoolVar(&options.nonInteractive, "non-interactive", false, "Do not ask for user input") + + return cmd +} + +func runBridgeNew(env *execenv.Env, opts bridgeNewOptions) error { + var err error + + if (opts.tokenStdin || opts.token != "" || opts.params.CredPrefix != "") && + (opts.name == "" || opts.target == "") { + return fmt.Errorf("you must provide a bridge name and target to configure a bridge with a credential") + } + + // early fail + if opts.params.CredPrefix != "" { + if _, err := auth.LoadWithPrefix(env.Repo, opts.params.CredPrefix); err != nil { + return err + } + } + + switch { + case opts.tokenStdin: + reader := bufio.NewReader(os.Stdin) + token, err := reader.ReadString('\n') + if err != nil { + return fmt.Errorf("reading from stdin: %v", err) + } + opts.params.TokenRaw = strings.TrimSpace(token) + case opts.token != "": + opts.params.TokenRaw = opts.token + } + + if !opts.nonInteractive && opts.target == "" { + opts.target, err = promptTarget() + if err != nil { + return err + } + } + + if !opts.nonInteractive && opts.name == "" { + opts.name, err = promptName(env.Repo) + if err != nil { + return err + } + } + + b, err := bridge.NewBridge(env.Backend, opts.target, opts.name) + if err != nil { + return err + } + + err = b.Configure(opts.params, !opts.nonInteractive) + if err != nil { + return err + } + + env.Out.Printf("Successfully configured bridge: %s\n", opts.name) + return nil +} + +func promptTarget() (string, error) { + // TODO: use the reusable prompt from the input package + targets := bridge.Targets() + + for { + for i, target := range targets { + fmt.Printf("[%d]: %s\n", i+1, target) + } + fmt.Printf("target: ") + + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + + if err != nil { + return "", err + } + + line = strings.TrimSpace(line) + + index, err := strconv.Atoi(line) + if err != nil || index <= 0 || index > len(targets) { + fmt.Println("invalid input") + continue + } + + return targets[index-1], nil + } +} + +func promptName(repo repository.RepoConfig) (string, error) { + // TODO: use the reusable prompt from the input package + const defaultName = "default" + + defaultExist := core.BridgeExist(repo, defaultName) + + for { + if defaultExist { + fmt.Printf("name: ") + } else { + fmt.Printf("name [%s]: ", defaultName) + } + + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { + return "", err + } + + line = strings.TrimSpace(line) + + name := line + if defaultExist && name == "" { + continue + } + + if name == "" { + name = defaultName + } + + if !core.BridgeExist(repo, name) { + return name, nil + } + + fmt.Println("a bridge with the same name already exist") + } +} diff --git a/commands/bridge/bridge_pull.go b/commands/bridge/bridge_pull.go new file mode 100644 index 00000000..d1fc279a --- /dev/null +++ b/commands/bridge/bridge_pull.go @@ -0,0 +1,155 @@ +package bridgecmd + +import ( + "context" + "fmt" + "os" + "sync" + "time" + + "github.com/araddon/dateparse" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/bridge" + "github.com/MichaelMure/git-bug/bridge/core" + "github.com/MichaelMure/git-bug/commands/completion" + "github.com/MichaelMure/git-bug/commands/execenv" + "github.com/MichaelMure/git-bug/util/interrupt" +) + +type bridgePullOptions struct { + importSince string + noResume bool +} + +func newBridgePullCommand() *cobra.Command { + env := execenv.NewEnv() + options := bridgePullOptions{} + + cmd := &cobra.Command{ + Use: "pull [NAME]", + Short: "Pull updates from a remote bug tracker", + PreRunE: execenv.LoadBackend(env), + RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error { + return runBridgePull(env, options, args) + }), + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: completion.Bridge(env), + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.BoolVarP(&options.noResume, "no-resume", "n", false, "force importing all bugs") + flags.StringVarP(&options.importSince, "since", "s", "", "import only bugs updated after the given date (ex: \"200h\" or \"june 2 2019\")") + + return cmd +} + +func runBridgePull(env *execenv.Env, opts bridgePullOptions, args []string) error { + if opts.noResume && opts.importSince != "" { + return fmt.Errorf("only one of --no-resume and --since flags should be used") + } + + var b *core.Bridge + var err error + + if len(args) == 0 { + b, err = bridge.DefaultBridge(env.Backend) + } else { + b, err = bridge.LoadBridge(env.Backend, args[0]) + } + + if err != nil { + return err + } + + parentCtx := context.Background() + ctx, cancel := context.WithCancel(parentCtx) + defer cancel() + + // buffered channel to avoid send block at the end + done := make(chan struct{}, 1) + + var mu sync.Mutex + interruptCount := 0 + interrupt.RegisterCleaner(func() error { + mu.Lock() + if interruptCount > 0 { + env.Err.Println("Received another interrupt before graceful stop, terminating...") + os.Exit(0) + } + + interruptCount++ + mu.Unlock() + + env.Err.Println("Received interrupt signal, stopping the import...\n(Hit ctrl-c again to kill the process.)") + + // send signal to stop the importer + cancel() + + // block until importer gracefully shutdown + <-done + return nil + }) + + var events <-chan core.ImportResult + switch { + case opts.noResume: + events, err = b.ImportAllSince(ctx, time.Time{}) + case opts.importSince != "": + since, err2 := parseSince(opts.importSince) + if err2 != nil { + return errors.Wrap(err2, "import time parsing") + } + events, err = b.ImportAllSince(ctx, since) + default: + events, err = b.ImportAll(ctx) + } + + if err != nil { + return err + } + + importedIssues := 0 + importedIdentities := 0 + for result := range events { + switch result.Event { + case core.ImportEventNothing: + // filtered + + case core.ImportEventBug: + importedIssues++ + env.Out.Println(result.String()) + + case core.ImportEventIdentity: + importedIdentities++ + env.Out.Println(result.String()) + + case core.ImportEventError: + if result.Err != context.Canceled { + env.Out.Println(result.String()) + } + + default: + env.Out.Println(result.String()) + } + } + + env.Out.Printf("imported %d issues and %d identities with %s bridge\n", importedIssues, importedIdentities, b.Name) + + // send done signal + close(done) + + return nil +} + +func parseSince(since string) (time.Time, error) { + duration, err := time.ParseDuration(since) + if err == nil { + return time.Now().Add(-duration), nil + } + + return dateparse.ParseLocal(since) +} diff --git a/commands/bridge/bridge_push.go b/commands/bridge/bridge_push.go new file mode 100644 index 00000000..51baed4d --- /dev/null +++ b/commands/bridge/bridge_push.go @@ -0,0 +1,99 @@ +package bridgecmd + +import ( + "context" + "os" + "sync" + "time" + + "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/bridge" + "github.com/MichaelMure/git-bug/bridge/core" + "github.com/MichaelMure/git-bug/commands/completion" + "github.com/MichaelMure/git-bug/commands/execenv" + "github.com/MichaelMure/git-bug/util/interrupt" +) + +func newBridgePushCommand() *cobra.Command { + env := execenv.NewEnv() + + cmd := &cobra.Command{ + Use: "push [NAME]", + Short: "Push updates to remote bug tracker", + PreRunE: execenv.LoadBackendEnsureUser(env), + RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error { + return runBridgePush(env, args) + }), + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: completion.Bridge(env), + } + + return cmd +} + +func runBridgePush(env *execenv.Env, args []string) error { + var b *core.Bridge + var err error + + if len(args) == 0 { + b, err = bridge.DefaultBridge(env.Backend) + } else { + b, err = bridge.LoadBridge(env.Backend, args[0]) + } + + if err != nil { + return err + } + + parentCtx := context.Background() + ctx, cancel := context.WithCancel(parentCtx) + defer cancel() + + done := make(chan struct{}, 1) + + var mu sync.Mutex + interruptCount := 0 + interrupt.RegisterCleaner(func() error { + mu.Lock() + if interruptCount > 0 { + env.Err.Println("Received another interrupt before graceful stop, terminating...") + os.Exit(0) + } + + interruptCount++ + mu.Unlock() + + env.Err.Println("Received interrupt signal, stopping the import...\n(Hit ctrl-c again to kill the process.)") + + // send signal to stop the importer + cancel() + + // block until importer gracefully shutdown + <-done + return nil + }) + + events, err := b.ExportAll(ctx, time.Time{}) + if err != nil { + return err + } + + exportedIssues := 0 + for result := range events { + if result.Event != core.ExportEventNothing { + env.Out.Println(result.String()) + } + + switch result.Event { + case core.ExportEventBug: + exportedIssues++ + } + } + + env.Out.Printf("exported %d issues with %s bridge\n", exportedIssues, b.Name) + + // send done signal + close(done) + return nil +} diff --git a/commands/bridge/bridge_rm.go b/commands/bridge/bridge_rm.go new file mode 100644 index 00000000..5d8d23c5 --- /dev/null +++ b/commands/bridge/bridge_rm.go @@ -0,0 +1,36 @@ +package bridgecmd + +import ( + "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/bridge" + "github.com/MichaelMure/git-bug/commands/completion" + "github.com/MichaelMure/git-bug/commands/execenv" +) + +func newBridgeRm() *cobra.Command { + env := execenv.NewEnv() + + cmd := &cobra.Command{ + Use: "rm NAME", + Short: "Delete a configured bridge", + PreRunE: execenv.LoadBackend(env), + RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error { + return runBridgeRm(env, args) + }), + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.Bridge(env), + } + + return cmd +} + +func runBridgeRm(env *execenv.Env, args []string) error { + err := bridge.RemoveBridge(env.Backend, args[0]) + if err != nil { + return err + } + + env.Out.Printf("Successfully removed bridge configuration %v\n", args[0]) + return nil +} -- cgit