diff options
author | Michael Muré <batolettre@gmail.com> | 2020-06-28 18:26:29 +0200 |
---|---|---|
committer | Michael Muré <batolettre@gmail.com> | 2020-06-28 18:26:29 +0200 |
commit | 26bd1dd11010b4d86cebe2510ad7085a6b316334 (patch) | |
tree | f1fe939311c75bd615071e96f3d37822cccd77a7 /commands | |
parent | c0dbc149d5c0c3610476ba14a800c9ba803a2c2c (diff) | |
download | git-bug-26bd1dd11010b4d86cebe2510ad7085a6b316334.tar.gz |
commands: refactor to avoid globals
Diffstat (limited to 'commands')
38 files changed, 1328 insertions, 1111 deletions
diff --git a/commands/add.go b/commands/add.go index e656a262..8b5facaf 100644 --- a/commands/add.go +++ b/commands/add.go @@ -1,42 +1,65 @@ package commands import ( - "fmt" + "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/input" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -var ( - addTitle string - addMessage string - addMessageFile string -) +type addOptions struct { + title string + message string + messageFile string +} + +func newAddCommand() *cobra.Command { + env := newEnv() + options := addOptions{} + + cmd := &cobra.Command{ + Use: "add", + Short: "Create a new bug.", + PreRunE: loadRepoEnsureUser(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runAdd(env, options) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false -func runAddBug(cmd *cobra.Command, args []string) error { - var err error + flags.StringVarP(&options.title, "title", "t", "", + "Provide a title to describe the issue") + flags.StringVarP(&options.message, "message", "m", "", + "Provide a message to describe the issue") + flags.StringVarP(&options.messageFile, "file", "F", "", + "Take the message from the given file. Use - to read the message from the standard input") - backend, err := cache.NewRepoCache(repo) + return cmd +} + +func runAdd(env *Env, opts addOptions) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } defer backend.Close() interrupt.RegisterCleaner(backend.Close) - if addMessageFile != "" && addMessage == "" { - addTitle, addMessage, err = input.BugCreateFileInput(addMessageFile) + if opts.messageFile != "" && opts.message == "" { + opts.title, opts.message, err = input.BugCreateFileInput(opts.messageFile) if err != nil { return err } } - if addMessageFile == "" && (addMessage == "" || addTitle == "") { - addTitle, addMessage, err = input.BugCreateEditorInput(backend, addTitle, addMessage) + if opts.messageFile == "" && (opts.message == "" || opts.title == "") { + opts.title, opts.message, err = input.BugCreateEditorInput(backend, opts.title, opts.message) if err == input.ErrEmptyTitle { - fmt.Println("Empty title, aborting.") + env.out.Println("Empty title, aborting.") return nil } if err != nil { @@ -44,35 +67,12 @@ func runAddBug(cmd *cobra.Command, args []string) error { } } - b, _, err := backend.NewBug(addTitle, addMessage) + b, _, err := backend.NewBug(opts.title, opts.message) if err != nil { return err } - fmt.Printf("%s created\n", b.Id().Human()) + env.out.Printf("%s created\n", b.Id().Human()) return nil } - -var addCmd = &cobra.Command{ - Use: "add", - Short: "Create a new bug.", - PreRunE: loadRepoEnsureUser, - RunE: runAddBug, -} - -func init() { - RootCmd.AddCommand(addCmd) - - addCmd.Flags().SortFlags = false - - addCmd.Flags().StringVarP(&addTitle, "title", "t", "", - "Provide a title to describe the issue", - ) - addCmd.Flags().StringVarP(&addMessage, "message", "m", "", - "Provide a message to describe the issue", - ) - addCmd.Flags().StringVarP(&addMessageFile, "file", "F", "", - "Take the message from the given file. Use - to read the message from the standard input", - ) -} diff --git a/commands/bridge.go b/commands/bridge.go index 3c398e6b..8a2cf38b 100644 --- a/commands/bridge.go +++ b/commands/bridge.go @@ -1,8 +1,6 @@ package commands import ( - "fmt" - "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/bridge" @@ -10,8 +8,30 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -func runBridge(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newBridgeCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "bridge", + Short: "Configure and use bridges to other bug trackers.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridge(env) + }, + Args: cobra.NoArgs, + } + + cmd.AddCommand(newBridgeAuthCommand()) + cmd.AddCommand(newBridgeConfigureCommand()) + cmd.AddCommand(newBridgePullCommand()) + cmd.AddCommand(newBridgePushCommand()) + cmd.AddCommand(newBridgeRm()) + + return cmd +} + +func runBridge(env *Env) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -24,20 +44,8 @@ func runBridge(cmd *cobra.Command, args []string) error { } for _, c := range configured { - fmt.Println(c) + env.out.Println(c) } return nil } - -var bridgeCmd = &cobra.Command{ - Use: "bridge", - Short: "Configure and use bridges to other bug trackers.", - PreRunE: loadRepo, - RunE: runBridge, - Args: cobra.NoArgs, -} - -func init() { - RootCmd.AddCommand(bridgeCmd) -} diff --git a/commands/bridge_auth.go b/commands/bridge_auth.go index 3a0e0c29..e51b9b9d 100644 --- a/commands/bridge_auth.go +++ b/commands/bridge_auth.go @@ -1,7 +1,6 @@ package commands import ( - "fmt" "sort" "strings" @@ -15,8 +14,28 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -func runBridgeAuth(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newBridgeAuthCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "auth", + Short: "List all known bridge authentication credentials.", + PreRunE: loadRepo(env), + RunE: 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 *Env) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -44,7 +63,7 @@ func runBridgeAuth(cmd *cobra.Command, args []string) error { sort.Strings(meta) metaFmt := strings.Join(meta, ",") - fmt.Printf("%s %s %s %s %s\n", + env.out.Printf("%s %s %s %s %s\n", colors.Cyan(cred.ID().Human()), colors.Yellow(targetFmt), colors.Magenta(cred.Kind()), @@ -55,16 +74,3 @@ func runBridgeAuth(cmd *cobra.Command, args []string) error { return nil } - -var bridgeAuthCmd = &cobra.Command{ - Use: "auth", - Short: "List all known bridge authentication credentials.", - PreRunE: loadRepo, - RunE: runBridgeAuth, - Args: cobra.NoArgs, -} - -func init() { - bridgeCmd.AddCommand(bridgeAuthCmd) - bridgeAuthCmd.Flags().SortFlags = false -} diff --git a/commands/bridge_auth_addtoken.go b/commands/bridge_auth_addtoken.go index 8eda28ac..dde7a6dd 100644 --- a/commands/bridge_auth_addtoken.go +++ b/commands/bridge_auth_addtoken.go @@ -17,34 +17,61 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -var ( - bridgeAuthAddTokenTarget string - bridgeAuthAddTokenLogin string - bridgeAuthAddTokenUser string -) +type bridgeAuthAddTokenOptions struct { + target string + login string + user string +} + +func newBridgeAuthAddTokenCommand() *cobra.Command { + env := newEnv() + options := bridgeAuthAddTokenOptions{} + + cmd := &cobra.Command{ + Use: "add-token [<token>]", + Short: "Store a new token", + PreRunE: loadRepoEnsureUser(env), + RunE: 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(), ","))) + 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") -func runBridgeTokenAdd(cmd *cobra.Command, args []string) error { + return cmd +} + +func runBridgeAuthAddToken(env *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 bridgeAuthAddTokenTarget == "" { + if opts.target == "" { return fmt.Errorf("flag --target is required") } - if bridgeAuthAddTokenLogin == "" { + if opts.login == "" { return fmt.Errorf("flag --login is required") } - backend, err := cache.NewRepoCache(repo) + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } defer backend.Close() interrupt.RegisterCleaner(backend.Close) - if !core.TargetExist(bridgeAuthAddTokenTarget) { + if !core.TargetExist(opts.target) { return fmt.Errorf("unknown target") } @@ -55,7 +82,7 @@ func runBridgeTokenAdd(cmd *cobra.Command, args []string) error { } else { // Read from Stdin if isatty.IsTerminal(os.Stdin.Fd()) { - fmt.Println("Enter the token:") + env.err.Println("Enter the token:") } reader := bufio.NewReader(os.Stdin) raw, err := reader.ReadString('\n') @@ -67,62 +94,43 @@ func runBridgeTokenAdd(cmd *cobra.Command, args []string) error { var user *cache.IdentityCache - if bridgeAuthAddTokenUser == "" { + if opts.user == "" { user, err = backend.GetUserIdentity() } else { - user, err = backend.ResolveIdentityPrefix(bridgeAuthAddTokenUser) + user, err = backend.ResolveIdentityPrefix(opts.user) } if err != nil { return err } - metaKey, _ := bridge.LoginMetaKey(bridgeAuthAddTokenTarget) + metaKey, _ := bridge.LoginMetaKey(opts.target) login, ok := user.ImmutableMetadata()[metaKey] switch { - case ok && login == bridgeAuthAddTokenLogin: + case ok && login == opts.login: // nothing to do - case ok && login != bridgeAuthAddTokenLogin: - return fmt.Errorf("this user is already tagged with a different %s login", bridgeAuthAddTokenTarget) + case ok && login != opts.login: + return fmt.Errorf("this user is already tagged with a different %s login", opts.target) default: - user.SetMetadata(metaKey, bridgeAuthAddTokenLogin) + user.SetMetadata(metaKey, opts.login) err = user.Commit() if err != nil { return err } } - token := auth.NewToken(bridgeAuthAddTokenTarget, value) - token.SetMetadata(auth.MetaKeyLogin, bridgeAuthAddTokenLogin) + 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(repo, token) + err = auth.Store(env.repo, token) if err != nil { return err } - fmt.Printf("token %s added\n", token.ID()) + env.out.Printf("token %s added\n", token.ID()) return nil } - -var bridgeAuthAddTokenCmd = &cobra.Command{ - Use: "add-token [<token>]", - Short: "Store a new token", - PreRunE: loadRepoEnsureUser, - RunE: runBridgeTokenAdd, - Args: cobra.MaximumNArgs(1), -} - -func init() { - bridgeAuthCmd.AddCommand(bridgeAuthAddTokenCmd) - bridgeAuthAddTokenCmd.Flags().StringVarP(&bridgeAuthAddTokenTarget, "target", "t", "", - fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ","))) - bridgeAuthAddTokenCmd.Flags().StringVarP(&bridgeAuthAddTokenLogin, - "login", "l", "", "The login in the remote bug-tracker") - bridgeAuthAddTokenCmd.Flags().StringVarP(&bridgeAuthAddTokenUser, - "user", "u", "", "The user to add the token to. Default is the current user") - bridgeAuthAddTokenCmd.Flags().SortFlags = false -} diff --git a/commands/bridge_auth_rm.go b/commands/bridge_auth_rm.go index 17e70625..b2a44e92 100644 --- a/commands/bridge_auth_rm.go +++ b/commands/bridge_auth_rm.go @@ -1,36 +1,38 @@ package commands import ( - "fmt" - "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/bridge/core/auth" ) -func runBridgeAuthRm(cmd *cobra.Command, args []string) error { - cred, err := auth.LoadWithPrefix(repo, args[0]) +func newBridgeAuthRm() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "rm <id>", + Short: "Remove a credential.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridgeAuthRm(env, args) + }, + Args: cobra.ExactArgs(1), + } + + return cmd +} + +func runBridgeAuthRm(env *Env, args []string) error { + cred, err := auth.LoadWithPrefix(env.repo, args[0]) if err != nil { return err } - err = auth.Remove(repo, cred.ID()) + err = auth.Remove(env.repo, cred.ID()) if err != nil { return err } - fmt.Printf("credential %s removed\n", cred.ID()) + env.out.Printf("credential %s removed\n", cred.ID()) return nil } - -var bridgeAuthRmCmd = &cobra.Command{ - Use: "rm <id>", - Short: "Remove a credential.", - PreRunE: loadRepo, - RunE: runBridgeAuthRm, - Args: cobra.ExactArgs(1), -} - -func init() { - bridgeAuthCmd.AddCommand(bridgeAuthRmCmd) -} diff --git a/commands/bridge_auth_show.go b/commands/bridge_auth_show.go index fbbf60a7..f8b15c9a 100644 --- a/commands/bridge_auth_show.go +++ b/commands/bridge_auth_show.go @@ -13,30 +13,46 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -func runBridgeAuthShow(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newBridgeAuthShow() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "show", + Short: "Display an authentication credential.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridgeAuthShow(env, args) + }, + Args: cobra.ExactArgs(1), + } + + return cmd +} + +func runBridgeAuthShow(env *Env, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } defer backend.Close() interrupt.RegisterCleaner(backend.Close) - cred, err := auth.LoadWithPrefix(repo, args[0]) + cred, err := auth.LoadWithPrefix(env.repo, args[0]) if err != nil { return err } - fmt.Printf("Id: %s\n", cred.ID()) - fmt.Printf("Target: %s\n", cred.Target()) - fmt.Printf("Kind: %s\n", cred.Kind()) - fmt.Printf("Creation: %s\n", cred.CreateTime().Format(time.RFC822)) + 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: - fmt.Printf("Value: %s\n", cred.Value) + env.out.Printf("Value: %s\n", cred.Value) } - fmt.Println("Metadata:") + env.out.Println("Metadata:") meta := make([]string, 0, len(cred.Metadata())) for key, value := range cred.Metadata() { @@ -44,19 +60,7 @@ func runBridgeAuthShow(cmd *cobra.Command, args []string) error { } sort.Strings(meta) - fmt.Print(strings.Join(meta, "")) + env.out.Print(strings.Join(meta, "")) return nil } - -var bridgeAuthShowCmd = &cobra.Command{ - Use: "show", - Short: "Display an authentication credential.", - PreRunE: loadRepo, - RunE: runBridgeAuthShow, - Args: cobra.ExactArgs(1), -} - -func init() { - bridgeAuthCmd.AddCommand(bridgeAuthShowCmd) -} diff --git a/commands/bridge_configure.go b/commands/bridge_configure.go index 89553633..83555b0c 100644 --- a/commands/bridge_configure.go +++ b/commands/bridge_configure.go @@ -17,79 +17,161 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -const ( - defaultName = "default" -) +type bridgeConfigureOptions struct { + name string + target string + params core.BridgeParams + token string + tokenStdin bool +} -var ( - bridgeConfigureName string - bridgeConfigureTarget string - bridgeConfigureParams core.BridgeParams - bridgeConfigureToken string - bridgeConfigureTokenStdin bool -) +func newBridgeConfigureCommand() *cobra.Command { + env := newEnv() + options := bridgeConfigureOptions{} + + cmd := &cobra.Command{ + Use: "configure", + 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 configure \ + --name=default \ + --target=github \ + --owner=$(OWNER) \ + --project=$(PROJECT) \ + --token=$(TOKEN) + +# For Launchpad +git bug bridge configure \ + --name=default \ + --target=launchpad-preview \ + --url=https://bugs.launchpad.net/ubuntu/ -func runBridgeConfigure(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +# For Gitlab +git bug bridge configure \ + --name=default \ + --target=github \ + --url=https://github.com/michaelmure/git-bug \ + --token=$(TOKEN)`, + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridgeConfigure(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(), ","))) + 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") + + return cmd +} + +func runBridgeConfigure(env *Env, opts bridgeConfigureOptions) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } defer backend.Close() interrupt.RegisterCleaner(backend.Close) - if (bridgeConfigureTokenStdin || bridgeConfigureToken != "" || bridgeConfigureParams.CredPrefix != "") && - (bridgeConfigureName == "" || bridgeConfigureTarget == "") { + 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 bridgeConfigureParams.CredPrefix != "" { - if _, err := auth.LoadWithPrefix(repo, bridgeConfigureParams.CredPrefix); err != nil { + if opts.params.CredPrefix != "" { + if _, err := auth.LoadWithPrefix(env.repo, opts.params.CredPrefix); err != nil { return err } } switch { - case bridgeConfigureTokenStdin: + case opts.tokenStdin: reader := bufio.NewReader(os.Stdin) token, err := reader.ReadString('\n') if err != nil { return fmt.Errorf("reading from stdin: %v", err) } - bridgeConfigureParams.TokenRaw = strings.TrimSpace(token) - case bridgeConfigureToken != "": - bridgeConfigureParams.TokenRaw = bridgeConfigureToken + opts.params.TokenRaw = strings.TrimSpace(token) + case opts.token != "": + opts.params.TokenRaw = opts.token } - if bridgeConfigureTarget == "" { - bridgeConfigureTarget, err = promptTarget() + if opts.target == "" { + opts.target, err = promptTarget() if err != nil { return err } } - if bridgeConfigureName == "" { - bridgeConfigureName, err = promptName(repo) + if opts.name == "" { + opts.name, err = promptName(env.repo) if err != nil { return err } } - b, err := bridge.NewBridge(backend, bridgeConfigureTarget, bridgeConfigureName) + b, err := bridge.NewBridge(backend, opts.target, opts.name) if err != nil { return err } - err = b.Configure(bridgeConfigureParams) + err = b.Configure(opts.params) if err != nil { return err } - fmt.Printf("Successfully configured bridge: %s\n", bridgeConfigureName) + 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 { @@ -117,6 +199,9 @@ func promptTarget() (string, error) { } 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 { @@ -149,80 +234,3 @@ func promptName(repo repository.RepoConfig) (string, error) { fmt.Println("a bridge with the same name already exist") } } - -var bridgeConfigureCmd = &cobra.Command{ - Use: "configure", - 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 configure \ - --name=default \ - --target=github \ - --owner=$(OWNER) \ - --project=$(PROJECT) \ - --token=$(TOKEN) - -# For Launchpad -git bug bridge configure \ - --name=default \ - --target=launchpad-preview \ - --url=https://bugs.launchpad.net/ubuntu/ - -# For Gitlab -git bug bridge configure \ - --name=default \ - --target=github \ - --url=https://github.com/michaelmure/git-bug \ - --token=$(TOKEN)`, - PreRunE: loadRepo, - RunE: runBridgeConfigure, -} - -func init() { - bridgeCmd.AddCommand(bridgeConfigureCmd) - bridgeConfigureCmd.Flags().StringVarP(&bridgeConfigureName, "name", "n", "", "A distinctive name to identify the bridge") - bridgeConfigureCmd.Flags().StringVarP(&bridgeConfigureTarget, "target", "t", "", - fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ","))) - bridgeConfigureCmd.Flags().StringVarP(&bridgeConfigureParams.URL, "url", "u", "", "The URL of the remote repository") - bridgeConfigureCmd.Flags().StringVarP(&bridgeConfigureParams.BaseURL, "base-url", "b", "", "The base URL of your remote issue tracker") - bridgeConfigureCmd.Flags().StringVarP(&bridgeConfigureParams.Login, "login", "l", "", "The login on your remote issue tracker") - bridgeConfigureCmd.Flags().StringVarP(&bridgeConfigureParams.CredPrefix, "credential", "c", "", "The identifier or prefix of an already known credential for your remote issue tracker (see \"git-bug bridge auth\")") - bridgeConfigureCmd.Flags().StringVar(&bridgeConfigureToken, "token", "", "A raw authentication token for the remote issue tracker") - bridgeConfigureCmd.Flags().BoolVar(&bridgeConfigureTokenStdin, "token-stdin", false, "Will read the token from stdin and ignore --token") - bridgeConfigureCmd.Flags().StringVarP(&bridgeConfigureParams.Owner, "owner", "o", "", "The owner of the remote repository") - bridgeConfigureCmd.Flags().StringVarP(&bridgeConfigureParams.Project, "project", "p", "", "The name of the remote repository") - bridgeConfigureCmd.Flags().SortFlags = false -} diff --git a/commands/bridge_pull.go b/commands/bridge_pull.go index 2dd3d93e..dc8825fa 100644 --- a/commands/bridge_pull.go +++ b/commands/bridge_pull.go @@ -17,17 +17,40 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -var ( - bridgePullImportSince string - bridgePullNoResume bool -) +type bridgePullOptions struct { + importSince string + noResume bool +} + +func newBridgePullCommand() *cobra.Command { + env := newEnv() + options := bridgePullOptions{} + + cmd := &cobra.Command{ + Use: "pull [<name>]", + Short: "Pull updates.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridgePull(env, options, args) + }, + Args: cobra.MaximumNArgs(1), + } + + 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\")") -func runBridgePull(cmd *cobra.Command, args []string) error { - if bridgePullNoResume && bridgePullImportSince != "" { + return cmd +} + +func runBridgePull(env *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") } - backend, err := cache.NewRepoCache(repo) + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -58,14 +81,14 @@ func runBridgePull(cmd *cobra.Command, args []string) error { interrupt.RegisterCleaner(func() error { mu.Lock() if interruptCount > 0 { - fmt.Println("Received another interrupt before graceful stop, terminating...") + env.err.Println("Received another interrupt before graceful stop, terminating...") os.Exit(0) } interruptCount++ mu.Unlock() - fmt.Println("Received interrupt signal, stopping the import...\n(Hit ctrl-c again to kill the process.)") + 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() @@ -77,10 +100,10 @@ func runBridgePull(cmd *cobra.Command, args []string) error { var events <-chan core.ImportResult switch { - case bridgePullNoResume: + case opts.noResume: events, err = b.ImportAllSince(ctx, time.Time{}) - case bridgePullImportSince != "": - since, err2 := parseSince(bridgePullImportSince) + case opts.importSince != "": + since, err2 := parseSince(opts.importSince) if err2 != nil { return errors.Wrap(err2, "import time parsing") } @@ -102,23 +125,23 @@ func runBridgePull(cmd *cobra.Command, args []string) error { case core.ImportEventBug: importedIssues++ - fmt.Println(result.String()) + env.out.Println(result.String()) case core.ImportEventIdentity: importedIdentities++ - fmt.Println(result.String()) + env.out.Println(result.String()) case core.ImportEventError: if result.Err != context.Canceled { - fmt.Println(result.String()) + env.out.Println(result.String()) } default: - fmt.Println(result.String()) + env.out.Println(result.String()) } } - fmt.Printf("imported %d issues and %d identities with %s bridge\n", importedIssues, importedIdentities, b.Name) + env.out.Printf("imported %d issues and %d identities with %s bridge\n", importedIssues, importedIdentities, b.Name) // send done signal close(done) @@ -134,17 +157,3 @@ func parseSince(since string) (time.Time, error) { return dateparse.ParseLocal(since) } - -var bridgePullCmd = &cobra.Command{ - Use: "pull [<name>]", - Short: "Pull updates.", - PreRunE: loadRepo, - RunE: runBridgePull, - Args: cobra.MaximumNArgs(1), -} - -func init() { - bridgeCmd.AddCommand(bridgePullCmd) - bridgePullCmd.Flags().BoolVarP(&bridgePullNoResume, "no-resume", "n", false, "force importing all bugs") - bridgePullCmd.Flags().StringVarP(&bridgePullImportSince, "since", "s", "", "import only bugs updated after the given date (ex: \"200h\" or \"june 2 2019\")") -} diff --git a/commands/bridge_push.go b/commands/bridge_push.go index 52d23a97..73df87fb 100644 --- a/commands/bridge_push.go +++ b/commands/bridge_push.go @@ -2,7 +2,6 @@ package commands import ( "context" - "fmt" "os" "sync" "time" @@ -15,8 +14,24 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -func runBridgePush(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newBridgePushCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "push [<name>]", + Short: "Push updates.", + PreRunE: loadRepoEnsureUser(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridgePush(env, args) + }, + Args: cobra.MaximumNArgs(1), + } + + return cmd +} + +func runBridgePush(env *Env, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -46,14 +61,14 @@ func runBridgePush(cmd *cobra.Command, args []string) error { interrupt.RegisterCleaner(func() error { mu.Lock() if interruptCount > 0 { - fmt.Println("Received another interrupt before graceful stop, terminating...") + env.err.Println("Received another interrupt before graceful stop, terminating...") os.Exit(0) } interruptCount++ mu.Unlock() - fmt.Println("Received interrupt signal, stopping the import...\n(Hit ctrl-c again to kill the process.)") + 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() @@ -71,7 +86,7 @@ func runBridgePush(cmd *cobra.Command, args []string) error { exportedIssues := 0 for result := range events { if result.Event != core.ExportEventNothing { - fmt.Println(result.String()) + env.out.Println(result.String()) } switch result.Event { @@ -80,21 +95,9 @@ func runBridgePush(cmd *cobra.Command, args []string) error { } } - fmt.Printf("exported %d issues with %s bridge\n", exportedIssues, b.Name) + env.out.Printf("exported %d issues with %s bridge\n", exportedIssues, b.Name) // send done signal close(done) return nil } - -var bridgePushCmd = &cobra.Command{ - Use: "push [<name>]", - Short: "Push updates.", - PreRunE: loadRepoEnsureUser, - RunE: runBridgePush, - Args: cobra.MaximumNArgs(1), -} - -func init() { - bridgeCmd.AddCommand(bridgePushCmd) -} diff --git a/commands/bridge_rm.go b/commands/bridge_rm.go index 76ad5949..4ff963db 100644 --- a/commands/bridge_rm.go +++ b/commands/bridge_rm.go @@ -1,8 +1,6 @@ package commands import ( - "fmt" - "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/bridge" @@ -10,8 +8,24 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -func runBridgeRm(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newBridgeRm() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "rm <name>", + Short: "Delete a configured bridge.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridgeRm(env, args) + }, + Args: cobra.ExactArgs(1), + } + + return cmd +} + +func runBridgeRm(env *Env, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -23,18 +37,6 @@ func runBridgeRm(cmd *cobra.Command, args []string) error { return err } - fmt.Printf("Successfully removed bridge configuration %v\n", args[0]) + env.out.Printf("Successfully removed bridge configuration %v\n", args[0]) return nil } - -var bridgeRmCmd = &cobra.Command{ - Use: "rm <name>", - Short: "Delete a configured bridge.", - PreRunE: loadRepo, - RunE: runBridgeRm, - Args: cobra.ExactArgs(1), -} - -func init() { - bridgeCmd.AddCommand(bridgeRmCmd) -} diff --git a/commands/commands.go b/commands/commands.go index bd8cb14d..103d5412 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -1,27 +1,42 @@ package commands import ( - "fmt" "sort" "github.com/spf13/cobra" ) -var ( - commandsDesc bool -) +type commandOptions struct { + desc bool +} -type commandSorterByName []*cobra.Command +func newCommandsCommand() *cobra.Command { + env := newEnv() + options := commandOptions{} -func (c commandSorterByName) Len() int { return len(c) } -func (c commandSorterByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] } -func (c commandSorterByName) Less(i, j int) bool { return c[i].CommandPath() < c[j].CommandPath() } + cmd := &cobra.Command{ + Use: "commands [<option>...]", + Short: "Display available commands.", + RunE: func(cmd *cobra.Command, args []string) error { + return runCommands(env, options) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.BoolVarP(&options.desc, "pretty", "p", false, + "Output the command description as well as Markdown compatible comment", + ) -func runCommands(cmd *cobra.Command, args []string) error { + return cmd +} + +func runCommands(env *Env, opts commandOptions) error { first := true var allCmds []*cobra.Command - queue := []*cobra.Command{RootCmd} + queue := []*cobra.Command{NewRootCommand()} for len(queue) > 0 { cmd := queue[0] @@ -34,41 +49,31 @@ func runCommands(cmd *cobra.Command, args []string) error { for _, cmd := range allCmds { if !first { - fmt.Println() + env.out.Println() } first = false - if commandsDesc { - fmt.Printf("# %s\n", cmd.Short) + if opts.desc { + env.out.Printf("# %s\n", cmd.Short) } - fmt.Print(cmd.UseLine()) + env.out.Print(cmd.UseLine()) - if commandsDesc { - fmt.Println() + if opts.desc { + env.out.Println() } } - if !commandsDesc { - fmt.Println() + if !opts.desc { + env.out.Println() } return nil } -var commandsCmd = &cobra.Command{ - Use: "commands [<option>...]", - Short: "Display available commands.", - RunE: runCommands, -} - -func init() { - RootCmd.AddCommand(commandsCmd) - - commandsCmd.Flags().SortFlags = false +type commandSorterByName []*cobra.Command - commandsCmd.Flags().BoolVarP(&commandsDesc, "pretty", "p", false, - "Output the command description as well as Markdown compatible comment", - ) -} +func (c commandSorterByName) Len() int { return len(c) } +func (c commandSorterByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +func (c commandSorterByName) Less(i, j int) bool { return c[i].CommandPath() < c[j].CommandPath() } diff --git a/commands/comment.go b/commands/comment.go index 4be39a84..82e7d9f6 100644 --- a/commands/comment.go +++ b/commands/comment.go @@ -1,20 +1,34 @@ package commands import ( - "fmt" - - "github.com/MichaelMure/go-term-text" + text "github.com/MichaelMure/go-term-text" "github.com/spf13/cobra" - "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/commands/select" "github.com/MichaelMure/git-bug/util/colors" "github.com/MichaelMure/git-bug/util/interrupt" ) -func runComment(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newCommentCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "comment [<id>]", + Short: "Display or add comments to a bug.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runComment(env, args) + }, + } + + cmd.AddCommand(newCommentAddCommand()) + + return cmd +} + +func runComment(env *Env, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -28,33 +42,16 @@ func runComment(cmd *cobra.Command, args []string) error { snap := b.Snapshot() - commentsTextOutput(snap.Comments) - - return nil -} - -func commentsTextOutput(comments []bug.Comment) { - for i, comment := range comments { + for i, comment := range snap.Comments { if i != 0 { - fmt.Println() + env.out.Println() } - fmt.Printf("Author: %s\n", colors.Magenta(comment.Author.DisplayName())) - fmt.Printf("Id: %s\n", colors.Cyan(comment.Id().Human())) - fmt.Printf("Date: %s\n\n", comment.FormatTime()) - fmt.Println(text.LeftPadLines(comment.Message, 4)) + env.out.Printf("Author: %s\n", colors.Magenta(comment.Author.DisplayName())) + env.out.Printf("Id: %s\n", colors.Cyan(comment.Id().Human())) + env.out.Printf("Date: %s\n\n", comment.FormatTime()) + env.out.Println(text.LeftPadLines(comment.Message, 4)) } -} -var commentCmd = &cobra.Command{ - Use: "comment [<id>]", - Short: "Display or add comments to a bug.", - PreRunE: loadRepo, - RunE: runComment, -} - -func init() { - RootCmd.AddCommand(commentCmd) - - commentCmd.Flags().SortFlags = false + return nil } diff --git a/commands/comment_add.go b/commands/comment_add.go index dfd63e38..1a560b5e 100644 --- a/commands/comment_add.go +++ b/commands/comment_add.go @@ -1,22 +1,46 @@ package commands import ( - "fmt" + "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/commands/select" "github.com/MichaelMure/git-bug/input" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -var ( - commentAddMessageFile string - commentAddMessage string -) +type commentAddOptions struct { + messageFile string + message string +} + +func newCommentAddCommand() *cobra.Command { + env := newEnv() + options := commentAddOptions{} + + cmd := &cobra.Command{ + Use: "add [<id>]", + Short: "Add a new comment to a bug.", + PreRunE: loadRepoEnsureUser(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runCommentAdd(env, options, args) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVarP(&options.messageFile, "file", "F", "", + "Take the message from the given file. Use - to read the message from the standard input") + + flags.StringVarP(&options.message, "message", "m", "", + "Provide the new message from the command line") -func runCommentAdd(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) + return cmd +} + +func runCommentAdd(env *Env, opts commentAddOptions, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -28,17 +52,17 @@ func runCommentAdd(cmd *cobra.Command, args []string) error { return err } - if commentAddMessageFile != "" && commentAddMessage == "" { - commentAddMessage, err = input.BugCommentFileInput(commentAddMessageFile) + if opts.messageFile != "" && opts.message == "" { + opts.message, err = input.BugCommentFileInput(opts.messageFile) if err != nil { return err } } - if commentAddMessageFile == "" && commentAddMessage == "" { - commentAddMessage, err = input.BugCommentEditorInput(backend, "") + if opts.messageFile == "" && opts.message == "" { + opts.message, err = input.BugCommentEditorInput(backend, "") if err == input.ErrEmptyMessage { - fmt.Println("Empty message, aborting.") + env.err.Println("Empty message, aborting.") return nil } if err != nil { @@ -46,31 +70,10 @@ func runCommentAdd(cmd *cobra.Command, args []string) error { } } - _, err = b.AddComment(commentAddMessage) + _, err = b.AddComment(opts.message) if err != nil { return err } return b.Commit() } - -var commentAddCmd = &cobra.Command{ - Use: "add [<id>]", - Short: "Add a new comment to a bug.", - PreRunE: loadRepoEnsureUser, - RunE: runCommentAdd, -} - -func init() { - commentCmd.AddCommand(commentAddCmd) - - commentAddCmd.Flags().SortFlags = false - - commentAddCmd.Flags().StringVarP(&commentAddMessageFile, "file", "F", "", - "Take the message from the given file. Use - to read the message from the standard input", - ) - - commentAddCmd.Flags().StringVarP(&commentAddMessage, "message", "m", "", - "Provide the new message from the command line", - ) -} diff --git a/commands/deselect.go b/commands/deselect.go index f92c81fd..22a5f55d 100644 --- a/commands/deselect.go +++ b/commands/deselect.go @@ -1,14 +1,35 @@ package commands import ( + "github.com/spf13/cobra" + "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/commands/select" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -func runDeselect(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newDeselectCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "deselect", + Short: "Clear the implicitly selected bug.", + Example: `git bug select 2f15 +git bug comment +git bug status +git bug deselect +`, + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runDeselect(env) + }, + } + + return cmd +} + +func runDeselect(env *Env) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -22,20 +43,3 @@ func runDeselect(cmd *cobra.Command, args []string) error { return nil } - -var deselectCmd = &cobra.Command{ - Use: "deselect", - Short: "Clear the implicitly selected bug.", - Example: `git bug select 2f15 -git bug comment -git bug status -git bug deselect -`, - PreRunE: loadRepo, - RunE: runDeselect, -} - -func init() { - RootCmd.AddCommand(deselectCmd) - deselectCmd.Flags().SortFlags = false -} diff --git a/commands/env.go b/commands/env.go new file mode 100644 index 00000000..daba8420 --- /dev/null +++ b/commands/env.go @@ -0,0 +1,40 @@ +package commands + +import ( + "fmt" + "io" + "os" + + "github.com/MichaelMure/git-bug/repository" +) + +// Env is the environment of a command +type Env struct { + repo repository.ClockedRepo + out out + err out +} + +func newEnv() *Env { + return &Env{ + repo: nil, + out: out{Writer: os.Stdout}, + err: out{Writer: os.Stderr}, + } +} + +type out struct { + io.Writer +} + +func (o out) Printf(format string, a ...interface{}) { + _, _ = fmt.Fprintf(o, format, a...) +} + +func (o out) Print(a ...interface{}) { + _, _ = fmt.Fprint(o, a...) +} + +func (o out) Println(a ...interface{}) { + _, _ = fmt.Fprintln(o, a...) +} diff --git a/commands/label.go b/commands/label.go index a07e9efc..e48be18e 100644 --- a/commands/label.go +++ b/commands/label.go @@ -1,16 +1,33 @@ package commands import ( - "fmt" + "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/commands/select" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -func runLabel(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newLabelCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "label [<id>]", + Short: "Display, add or remove labels to/from a bug.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runLabel(env, args) + }, + } + + cmd.AddCommand(newLabelAddCommand()) + cmd.AddCommand(newLabelRmCommand()) + + return cmd +} + +func runLabel(env *Env, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -25,21 +42,8 @@ func runLabel(cmd *cobra.Command, args []string) error { snap := b.Snapshot() for _, l := range snap.Labels { - fmt.Println(l) + env.out.Println(l) } return nil } - -var labelCmd = &cobra.Command{ - Use: "label [<id>]", - Short: "Display, add or remove labels to/from a bug.", - PreRunE: loadRepo, - RunE: runLabel, -} - -func init() { - RootCmd.AddCommand(labelCmd) - - labelCmd.Flags().SortFlags = false -} diff --git a/commands/label_add.go b/commands/label_add.go index 39dfb085..83a9f064 100644 --- a/commands/label_add.go +++ b/commands/label_add.go @@ -1,16 +1,30 @@ package commands import ( - "fmt" + "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/commands/select" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -func runLabelAdd(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newLabelAddCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "add [<id>] <label>[...]", + Short: "Add a label to a bug.", + PreRunE: loadRepoEnsureUser(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runLabelAdd(env, args) + }, + } + + return cmd +} + +func runLabelAdd(env *Env, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -25,7 +39,7 @@ func runLabelAdd(cmd *cobra.Command, args []string) error { changes, _, err := b.ChangeLabels(args, nil) for _, change := range changes { - fmt.Println(change) + env.out.Println(change) } if err != nil { @@ -34,14 +48,3 @@ func runLabelAdd(cmd *cobra.Command, args []string) error { return b.Commit() } - -var labelAddCmd = &cobra.Command{ - Use: "add [<id>] <label>[...]", - Short: "Add a label to a bug.", - PreRunE: loadRepoEnsureUser, - RunE: runLabelAdd, -} - -func init() { - labelCmd.AddCommand(labelAddCmd) -} diff --git a/commands/label_rm.go b/commands/label_rm.go index 11300c78..5fba4150 100644 --- a/commands/label_rm.go +++ b/commands/label_rm.go @@ -1,8 +1,6 @@ package commands import ( - "fmt" - "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" @@ -10,8 +8,23 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -func runLabelRm(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newLabelRmCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "rm [<id>] <label>[...]", + Short: "Remove a label from a bug.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runLabelRm(env, args) + }, + } + + return cmd +} + +func runLabelRm(env *Env, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -26,7 +39,7 @@ func runLabelRm(cmd *cobra.Command, args []string) error { changes, _, err := b.ChangeLabels(nil, args) for _, change := range changes { - fmt.Println(change) + env.out.Println(change) } if err != nil { @@ -35,14 +48,3 @@ func runLabelRm(cmd *cobra.Command, args []string) error { return b.Commit() } - -var labelRmCmd = &cobra.Command{ - Use: "rm [<id>] <label>[...]", - Short: "Remove a label from a bug.", - PreRunE: loadRepo, - RunE: runLabelRm, -} - -func init() { - labelCmd.AddCommand(labelRmCmd) -} diff --git a/commands/ls-id.go b/commands/ls-id.go index 22357eb4..6624793d 100644 --- a/commands/ls-id.go +++ b/commands/ls-id.go @@ -1,17 +1,29 @@ package commands import ( - "fmt" - "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/util/interrupt" ) -func runLsID(cmd *cobra.Command, args []string) error { +func newLsIdCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "ls-id [<prefix>]", + Short: "List bug identifiers.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runLsId(env, args) + }, + } + + return cmd +} - backend, err := cache.NewRepoCache(repo) +func runLsId(env *Env, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -25,20 +37,9 @@ func runLsID(cmd *cobra.Command, args []string) error { for _, id := range backend.AllBugsIds() { if prefix == "" || id.HasPrefix(prefix) { - fmt.Println(id) + env.out.Println(id) } } return nil } - -var listBugIDCmd = &cobra.Command{ - Use: "ls-id [<prefix>]", - Short: "List bug identifiers.", - PreRunE: loadRepo, - RunE: runLsID, -} - -func init() { - RootCmd.AddCommand(listBugIDCmd) -} diff --git a/commands/ls-labels.go b/commands/ls-labels.go index 5610fb56..3473aadd 100644 --- a/commands/ls-labels.go +++ b/commands/ls-labels.go @@ -1,15 +1,32 @@ package commands import ( - "fmt" + "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -func runLsLabel(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newLsLabelCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "ls-label", + Short: "List valid labels.", + Long: `List valid labels. + +Note: in the future, a proper label policy could be implemented where valid labels are defined in a configuration file. Until that, the default behavior is to return the list of labels already used.`, + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runLsLabel(env) + }, + } + + return cmd +} + +func runLsLabel(env *Env) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -19,22 +36,8 @@ func runLsLabel(cmd *cobra.Command, args []string) error { labels := backend.ValidLabels() for _, l := range labels { - fmt.Println(l) + env.out.Println(l) } return nil } - -var lsLabelCmd = &cobra.Command{ - Use: "ls-label", - Short: "List valid labels.", - Long: `List valid labels. - -Note: in the future, a proper label policy could be implemented where valid labels are defined in a configuration file. Until that, the default behavior is to return the list of labels already used.`, - PreRunE: loadRepo, - RunE: runLsLabel, -} - -func init() { - RootCmd.AddCommand(lsLabelCmd) -} diff --git a/commands/ls.go b/commands/ls.go index 34f1f982..f48a5796 100644 --- a/commands/ls.go +++ b/commands/ls.go @@ -15,18 +15,67 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -var ( - lsQuery query.Query - - lsStatusQuery []string - lsNoQuery []string - lsSortBy string - lsSortDirection string - lsOutputFormat string -) +type lsOptions struct { + query query.Query + + statusQuery []string + noQuery []string + sortBy string + sortDirection string + outputFormat string +} + +func newLsCommand() *cobra.Command { + env := newEnv() + options := lsOptions{} + + cmd := &cobra.Command{ + Use: "ls [<query>]", + Short: "List bugs.", + Long: `Display a summary of each bugs. + +You can pass an additional query to filter and order the list. This query can be expressed either with a simple query language or with flags.`, + Example: `List open bugs sorted by last edition with a query: +git bug ls status:open sort:edit-desc + +List closed bugs sorted by creation with flags: +git bug ls --status closed --by creation +`, + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runLs(env, options, args) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false -func runLsBug(_ *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) + flags.StringSliceVarP(&options.statusQuery, "status", "s", nil, + "Filter by status. Valid values are [open,closed]") + flags.StringSliceVarP(&options.query.Author, "author", "a", nil, + "Filter by author") + flags.StringSliceVarP(&options.query.Participant, "participant", "p", nil, + "Filter by participant") + flags.StringSliceVarP(&options.query.Actor, "actor", "A", nil, + "Filter by actor") + flags.StringSliceVarP(&options.query.Label, "label", "l", nil, + "Filter by label") + flags.StringSliceVarP(&options.query.Title, "title", "t", nil, + "Filter by title") + flags.StringSliceVarP(&options.noQuery, "no", "n", nil, + "Filter by absence of something. Valid values are [label]") + flags.StringVarP(&options.sortBy, "by", "b", "creation", + "Sort the results by a characteristic. Valid values are [id,creation,edit]") + flags.StringVarP(&options.sortDirection, "direction", "d", "asc", + "Select the sorting direction. Valid values are [asc,desc]") + flags.StringVarP(&options.outputFormat, "format", "f", "default", + "Select the output formatting style. Valid values are [default,plain,json,org-mode]") + + return cmd +} + +func runLs(env *Env, opts lsOptions, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -41,11 +90,11 @@ func runLsBug(_ *cobra.Command, args []string) error { return err } } else { - err = completeQuery() + err = completeQuery(&opts) if err != nil { return err } - q = &lsQuery + q = &opts.query } allIds := backend.QueryBugs(q) @@ -59,17 +108,17 @@ func runLsBug(_ *cobra.Command, args []string) error { bugExcerpt[i] = b } - switch lsOutputFormat { + switch opts.outputFormat { case "org-mode": - return lsOrgmodeFormatter(backend, bugExcerpt) + return lsOrgmodeFormatter(env, backend, bugExcerpt) case "plain": - return lsPlainFormatter(backend, bugExcerpt) + return lsPlainFormatter(env, backend, bugExcerpt) case "json": - return lsJsonFormatter(backend, bugExcerpt) + return lsJsonFormatter(env, backend, bugExcerpt) case "default": - return lsDefaultFormatter(backend, bugExcerpt) + return lsDefaultFormatter(env, backend, bugExcerpt) default: - return fmt.Errorf("unknown format %s", lsOutputFormat) + return fmt.Errorf("unknown format %s", opts.outputFormat) } } @@ -90,7 +139,7 @@ type JSONBugExcerpt struct { Metadata map[string]string `json:"metadata"` } -func lsJsonFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error { +func lsJsonFormatter(env *Env, backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error { jsonBugs := make([]JSONBugExcerpt, len(bugExcerpts)) for i, b := range bugExcerpts { jsonBug := JSONBugExcerpt{ @@ -136,11 +185,11 @@ func lsJsonFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) jsonBugs[i] = jsonBug } jsonObject, _ := json.MarshalIndent(jsonBugs, "", " ") - fmt.Printf("%s\n", jsonObject) + env.out.Printf("%s\n", jsonObject) return nil } -func lsDefaultFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error { +func lsDefaultFormatter(env *Env, backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error { for _, b := range bugExcerpts { var name string if b.AuthorId != "" { @@ -171,7 +220,7 @@ func lsDefaultFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerp comments = " ∞ 💬" } - fmt.Printf("%s %s\t%s\t%s\t%s\n", + env.out.Printf("%s %s\t%s\t%s\t%s\n", colors.Cyan(b.Id.Human()), colors.Yellow(b.Status), titleFmt+labelsFmt, @@ -182,15 +231,15 @@ func lsDefaultFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerp return nil } -func lsPlainFormatter(_ *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error { +func lsPlainFormatter(env *Env, _ *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error { for _, b := range bugExcerpts { - fmt.Printf("%s [%s] %s\n", b.Id.Human(), b.Status, b.Title) + env.out.Printf("%s [%s] %s\n", b.Id.Human(), b.Status, b.Title) } return nil } -func lsOrgmodeFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error { - fmt.Println("+TODO: OPEN | CLOSED") +func lsOrgmodeFormatter(env *Env, backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error { + env.out.Println("+TODO: OPEN | CLOSED") for _, b := range bugExcerpts { status := strings.Title(b.Status.String()) @@ -221,7 +270,7 @@ func lsOrgmodeFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerp labelsString = "" } - fmt.Printf("* %s %s [%s] %s: %s %s\n", + env.out.Printf("* %s %s [%s] %s: %s %s\n", b.Id.Human(), status, b.CreateTime(), @@ -230,29 +279,29 @@ func lsOrgmodeFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerp labelsString, ) - fmt.Printf("** Last Edited: %s\n", b.EditTime().String()) + env.out.Printf("** Last Edited: %s\n", b.EditTime().String()) - fmt.Printf("** Actors:\n") + env.out.Printf("** Actors:\n") for _, element := range b.Actors { actor, err := backend.ResolveIdentityExcerpt(element) if err != nil { return err } - fmt.Printf(": %s %s\n", + env.out.Printf(": %s %s\n", actor.Id.Human(), actor.DisplayName(), ) } - fmt.Printf("** Participants:\n") + env.out.Printf("** Participants:\n") for _, element := range b.Participants { participant, err := backend.ResolveIdentityExcerpt(element) if err != nil { return err } - fmt.Printf(": %s %s\n", + env.out.Printf(": %s %s\n", participant.Id.Human(), participant.DisplayName(), ) @@ -263,86 +312,43 @@ func lsOrgmodeFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerp } // Finish the command flags transformation into the query.Query -func completeQuery() error { - for _, str := range lsStatusQuery { +func completeQuery(opts *lsOptions) error { + for _, str := range opts.statusQuery { status, err := bug.StatusFromString(str) if err != nil { return err } - lsQuery.Status = append(lsQuery.Status, status) + opts.query.Status = append(opts.query.Status, status) } - for _, no := range lsNoQuery { + for _, no := range opts.noQuery { switch no { case "label": - lsQuery.NoLabel = true + opts.query.NoLabel = true default: return fmt.Errorf("unknown \"no\" filter %s", no) } } - switch lsSortBy { + switch opts.sortBy { case "id": - lsQuery.OrderBy = query.OrderById + opts.query.OrderBy = query.OrderById case "creation": - lsQuery.OrderBy = query.OrderByCreation + opts.query.OrderBy = query.OrderByCreation case "edit": - lsQuery.OrderBy = query.OrderByEdit + opts.query.OrderBy = query.OrderByEdit default: - return fmt.Errorf("unknown sort flag %s", lsSortBy) + return fmt.Errorf("unknown sort flag %s", opts.sortBy) } - switch lsSortDirection { + switch opts.sortDirection { case "asc": - lsQuery.OrderDirection = query.OrderAscending + opts.query.OrderDirection = query.OrderAscending case "desc": - lsQuery.OrderDirection = query.OrderDescending + opts.query.OrderDirection = query.OrderDescending default: - return fmt.Errorf("unknown sort direction %s", lsSortDirection) + return fmt.Errorf("unknown sort direction %s", opts.sortDirection) } return nil } - -var lsCmd = &cobra.Command{ - Use: "ls [<query>]", - Short: "List bugs.", - Long: `Display a summary of each bugs. - -You can pass an additional query to filter and order the list. This query can be expressed either with a simple query language or with flags.`, - Example: `List open bugs sorted by last edition with a query: -git bug ls status:open sort:edit-desc - -List closed bugs sorted by creation with flags: -git bug ls --status closed --by creation -`, - PreRunE: loadRepo, - RunE: runLsBug, -} - -func init() { - RootCmd.AddCommand(lsCmd) - - lsCmd.Flags().SortFlags = false - - lsCmd.Flags().StringSliceVarP(&lsStatusQuery, "status", "s", nil, - "Filter by status. Valid values are [open,closed]") - lsCmd.Flags().StringSliceVarP(&lsQuery.Author, "author", "a", nil, - "Filter by author") - lsCmd.Flags().StringSliceVarP(&lsQuery.Participant, "participant", "p", nil, - "Filter by participant") - lsCmd.Flags().StringSliceVarP(&lsQuery.Actor, "actor", "A", nil, - "Filter by actor") - lsCmd.Flags().StringSliceVarP(&lsQuery.Label, "label", "l", nil, - "Filter by label") - lsCmd.Flags().StringSliceVarP(&lsQuery.Title, "title", "t", nil, - "Filter by title") - lsCmd.Flags().StringSliceVarP(&lsNoQuery, "no", "n", nil, - "Filter by absence of something. Valid values are [label]") - lsCmd.Flags().StringVarP(&lsSortBy, "by", "b", "creation", - "Sort the results by a characteristic. Valid values are [id,creation,edit]") - lsCmd.Flags().StringVarP(&lsSortDirection, "direction", "d", "asc", - "Select the sorting direction. Valid values are [asc,desc]") - lsCmd.Flags().StringVarP(&lsOutputFormat, "format", "f", "default", - "Select the output formatting style. Valid values are [default,plain,json,org-mode]") -} diff --git a/commands/pull.go b/commands/pull.go index 0439ab41..fb50a03b 100644 --- a/commands/pull.go +++ b/commands/pull.go @@ -2,7 +2,6 @@ package commands import ( "errors" - "fmt" "github.com/spf13/cobra" @@ -11,7 +10,22 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -func runPull(cmd *cobra.Command, args []string) error { +func newPullCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "pull [<remote>]", + Short: "Pull bugs update from a git remote.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runPull(env, args) + }, + } + + return cmd +} + +func runPull(env *Env, args []string) error { if len(args) > 1 { return errors.New("Only pulling from one remote at a time is supported") } @@ -21,45 +35,33 @@ func runPull(cmd *cobra.Command, args []string) error { remote = args[0] } - backend, err := cache.NewRepoCache(repo) + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } defer backend.Close() interrupt.RegisterCleaner(backend.Close) - fmt.Println("Fetching remote ...") + env.out.Println("Fetching remote ...") stdout, err := backend.Fetch(remote) if err != nil { return err } - fmt.Println(stdout) + env.out.Println(stdout) - fmt.Println("Merging data ...") + env.out.Println("Merging data ...") for result := range backend.MergeAll(remote) { if result.Err != nil { - fmt.Println(result.Err) + env.err.Println(result.Err) } if result.Status != entity.MergeStatusNothing { - fmt.Printf("%s: %s\n", result.Id.Human(), result) + env.out.Printf("%s: %s\n", result.Id.Human(), result) } } return nil } - -// showCmd defines the "push" subcommand. -var pullCmd = &cobra.Command{ - Use: "pull [<remote>]", - Short: "Pull bugs update from a git remote.", - PreRunE: loadRepo, - RunE: runPull, -} - -func init() { - RootCmd.AddCommand(pullCmd) -} diff --git a/commands/push.go b/commands/push.go index 8f67d3c0..fee8a50d 100644 --- a/commands/push.go +++ b/commands/push.go @@ -2,14 +2,29 @@ package commands import ( "errors" - "fmt" + + "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -func runPush(cmd *cobra.Command, args []string) error { +func newPushCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "push [<remote>]", + Short: "Push bugs update to a git remote.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runPush(env, args) + }, + } + + return cmd +} + +func runPush(env *Env, args []string) error { if len(args) > 1 { return errors.New("Only pushing to one remote at a time is supported") } @@ -19,7 +34,7 @@ func runPush(cmd *cobra.Command, args []string) error { remote = args[0] } - backend, err := cache.NewRepoCache(repo) + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -31,19 +46,7 @@ func runPush(cmd *cobra.Command, args []string) error { return err } - fmt.Println(stdout) + env.out.Println(stdout) return nil } - -// showCmd defines the "push" subcommand. -var pushCmd = &cobra.Command{ - Use: "push [<remote>]", - Short: "Push bugs update to a git remote.", - PreRunE: loadRepo, - RunE: runPush, -} - -func init() { - RootCmd.AddCommand(pushCmd) -} diff --git a/commands/root.go b/commands/root.go index 2ea95d4b..42859d19 100644 --- a/commands/root.go +++ b/commands/root.go @@ -14,14 +14,16 @@ import ( const rootCommandName = "git-bug" -// package scoped var to hold the repo after the PreRun execution -var repo repository.ClockedRepo +// These variables are initialized externally during the build. See the Makefile. +var GitCommit string +var GitLastTag string +var GitExactTag string -// RootCmd represents the base command when called without any subcommands -var RootCmd = &cobra.Command{ - Use: rootCommandName, - Short: "A bug tracker embedded in Git.", - Long: `git-bug is a bug tracker embedded in git. +func NewRootCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: rootCommandName, + Short: "A bug tracker embedded in Git.", + Long: `git-bug is a bug tracker embedded in git. git-bug use git objects to store the bug tracking separated from the files history. As bugs are regular git objects, they can be pushed and pulled from/to @@ -29,65 +31,104 @@ the same git remote you are already using to collaborate with other people. `, - // For the root command, force the execution of the PreRun - // even if we just display the help. This is to make sure that we check - // the repository and give the user early feedback. - Run: func(cmd *cobra.Command, args []string) { - if err := cmd.Help(); err != nil { - os.Exit(1) - } - }, - - SilenceUsage: true, - DisableAutoGenTag: true, - - // Custom bash code to connect the git completion for "git bug" to the - // git-bug completion for "git-bug" - BashCompletionFunction: ` + PersistentPreRun: func(cmd *cobra.Command, args []string) { + root := cmd.Root() + + if GitExactTag == "undefined" { + GitExactTag = "" + } + root.Version = GitLastTag + if GitExactTag == "" { + root.Version = fmt.Sprintf("%s-dev-%.10s", root.Version, GitCommit) + } + }, + + // For the root command, force the execution of the PreRun + // even if we just display the help. This is to make sure that we check + // the repository and give the user early feedback. + Run: func(cmd *cobra.Command, args []string) { + if err := cmd.Help(); err != nil { + os.Exit(1) + } + }, + + SilenceUsage: true, + DisableAutoGenTag: true, + + // Custom bash code to connect the git completion for "git bug" to the + // git-bug completion for "git-bug" + BashCompletionFunction: ` _git_bug() { __start_git-bug "$@" } `, + } + + cmd.AddCommand(newAddCommand()) + cmd.AddCommand(newBridgeCommand()) + cmd.AddCommand(newCommandsCommand()) + cmd.AddCommand(newCommentCommand()) + cmd.AddCommand(newDeselectCommand()) + cmd.AddCommand(newLabelCommand()) + cmd.AddCommand(newLsCommand()) + cmd.AddCommand(newLsIdCommand()) + cmd.AddCommand(newLsLabelCommand()) + cmd.AddCommand(newPullCommand()) + cmd.AddCommand(newPushCommand()) + cmd.AddCommand(newSelectCommand()) + cmd.AddCommand(newShowCommand()) + cmd.AddCommand(newStatusCommand()) + cmd.AddCommand(newTermUICommand()) + cmd.AddCommand(newTitleCommand()) + cmd.AddCommand(newUserCommand()) + cmd.AddCommand(newVersionCommand()) + cmd.AddCommand(newWebUICommand()) + + return cmd } func Execute() { - if err := RootCmd.Execute(); err != nil { + if err := NewRootCommand().Execute(); err != nil { os.Exit(1) } } // loadRepo is a pre-run function that load the repository for use in a command -func loadRepo(cmd *cobra.Command, args []string) error { - cwd, err := os.Getwd() - if err != nil { - return fmt.Errorf("unable to get the current working directory: %q", err) - } +func loadRepo(env *Env) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + cwd, err := os.Getwd() + if err != nil { + return fmt.Errorf("unable to get the current working directory: %q", err) + } - repo, err = repository.NewGitRepo(cwd, []repository.ClockLoader{bug.ClockLoader}) - if err == repository.ErrNotARepo { - return fmt.Errorf("%s must be run from within a git repo", rootCommandName) - } + env.repo, err = repository.NewGitRepo(cwd, []repository.ClockLoader{bug.ClockLoader}) + if err == repository.ErrNotARepo { + return fmt.Errorf("%s must be run from within a git repo", rootCommandName) + } - if err != nil { - return err - } + if err != nil { + return err + } - return nil + return nil + } } // loadRepoEnsureUser is the same as loadRepo, but also ensure that the user has configured // an identity. Use this pre-run function when an error after using the configured user won't // do. -func loadRepoEnsureUser(cmd *cobra.Command, args []string) error { - err := loadRepo(cmd, args) - if err != nil { - return err - } +func loadRepoEnsureUser(env *Env) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + err := loadRepo(env)(cmd, args) + if err != nil { + return err + } - _, err = identity.GetUserIdentity(repo) - if err != nil { - return err - } + _, err = identity.GetUserIdentity(env.repo) + if err != nil { + return err + } - return nil + return nil + } } diff --git a/commands/select.go b/commands/select.go index f2ae33ca..e4916650 100644 --- a/commands/select.go +++ b/commands/select.go @@ -2,7 +2,6 @@ package commands import ( "errors" - "fmt" "github.com/spf13/cobra" @@ -11,12 +10,40 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -func runSelect(cmd *cobra.Command, args []string) error { +func newSelectCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "select <id>", + Short: "Select a bug for implicit use in future commands.", + Example: `git bug select 2f15 +git bug comment +git bug status +`, + Long: `Select a bug for implicit use in future commands. + +This command allows you to omit any bug <id> argument, for example: + git bug show +instead of + git bug show 2f153ca + +The complementary command is "git bug deselect" performing the opposite operation. +`, + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runSelect(env, args) + }, + } + + return cmd +} + +func runSelect(env *Env, args []string) error { if len(args) == 0 { return errors.New("You must provide a bug id") } - backend, err := cache.NewRepoCache(repo) + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -35,32 +62,7 @@ func runSelect(cmd *cobra.Command, args []string) error { return err } - fmt.Printf("selected bug %s: %s\n", b.Id().Human(), b.Snapshot().Title) + env.out.Printf("selected bug %s: %s\n", b.Id().Human(), b.Snapshot().Title) return nil } - -var selectCmd = &cobra.Command{ - Use: "select <id>", - Short: "Select a bug for implicit use in future commands.", - Example: `git bug select 2f15 -git bug comment -git bug status -`, - Long: `Select a bug for implicit use in future commands. - -This command allows you to omit any bug <id> argument, for example: - git bug show -instead of - git bug show 2f153ca - -The complementary command is "git bug deselect" performing the opposite operation. -`, - PreRunE: loadRepo, - RunE: runSelect, -} - -func init() { - RootCmd.AddCommand(selectCmd) - selectCmd.Flags().SortFlags = false -} diff --git a/commands/show.go b/commands/show.go index 2f4e46ed..9e6a8e8c 100644 --- a/commands/show.go +++ b/commands/show.go @@ -15,13 +15,37 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -var ( - showFieldsQuery string - showOutputFormat string -) +type showOptions struct { + query string + format string +} + +func newShowCommand() *cobra.Command { + env := newEnv() + options := showOptions{} + + cmd := &cobra.Command{ + Use: "show [<id>]", + Short: "Display the details of a bug.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runShow(env, options, args) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false -func runShowBug(_ *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) + flags.StringVarP(&options.query, "field", "", "", + "Select field to display. Valid values are [author,authorEmail,createTime,lastEdit,humanId,id,labels,shortId,status,title,actors,participants]") + flags.StringVarP(&options.format, "format", "f", "default", + "Select the output formatting style. Valid values are [default,json,org-mode]") + + return cmd +} + +func runShow(env *Env, opts showOptions, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -33,77 +57,77 @@ func runShowBug(_ *cobra.Command, args []string) error { return err } - snapshot := b.Snapshot() + snap := b.Snapshot() - if len(snapshot.Comments) == 0 { + if len(snap.Comments) == 0 { return errors.New("invalid bug: no comment") } - if showFieldsQuery != "" { - switch showFieldsQuery { + if opts.query != "" { + switch opts.query { case "author": - fmt.Printf("%s\n", snapshot.Author.DisplayName()) + env.out.Printf("%s\n", snap.Author.DisplayName()) case "authorEmail": - fmt.Printf("%s\n", snapshot.Author.Email()) + env.out.Printf("%s\n", snap.Author.Email()) case "createTime": - fmt.Printf("%s\n", snapshot.CreateTime.String()) + env.out.Printf("%s\n", snap.CreateTime.String()) case "lastEdit": - fmt.Printf("%s\n", snapshot.EditTime().String()) + env.out.Printf("%s\n", snap.EditTime().String()) case "humanId": - fmt.Printf("%s\n", snapshot.Id().Human()) + env.out.Printf("%s\n", snap.Id().Human()) case "id": - fmt.Printf("%s\n", snapshot.Id()) + env.out.Printf("%s\n", snap.Id()) case "labels": - for _, l := range snapshot.Labels { - fmt.Printf("%s\n", l.String()) + for _, l := range snap.Labels { + env.out.Printf("%s\n", l.String()) } case "actors": - for _, a := range snapshot.Actors { - fmt.Printf("%s\n", a.DisplayName()) + for _, a := range snap.Actors { + env.out.Printf("%s\n", a.DisplayName()) } case "participants": - for _, p := range snapshot.Participants { - fmt.Printf("%s\n", p.DisplayName()) + for _, p := range snap.Participants { + env.out.Printf("%s\n", p.DisplayName()) } case "shortId": - fmt.Printf("%s\n", snapshot.Id().Human()) + env.out.Printf("%s\n", snap.Id().Human()) case "status": - fmt.Printf("%s\n", snapshot.Status) + env.out.Printf("%s\n", snap.Status) case "title": - fmt.Printf("%s\n", snapshot.Title) + env.out.Printf("%s\n", snap.Title) default: - return fmt.Errorf("\nUnsupported field: %s\n", showFieldsQuery) + return fmt.Errorf("\nUnsupported field: %s\n", opts.query) } return nil } - switch showOutputFormat { + switch opts.format { case "org-mode": - return showOrgmodeFormatter(snapshot) + return showOrgModeFormatter(env, snap) case "json": - return showJsonFormatter(snapshot) + return showJsonFormatter(env, snap) case "default": - return showDefaultFormatter(snapshot) + return showDefaultFormatter(env, snap) default: - return fmt.Errorf("unknown format %s", showOutputFormat) + return fmt.Errorf("unknown format %s", opts.format) } } -func showDefaultFormatter(snapshot *bug.Snapshot) error { +func showDefaultFormatter(env *Env, snapshot *bug.Snapshot) error { // Header - fmt.Printf("%s [%s] %s\n\n", + env.out.Printf("%s [%s] %s\n\n", colors.Cyan(snapshot.Id().Human()), colors.Yellow(snapshot.Status), snapshot.Title, ) - fmt.Printf("%s opened this issue %s\n", + env.out.Printf("%s opened this issue %s\n", colors.Magenta(snapshot.Author.DisplayName()), snapshot.CreateTime.String(), ) - fmt.Printf("This was last edited at %s\n\n", + env.out.Printf("This was last edited at %s\n\n", snapshot.EditTime().String(), ) @@ -113,7 +137,7 @@ func showDefaultFormatter(snapshot *bug.Snapshot) error { labels[i] = string(snapshot.Labels[i]) } - fmt.Printf("labels: %s\n", + env.out.Printf("labels: %s\n", strings.Join(labels, ", "), ) @@ -123,7 +147,7 @@ func showDefaultFormatter(snapshot *bug.Snapshot) error { actors[i] = snapshot.Actors[i].DisplayName() } - fmt.Printf("actors: %s\n", + env.out.Printf("actors: %s\n", strings.Join(actors, ", "), ) @@ -133,7 +157,7 @@ func showDefaultFormatter(snapshot *bug.Snapshot) error { participants[i] = snapshot.Participants[i].DisplayName() } - fmt.Printf("participants: %s\n\n", + env.out.Printf("participants: %s\n\n", strings.Join(participants, ", "), ) @@ -142,7 +166,7 @@ func showDefaultFormatter(snapshot *bug.Snapshot) error { for i, comment := range snapshot.Comments { var message string - fmt.Printf("%s#%d %s <%s>\n\n", + env.out.Printf("%s#%d %s <%s>\n\n", indent, i, comment.Author.DisplayName(), @@ -155,7 +179,7 @@ func showDefaultFormatter(snapshot *bug.Snapshot) error { message = comment.Message } - fmt.Printf("%s%s\n\n\n", + env.out.Printf("%s%s\n\n\n", indent, message, ) @@ -194,7 +218,7 @@ func NewJSONComment(comment bug.Comment) JSONComment { } } -func showJsonFormatter(snapshot *bug.Snapshot) error { +func showJsonFormatter(env *Env, snapshot *bug.Snapshot) error { jsonBug := JSONBugSnapshot{ Id: snapshot.Id().String(), HumanId: snapshot.Id().Human(), @@ -222,28 +246,28 @@ func showJsonFormatter(snapshot *bug.Snapshot) error { } jsonObject, _ := json.MarshalIndent(jsonBug, "", " ") - fmt.Printf("%s\n", jsonObject) + env.out.Printf("%s\n", jsonObject) return nil } -func showOrgmodeFormatter(snapshot *bug.Snapshot) error { +func showOrgModeFormatter(env *Env, snapshot *bug.Snapshot) error { // Header - fmt.Printf("%s [%s] %s\n", + env.out.Printf("%s [%s] %s\n", snapshot.Id().Human(), snapshot.Status, snapshot.Title, ) - fmt.Printf("* Author: %s\n", + env.out.Printf("* Author: %s\n", snapshot.Author.DisplayName(), ) - fmt.Printf("* Creation Time: %s\n", + env.out.Printf("* Creation Time: %s\n", snapshot.CreateTime.String(), ) - fmt.Printf("* Last Edit: %s\n", + env.out.Printf("* Last Edit: %s\n", snapshot.EditTime().String(), ) @@ -253,9 +277,9 @@ func showOrgmodeFormatter(snapshot *bug.Snapshot) error { labels[i] = string(label) } - fmt.Printf("* Labels:\n") + env.out.Printf("* Labels:\n") if len(labels) > 0 { - fmt.Printf("** %s\n", + env.out.Printf("** %s\n", strings.Join(labels, "\n** "), ) } @@ -269,7 +293,7 @@ func showOrgmodeFormatter(snapshot *bug.Snapshot) error { ) } - fmt.Printf("* Actors:\n** %s\n", + env.out.Printf("* Actors:\n** %s\n", strings.Join(actors, "\n** "), ) @@ -282,18 +306,16 @@ func showOrgmodeFormatter(snapshot *bug.Snapshot) error { ) } - fmt.Printf("* Participants:\n** %s\n", + env.out.Printf("* Participants:\n** %s\n", strings.Join(participants, "\n** "), ) - fmt.Printf("* Comments:\n") + env.out.Printf("* Comments:\n") for i, comment := range snapshot.Comments { var message string - fmt.Printf("** #%d %s\n", - i, - comment.Author.DisplayName(), - ) + env.out.Printf("** #%d %s\n", + i, comment.Author.DisplayName()) if comment.Message == "" { message = "No description provided." @@ -301,25 +323,8 @@ func showOrgmodeFormatter(snapshot *bug.Snapshot) error { message = strings.ReplaceAll(comment.Message, "\n", "\n: ") } - fmt.Printf(": %s\n", - message, - ) + env.out.Printf(": %s\n", message) } return nil } - -var showCmd = &cobra.Command{ - Use: "show [<id>]", - Short: "Display the details of a bug.", - PreRunE: loadRepo, - RunE: runShowBug, -} - -func init() { - RootCmd.AddCommand(showCmd) - showCmd.Flags().StringVarP(&showFieldsQuery, "field", "", "", - "Select field to display. Valid values are [author,authorEmail,createTime,lastEdit,humanId,id,labels,shortId,status,title,actors,participants]") - showCmd.Flags().StringVarP(&showOutputFormat, "format", "f", "default", - "Select the output formatting style. Valid values are [default,json,org-mode]") -} diff --git a/commands/status.go b/commands/status.go index 4675195d..aaf9e636 100644 --- a/commands/status.go +++ b/commands/status.go @@ -1,16 +1,33 @@ package commands import ( - "fmt" + "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/commands/select" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -func runStatus(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newStatusCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "status [<id>]", + Short: "Display or change a bug status.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runStatus(env, args) + }, + } + + cmd.AddCommand(newStatusCloseCommand()) + cmd.AddCommand(newStatusOpenCommand()) + + return cmd +} + +func runStatus(env *Env, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -24,18 +41,7 @@ func runStatus(cmd *cobra.Command, args []string) error { snap := b.Snapshot() - fmt.Println(snap.Status) + env.out.Println(snap.Status) return nil } - -var statusCmd = &cobra.Command{ - Use: "status [<id>]", - Short: "Display or change a bug status.", - PreRunE: loadRepo, - RunE: runStatus, -} - -func init() { - RootCmd.AddCommand(statusCmd) -} diff --git a/commands/status_close.go b/commands/status_close.go index 08c67e87..2c7079e6 100644 --- a/commands/status_close.go +++ b/commands/status_close.go @@ -1,14 +1,30 @@ package commands import ( + "github.com/spf13/cobra" + "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/commands/select" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -func runStatusClose(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newStatusCloseCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "close [<id>]", + Short: "Mark a bug as closed.", + PreRunE: loadRepoEnsureUser(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runStatusClose(env, args) + }, + } + + return cmd +} + +func runStatusClose(env *Env, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -27,14 +43,3 @@ func runStatusClose(cmd *cobra.Command, args []string) error { return b.Commit() } - -var closeCmd = &cobra.Command{ - Use: "close [<id>]", - Short: "Mark a bug as closed.", - PreRunE: loadRepoEnsureUser, - RunE: runStatusClose, -} - -func init() { - statusCmd.AddCommand(closeCmd) -} diff --git a/commands/status_open.go b/commands/status_open.go index 1b1c426e..ff98fa33 100644 --- a/commands/status_open.go +++ b/commands/status_open.go @@ -1,14 +1,30 @@ package commands import ( + "github.com/spf13/cobra" + "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/commands/select" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -func runStatusOpen(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newStatusOpenCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "open [<id>]", + Short: "Mark a bug as open.", + PreRunE: loadRepoEnsureUser(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runStatusOpen(env, args) + }, + } + + return cmd +} + +func runStatusOpen(env *Env, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -27,14 +43,3 @@ func runStatusOpen(cmd *cobra.Command, args []string) error { return b.Commit() } - -var openCmd = &cobra.Command{ - Use: "open [<id>]", - Short: "Mark a bug as open.", - PreRunE: loadRepoEnsureUser, - RunE: runStatusOpen, -} - -func init() { - statusCmd.AddCommand(openCmd) -} diff --git a/commands/termui.go b/commands/termui.go index 2cdc3507..174d60ec 100644 --- a/commands/termui.go +++ b/commands/termui.go @@ -1,14 +1,31 @@ package commands import ( + "github.com/spf13/cobra" + "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/termui" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -func runTermUI(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newTermUICommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "termui", + Aliases: []string{"tui"}, + Short: "Launch the terminal UI.", + PreRunE: loadRepoEnsureUser(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runTermUI(env) + }, + } + + return cmd +} + +func runTermUI(env *Env) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -17,15 +34,3 @@ func runTermUI(cmd *cobra.Command, args []string) error { return termui.Run(backend) } - -var termUICmd = &cobra.Command{ - Use: "termui", - Aliases: []string{"tui"}, - Short: "Launch the terminal UI.", - PreRunE: loadRepoEnsureUser, - RunE: runTermUI, -} - -func init() { - RootCmd.AddCommand(termUICmd) -} diff --git a/commands/title.go b/commands/title.go index 66a9de7a..c1a0e7fb 100644 --- a/commands/title.go +++ b/commands/title.go @@ -1,16 +1,32 @@ package commands import ( - "fmt" + "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/commands/select" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -func runTitle(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newTitleCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "title [<id>]", + Short: "Display or change a title of a bug.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runTitle(env, args) + }, + } + + cmd.AddCommand(newTitleEditCommand()) + + return cmd +} + +func runTitle(env *Env, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -24,20 +40,7 @@ func runTitle(cmd *cobra.Command, args []string) error { snap := b.Snapshot() - fmt.Println(snap.Title) + env.out.Println(snap.Title) return nil } - -var titleCmd = &cobra.Command{ - Use: "title [<id>]", - Short: "Display or change a title of a bug.", - PreRunE: loadRepo, - RunE: runTitle, -} - -func init() { - RootCmd.AddCommand(titleCmd) - - titleCmd.Flags().SortFlags = false -} diff --git a/commands/title_edit.go b/commands/title_edit.go index 3e40bd9e..853622cd 100644 --- a/commands/title_edit.go +++ b/commands/title_edit.go @@ -1,21 +1,43 @@ package commands import ( - "fmt" + "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/commands/select" "github.com/MichaelMure/git-bug/input" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -var ( - titleEditTitle string -) +type titleEditOptions struct { + title string +} + +func newTitleEditCommand() *cobra.Command { + env := newEnv() + options := titleEditOptions{} + + cmd := &cobra.Command{ + Use: "edit [<id>]", + Short: "Edit a title of a bug.", + PreRunE: loadRepoEnsureUser(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runTitleEdit(env, options, args) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVarP(&options.title, "title", "t", "", + "Provide a title to describe the issue", + ) -func runTitleEdit(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) + return cmd +} + +func runTitleEdit(env *Env, opts titleEditOptions, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -29,10 +51,10 @@ func runTitleEdit(cmd *cobra.Command, args []string) error { snap := b.Snapshot() - if titleEditTitle == "" { - titleEditTitle, err = input.BugTitleEditorInput(repo, snap.Title) + if opts.title == "" { + opts.title, err = input.BugTitleEditorInput(env.repo, snap.Title) if err == input.ErrEmptyTitle { - fmt.Println("Empty title, aborting.") + env.out.Println("Empty title, aborting.") return nil } if err != nil { @@ -40,31 +62,14 @@ func runTitleEdit(cmd *cobra.Command, args []string) error { } } - if titleEditTitle == snap.Title { - fmt.Println("No change, aborting.") + if opts.title == snap.Title { + env.err.Println("No change, aborting.") } - _, err = b.SetTitle(titleEditTitle) + _, err = b.SetTitle(opts.title) if err != nil { return err } return b.Commit() } - -var titleEditCmd = &cobra.Command{ - Use: "edit [<id>]", - Short: "Edit a title of a bug.", - PreRunE: loadRepoEnsureUser, - RunE: runTitleEdit, -} - -func init() { - titleCmd.AddCommand(titleEditCmd) - - titleEditCmd.Flags().SortFlags = false - - titleEditCmd.Flags().StringVarP(&titleEditTitle, "title", "t", "", - "Provide a title to describe the issue", - ) -} diff --git a/commands/user.go b/commands/user.go index 2888e131..35e00a7c 100644 --- a/commands/user.go +++ b/commands/user.go @@ -10,12 +10,38 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -var ( - userFieldsQuery string -) +type userOptions struct { + fieldsQuery string +} + +func newUserCommand() *cobra.Command { + env := newEnv() + options := userOptions{} + + cmd := &cobra.Command{ + Use: "user [<user-id>]", + Short: "Display or change the user identity.", + PreRunE: loadRepoEnsureUser(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runUser(env, options, args) + }, + } + + cmd.AddCommand(newUserAdoptCommand()) + cmd.AddCommand(newUserCreateCommand()) + cmd.AddCommand(newUserLsCommand()) + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVarP(&options.fieldsQuery, "field", "f", "", + "Select field to display. Valid values are [email,humanId,id,lastModification,lastModificationLamport,login,metadata,name]") -func runUser(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) + return cmd +} + +func runUser(env *Env, opts userOptions, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -37,62 +63,47 @@ func runUser(cmd *cobra.Command, args []string) error { return err } - if userFieldsQuery != "" { - switch userFieldsQuery { + if opts.fieldsQuery != "" { + switch opts.fieldsQuery { case "email": - fmt.Printf("%s\n", id.Email()) + env.out.Printf("%s\n", id.Email()) case "login": - fmt.Printf("%s\n", id.Login()) + env.out.Printf("%s\n", id.Login()) case "humanId": - fmt.Printf("%s\n", id.Id().Human()) + env.out.Printf("%s\n", id.Id().Human()) case "id": - fmt.Printf("%s\n", id.Id()) + env.out.Printf("%s\n", id.Id()) case "lastModification": - fmt.Printf("%s\n", id.LastModification(). + env.out.Printf("%s\n", id.LastModification(). Time().Format("Mon Jan 2 15:04:05 2006 +0200")) case "lastModificationLamport": - fmt.Printf("%d\n", id.LastModificationLamport()) + env.out.Printf("%d\n", id.LastModificationLamport()) case "metadata": for key, value := range id.ImmutableMetadata() { - fmt.Printf("%s\n%s\n", key, value) + env.out.Printf("%s\n%s\n", key, value) } case "name": - fmt.Printf("%s\n", id.Name()) + env.out.Printf("%s\n", id.Name()) default: - return fmt.Errorf("\nUnsupported field: %s\n", userFieldsQuery) + return fmt.Errorf("\nUnsupported field: %s\n", opts.fieldsQuery) } return nil } - fmt.Printf("Id: %s\n", id.Id()) - fmt.Printf("Name: %s\n", id.Name()) - fmt.Printf("Email: %s\n", id.Email()) - fmt.Printf("Login: %s\n", id.Login()) - fmt.Printf("Last modification: %s (lamport %d)\n", + env.out.Printf("Id: %s\n", id.Id()) + env.out.Printf("Name: %s\n", id.Name()) + env.out.Printf("Email: %s\n", id.Email()) + env.out.Printf("Login: %s\n", id.Login()) + env.out.Printf("Last modification: %s (lamport %d)\n", id.LastModification().Time().Format("Mon Jan 2 15:04:05 2006 +0200"), id.LastModificationLamport()) - fmt.Println("Metadata:") + env.out.Println("Metadata:") for key, value := range id.ImmutableMetadata() { - fmt.Printf(" %s --> %s\n", key, value) + env.out.Printf(" %s --> %s\n", key, value) } - // fmt.Printf("Protected: %v\n", id.IsProtected()) + // env.out.Printf("Protected: %v\n", id.IsProtected()) return nil } - -var userCmd = &cobra.Command{ - Use: "user [<user-id>]", - Short: "Display or change the user identity.", - PreRunE: loadRepoEnsureUser, - RunE: runUser, -} - -func init() { - RootCmd.AddCommand(userCmd) - userCmd.Flags().SortFlags = false - - userCmd.Flags().StringVarP(&userFieldsQuery, "field", "f", "", - "Select field to display. Valid values are [email,humanId,id,lastModification,lastModificationLamport,login,metadata,name]") -} diff --git a/commands/user_adopt.go b/commands/user_adopt.go index 7054f1f7..0eb8b467 100644 --- a/commands/user_adopt.go +++ b/commands/user_adopt.go @@ -1,17 +1,30 @@ package commands import ( - "fmt" - "os" - "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/util/interrupt" ) -func runUserAdopt(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newUserAdoptCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "adopt <user-id>", + Short: "Adopt an existing identity as your own.", + Args: cobra.ExactArgs(1), + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runUserAdopt(env, args) + }, + } + + return cmd +} + +func runUserAdopt(env *Env, args []string) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -30,20 +43,7 @@ func runUserAdopt(cmd *cobra.Command, args []string) error { return err } - _, _ = fmt.Fprintf(os.Stderr, "Your identity is now: %s\n", i.DisplayName()) + env.out.Printf("Your identity is now: %s\n", i.DisplayName()) return nil } - -var userAdoptCmd = &cobra.Command{ - Use: "adopt <user-id>", - Short: "Adopt an existing identity as your own.", - PreRunE: loadRepo, - RunE: runUserAdopt, - Args: cobra.ExactArgs(1), -} - -func init() { - userCmd.AddCommand(userAdoptCmd) - userAdoptCmd.Flags().SortFlags = false -} diff --git a/commands/user_create.go b/commands/user_create.go index df4aa8e9..6433d38e 100644 --- a/commands/user_create.go +++ b/commands/user_create.go @@ -1,17 +1,30 @@ package commands import ( - "fmt" - "os" + "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/input" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) -func runUserCreate(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) +func newUserCreateCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "create", + Short: "Create a new identity.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runUserCreate(env) + }, + } + + return cmd +} + +func runUserCreate(env *Env) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -65,20 +78,8 @@ func runUserCreate(cmd *cobra.Command, args []string) error { } } - _, _ = fmt.Fprintln(os.Stderr) - fmt.Println(id.Id()) + env.err.Println() + env.out.Println(id.Id()) return nil } - -var userCreateCmd = &cobra.Command{ - Use: "create", - Short: "Create a new identity.", - PreRunE: loadRepo, - RunE: runUserCreate, -} - -func init() { - userCmd.AddCommand(userCreateCmd) - userCreateCmd.Flags().SortFlags = false -} diff --git a/commands/user_ls.go b/commands/user_ls.go index b3fb32e6..5087ebd4 100644 --- a/commands/user_ls.go +++ b/commands/user_ls.go @@ -11,12 +11,34 @@ import ( "github.com/MichaelMure/git-bug/util/interrupt" ) -var ( - userLsOutputFormat string -) +type userLsOptions struct { + format string +} + +func newUserLsCommand() *cobra.Command { + env := newEnv() + options := userLsOptions{} + + cmd := &cobra.Command{ + Use: "ls", + Short: "List identities.", + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runUserLs(env, options) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVarP(&options.format, "format", "f", "default", + "Select the output formatting style. Valid values are [default,json]") -func runUserLs(_ *cobra.Command, _ []string) error { - backend, err := cache.NewRepoCache(repo) + return cmd +} + +func runUserLs(env *Env, opts userLsOptions) error { + backend, err := cache.NewRepoCache(env.repo) if err != nil { return err } @@ -33,19 +55,19 @@ func runUserLs(_ *cobra.Command, _ []string) error { users = append(users, user) } - switch userLsOutputFormat { + switch opts.format { case "json": - return userLsJsonFormatter(users) + return userLsJsonFormatter(env, users) case "default": - return userLsDefaultFormatter(users) + return userLsDefaultFormatter(env, users) default: - return fmt.Errorf("unknown format %s", userLsOutputFormat) + return fmt.Errorf("unknown format %s", opts.format) } } -func userLsDefaultFormatter(users []*cache.IdentityExcerpt) error { +func userLsDefaultFormatter(env *Env, users []*cache.IdentityExcerpt) error { for _, user := range users { - fmt.Printf("%s %s\n", + env.out.Printf("%s %s\n", colors.Cyan(user.Id.Human()), user.DisplayName(), ) @@ -54,27 +76,13 @@ func userLsDefaultFormatter(users []*cache.IdentityExcerpt) error { return nil } -func userLsJsonFormatter(users []*cache.IdentityExcerpt) error { +func userLsJsonFormatter(env *Env, users []*cache.IdentityExcerpt) error { jsonUsers := make([]JSONIdentity, len(users)) for i, user := range users { jsonUsers[i] = NewJSONIdentityFromExcerpt(user) } jsonObject, _ := json.MarshalIndent(jsonUsers, "", " ") - fmt.Printf("%s\n", jsonObject) + env.out.Printf("%s\n", jsonObject) return nil } - -var userLsCmd = &cobra.Command{ - Use: "ls", - Short: "List identities.", - PreRunE: loadRepo, - RunE: runUserLs, -} - -func init() { - userCmd.AddCommand(userLsCmd) - userLsCmd.Flags().SortFlags = false - userLsCmd.Flags().StringVarP(&userLsOutputFormat, "format", "f", "default", - "Select the output formatting style. Valid values are [default,json]") -} diff --git a/commands/version.go b/commands/version.go index 668c37fe..71baba40 100644 --- a/commands/version.go +++ b/commands/version.go @@ -1,71 +1,62 @@ package commands import ( - "fmt" "runtime" "github.com/spf13/cobra" ) -// These variables are initialized externally during the build. See the Makefile. -var GitCommit string -var GitLastTag string -var GitExactTag string +type versionOptions struct { + number bool + commit bool + all bool +} -var ( - versionNumber bool - versionCommit bool - versionAll bool -) +func newVersionCommand() *cobra.Command { + env := newEnv() + options := versionOptions{} -func runVersionCmd(cmd *cobra.Command, args []string) { - if versionAll { - fmt.Printf("%s version: %s\n", rootCommandName, RootCmd.Version) - fmt.Printf("System version: %s/%s\n", runtime.GOARCH, runtime.GOOS) - fmt.Printf("Golang version: %s\n", runtime.Version()) - return + cmd := &cobra.Command{ + Use: "version", + Short: "Show git-bug version information.", + Run: func(cmd *cobra.Command, args []string) { + runVersion(env, options, cmd.Root()) + }, } - if versionNumber { - fmt.Println(RootCmd.Version) - return - } + flags := cmd.Flags() + flags.SortFlags = false - if versionCommit { - fmt.Println(GitCommit) - return - } - - fmt.Printf("%s version: %s\n", rootCommandName, RootCmd.Version) -} + flags.BoolVarP(&options.number, "number", "n", false, + "Only show the version number", + ) + flags.BoolVarP(&options.commit, "commit", "c", false, + "Only show the commit hash", + ) + flags.BoolVarP(&options.all, "all", "a", false, + "Show all version information", + ) -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Show git-bug version information.", - Run: runVersionCmd, + return cmd } -func init() { - if GitExactTag == "undefined" { - GitExactTag = "" +func runVersion(env *Env, opts versionOptions, root *cobra.Command) { + if opts.all { + env.out.Printf("%s version: %s\n", rootCommandName, root.Version) + env.out.Printf("System version: %s/%s\n", runtime.GOARCH, runtime.GOOS) + env.out.Printf("Golang version: %s\n", runtime.Version()) + return } - RootCmd.Version = GitLastTag - - if GitExactTag == "" { - RootCmd.Version = fmt.Sprintf("%s-dev-%.10s", RootCmd.Version, GitCommit) + if opts.number { + env.out.Println(root.Version) + return } - RootCmd.AddCommand(versionCmd) - versionCmd.Flags().SortFlags = false + if opts.commit { + env.out.Println(GitCommit) + return + } - versionCmd.Flags().BoolVarP(&versionNumber, "number", "n", false, - "Only show the version number", - ) - versionCmd.Flags().BoolVarP(&versionCommit, "commit", "c", false, - "Only show the commit hash", - ) - versionCmd.Flags().BoolVarP(&versionAll, "all", "a", false, - "Show all version information", - ) + env.out.Printf("%s version: %s\n", rootCommandName, root.Version) } diff --git a/commands/webui.go b/commands/webui.go index 83480e08..4d87a303 100644 --- a/commands/webui.go +++ b/commands/webui.go @@ -24,25 +24,54 @@ import ( "github.com/MichaelMure/git-bug/webui" ) -var ( - webUIPort int - webUIOpen bool - webUINoOpen bool - webUIReadOnly bool -) - const webUIOpenConfigKey = "git-bug.webui.open" -func runWebUI(cmd *cobra.Command, args []string) error { - if webUIPort == 0 { +type webUIOptions struct { + port int + open bool + noOpen bool + readOnly bool +} + +func newWebUICommand() *cobra.Command { + env := newEnv() + options := webUIOptions{} + + cmd := &cobra.Command{ + Use: "webui", + Short: "Launch the web UI.", + Long: `Launch the web UI. + +Available git config: + git-bug.webui.open [bool]: control the automatic opening of the web UI in the default browser +`, + PreRunE: loadRepo(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runWebUI(env, options, args) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.BoolVar(&options.open, "open", false, "Automatically open the web UI in the default browser") + flags.BoolVar(&options.noOpen, "no-open", false, "Prevent the automatic opening of the web UI in the default browser") + flags.IntVarP(&options.port, "port", "p", 0, "Port to listen to (default is random)") + flags.BoolVar(&options.readOnly, "read-only", false, "Whether to run the web UI in read-only mode") + + return cmd +} + +func runWebUI(env *Env, opts webUIOptions, args []string) error { + if opts.port == 0 { var err error - webUIPort, err = freeport.GetFreePort() + opts.port, err = freeport.GetFreePort() if err != nil { return err } } - addr := fmt.Sprintf("127.0.0.1:%d", webUIPort) + addr := fmt.Sprintf("127.0.0.1:%d", opts.port) webUiAddr := fmt.Sprintf("http://%s", addr) router := mux.NewRouter() @@ -50,8 +79,8 @@ func runWebUI(cmd *cobra.Command, args []string) error { // If the webUI is not read-only, use an authentication middleware with a // fixed identity: the default user of the repo // TODO: support dynamic authentication with OAuth - if !webUIReadOnly { - author, err := identity.GetUserIdentity(repo) + if !opts.readOnly { + author, err := identity.GetUserIdentity(env.repo) if err != nil { return err } @@ -59,7 +88,7 @@ func runWebUI(cmd *cobra.Command, args []string) error { } mrc := cache.NewMultiRepoCache() - _, err := mrc.RegisterDefaultRepository(repo) + _, err := mrc.RegisterDefaultRepository(env.repo) if err != nil { return err } @@ -86,7 +115,7 @@ func runWebUI(cmd *cobra.Command, args []string) error { go func() { <-quit - fmt.Println("WebUI is shutting down...") + env.out.Println("WebUI is shutting down...") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() @@ -99,18 +128,18 @@ func runWebUI(cmd *cobra.Command, args []string) error { // Teardown err := graphqlHandler.Close() if err != nil { - fmt.Println(err) + env.out.Println(err) } close(done) }() - fmt.Printf("Web UI: %s\n", webUiAddr) - fmt.Printf("Graphql API: http://%s/graphql\n", addr) - fmt.Printf("Graphql Playground: http://%s/playground\n", addr) - fmt.Println("Press Ctrl+c to quit") + env.out.Printf("Web UI: %s\n", webUiAddr) + env.out.Printf("Graphql API: http://%s/graphql\n", addr) + env.out.Printf("Graphql Playground: http://%s/playground\n", addr) + env.out.Println("Press Ctrl+c to quit") - configOpen, err := repo.LocalConfig().ReadBool(webUIOpenConfigKey) + configOpen, err := env.repo.LocalConfig().ReadBool(webUIOpenConfigKey) if err == repository.ErrNoConfigEntry { // default to true configOpen = true @@ -118,12 +147,12 @@ func runWebUI(cmd *cobra.Command, args []string) error { return err } - shouldOpen := (configOpen && !webUINoOpen) || webUIOpen + shouldOpen := (configOpen && !opts.noOpen) || opts.open if shouldOpen { err = open.Run(webUiAddr) if err != nil { - fmt.Println(err) + env.out.Println(err) } } @@ -134,29 +163,6 @@ func runWebUI(cmd *cobra.Command, args []string) error { <-done - fmt.Println("WebUI stopped") + env.out.Println("WebUI stopped") return nil } - -var webUICmd = &cobra.Command{ - Use: "webui", - Short: "Launch the web UI.", - Long: `Launch the web UI. - -Available git config: - git-bug.webui.open [bool]: control the automatic opening of the web UI in the default browser -`, - PreRunE: loadRepo, - RunE: runWebUI, -} - -func init() { - RootCmd.AddCommand(webUICmd) - - webUICmd.Flags().SortFlags = false - - webUICmd.Flags().BoolVar(&webUIOpen, "open", false, "Automatically open the web UI in the default browser") - webUICmd.Flags().BoolVar(&webUINoOpen, "no-open", false, "Prevent the automatic opening of the web UI in the default browser") - webUICmd.Flags().IntVarP(&webUIPort, "port", "p", 0, "Port to listen to (default is random)") - webUICmd.Flags().BoolVar(&webUIReadOnly, "read-only", false, "Whether to run the web UI in read-only mode") -} |