diff options
author | Michael Muré <batolettre@gmail.com> | 2020-06-28 19:24:32 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-28 19:24:32 +0200 |
commit | 33d510ac3999b3d8fa7a4652dc44a21c713f97de (patch) | |
tree | 79ea46f76ac6f69c2e6cbf3323be7c620c39a510 /commands | |
parent | c0dbc149d5c0c3610476ba14a800c9ba803a2c2c (diff) | |
parent | 536c290dfbe6e0741c56f33659563c528c9f09b1 (diff) | |
download | git-bug-33d510ac3999b3d8fa7a4652dc44a21c713f97de.tar.gz |
Merge pull request #414 from MichaelMure/cmd-rework
commands: refactor to avoid globals
Diffstat (limited to 'commands')
38 files changed, 1410 insertions, 1369 deletions
diff --git a/commands/add.go b/commands/add.go index e656a262..17fbbc93 100644 --- a/commands/add.go +++ b/commands/add.go @@ -1,42 +1,58 @@ 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 runAddBug(cmd *cobra.Command, args []string) error { - var err error +func newAddCommand() *cobra.Command { + env := newEnv() + options := addOptions{} - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err + cmd := &cobra.Command{ + Use: "add", + Short: "Create a new bug.", + PreRunE: loadBackendEnsureUser(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runAdd(env, options) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - if addMessageFile != "" && addMessage == "" { - addTitle, addMessage, err = input.BugCreateFileInput(addMessageFile) + flags := cmd.Flags() + flags.SortFlags = false + + 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") + + return cmd +} + +func runAdd(env *Env, opts addOptions) error { + var err error + 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(env.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 +60,12 @@ func runAddBug(cmd *cobra.Command, args []string) error { } } - b, _, err := backend.NewBug(addTitle, addMessage) + b, _, err := env.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..46b3d9ec 100644 --- a/commands/bridge.go +++ b/commands/bridge.go @@ -1,43 +1,43 @@ package commands import ( - "fmt" - "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/bridge" - "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/util/interrupt" ) -func runBridge(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newBridgeCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "bridge", + Short: "Configure and use bridges to other bug trackers.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridge(env) + }, + Args: cobra.NoArgs, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - configured, err := bridge.ConfiguredBridges(backend) + cmd.AddCommand(newBridgeAuthCommand()) + cmd.AddCommand(newBridgeConfigureCommand()) + cmd.AddCommand(newBridgePullCommand()) + cmd.AddCommand(newBridgePushCommand()) + cmd.AddCommand(newBridgeRm()) + + return cmd +} + +func runBridge(env *Env) error { + configured, err := bridge.ConfiguredBridges(env.backend) if err != nil { return err } 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..db4c5212 100644 --- a/commands/bridge_auth.go +++ b/commands/bridge_auth.go @@ -1,7 +1,6 @@ package commands import ( - "fmt" "sort" "strings" @@ -10,20 +9,32 @@ import ( text "github.com/MichaelMure/go-term-text" "github.com/MichaelMure/git-bug/bridge/core/auth" - "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/util/colors" - "github.com/MichaelMure/git-bug/util/interrupt" ) -func runBridgeAuth(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newBridgeAuthCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "auth", + Short: "List all known bridge authentication credentials.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridgeAuth(env) + }, + Args: cobra.NoArgs, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - creds, err := auth.List(backend) + cmd.AddCommand(newBridgeAuthAddTokenCommand()) + cmd.AddCommand(newBridgeAuthRm()) + cmd.AddCommand(newBridgeAuthShow()) + + return cmd +} + +func runBridgeAuth(env *Env) error { + creds, err := auth.List(env.backend) if err != nil { return err } @@ -44,7 +55,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 +66,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..55edd919 100644 --- a/commands/bridge_auth_addtoken.go +++ b/commands/bridge_auth_addtoken.go @@ -14,37 +14,57 @@ import ( "github.com/MichaelMure/git-bug/bridge/core" "github.com/MichaelMure/git-bug/bridge/core/auth" "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/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: loadBackendEnsureUser(env), + PostRunE: closeBackend(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") + + return cmd +} -func runBridgeTokenAdd(cmd *cobra.Command, args []string) error { +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) - 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 +75,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') @@ -66,63 +86,45 @@ func runBridgeTokenAdd(cmd *cobra.Command, args []string) error { } var user *cache.IdentityCache + var err error - if bridgeAuthAddTokenUser == "" { - user, err = backend.GetUserIdentity() + if opts.user == "" { + user, err = env.backend.GetUserIdentity() } else { - user, err = backend.ResolveIdentityPrefix(bridgeAuthAddTokenUser) + user, err = env.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..408a6cf1 100644 --- a/commands/bridge_auth_show.go +++ b/commands/bridge_auth_show.go @@ -9,34 +9,42 @@ import ( "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/bridge/core/auth" - "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/util/interrupt" ) -func runBridgeAuthShow(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newBridgeAuthShow() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "show", + Short: "Display an authentication credential.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridgeAuthShow(env, args) + }, + Args: cobra.ExactArgs(1), } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - cred, err := auth.LoadWithPrefix(repo, args[0]) + return cmd +} + +func runBridgeAuthShow(env *Env, args []string) error { + 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 +52,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..ecdb6502 100644 --- a/commands/bridge_configure.go +++ b/commands/bridge_configure.go @@ -12,84 +12,160 @@ import ( "github.com/MichaelMure/git-bug/bridge" "github.com/MichaelMure/git-bug/bridge/core" "github.com/MichaelMure/git-bug/bridge/core/auth" - "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/repository" - "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{} -func runBridgeConfigure(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err + 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/ + +# For Gitlab +git bug bridge configure \ + --name=default \ + --target=github \ + --url=https://github.com/michaelmure/git-bug \ + --token=$(TOKEN)`, + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridgeConfigure(env, options) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - if (bridgeConfigureTokenStdin || bridgeConfigureToken != "" || bridgeConfigureParams.CredPrefix != "") && - (bridgeConfigureName == "" || bridgeConfigureTarget == "") { + 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 { + var err error + + if (opts.tokenStdin || opts.token != "" || opts.params.CredPrefix != "") && + (opts.name == "" || opts.target == "") { return fmt.Errorf("you must provide a bridge name and target to configure a bridge with a credential") } // early fail - if 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(env.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 +193,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 +228,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..bb705582 100644 --- a/commands/bridge_pull.go +++ b/commands/bridge_pull.go @@ -13,33 +13,50 @@ import ( "github.com/MichaelMure/git-bug/bridge" "github.com/MichaelMure/git-bug/bridge/core" - "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/util/interrupt" ) -var ( - bridgePullImportSince string - bridgePullNoResume bool -) +type bridgePullOptions struct { + importSince string + noResume bool +} -func runBridgePull(cmd *cobra.Command, args []string) error { - if bridgePullNoResume && bridgePullImportSince != "" { - return fmt.Errorf("only one of --no-resume and --since flags should be used") +func newBridgePullCommand() *cobra.Command { + env := newEnv() + options := bridgePullOptions{} + + cmd := &cobra.Command{ + Use: "pull [<name>]", + Short: "Pull updates.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridgePull(env, options, args) + }, + Args: cobra.MaximumNArgs(1), } - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err + flags := cmd.Flags() + flags.SortFlags = false + + flags.BoolVarP(&options.noResume, "no-resume", "n", false, "force importing all bugs") + flags.StringVarP(&options.importSince, "since", "s", "", "import only bugs updated after the given date (ex: \"200h\" or \"june 2 2019\")") + + return cmd +} + +func runBridgePull(env *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") } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) var b *core.Bridge + var err error if len(args) == 0 { - b, err = bridge.DefaultBridge(backend) + b, err = bridge.DefaultBridge(env.backend) } else { - b, err = bridge.LoadBridge(backend, args[0]) + b, err = bridge.LoadBridge(env.backend, args[0]) } if err != nil { @@ -58,14 +75,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 +94,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 +119,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 +151,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..7061a5ca 100644 --- a/commands/bridge_push.go +++ b/commands/bridge_push.go @@ -2,7 +2,6 @@ package commands import ( "context" - "fmt" "os" "sync" "time" @@ -11,24 +10,34 @@ import ( "github.com/MichaelMure/git-bug/bridge" "github.com/MichaelMure/git-bug/bridge/core" - "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/util/interrupt" ) -func runBridgePush(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newBridgePushCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "push [<name>]", + Short: "Push updates.", + PreRunE: loadBackendEnsureUser(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridgePush(env, args) + }, + Args: cobra.MaximumNArgs(1), } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) + return cmd +} + +func runBridgePush(env *Env, args []string) error { var b *core.Bridge + var err error if len(args) == 0 { - b, err = bridge.DefaultBridge(backend) + b, err = bridge.DefaultBridge(env.backend) } else { - b, err = bridge.LoadBridge(backend, args[0]) + b, err = bridge.LoadBridge(env.backend, args[0]) } if err != nil { @@ -46,14 +55,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 +80,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 +89,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..9f93c37a 100644 --- a/commands/bridge_rm.go +++ b/commands/bridge_rm.go @@ -1,40 +1,34 @@ package commands import ( - "fmt" - "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/bridge" - "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/util/interrupt" ) -func runBridgeRm(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newBridgeRm() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "rm <name>", + Short: "Delete a configured bridge.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runBridgeRm(env, args) + }, + Args: cobra.ExactArgs(1), } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - err = bridge.RemoveBridge(backend, args[0]) + return cmd +} + +func runBridgeRm(env *Env, args []string) error { + err := bridge.RemoveBridge(env.backend, args[0]) if err != nil { 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..e81405a6 100644 --- a/commands/comment.go +++ b/commands/comment.go @@ -1,60 +1,49 @@ 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) - if err != nil { - return err +func newCommentCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "comment [<id>]", + Short: "Display or add comments to a bug.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runComment(env, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - b, args, err := _select.ResolveBug(backend, args) + cmd.AddCommand(newCommentAddCommand()) + + return cmd +} + +func runComment(env *Env, args []string) error { + b, args, err := _select.ResolveBug(env.backend, args) if err != nil { return err } 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..47366d00 100644 --- a/commands/comment_add.go +++ b/commands/comment_add.go @@ -1,44 +1,60 @@ 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 runCommentAdd(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newCommentAddCommand() *cobra.Command { + env := newEnv() + options := commentAddOptions{} + + cmd := &cobra.Command{ + Use: "add [<id>]", + Short: "Add a new comment to a bug.", + PreRunE: loadBackendEnsureUser(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runCommentAdd(env, options, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - b, args, err := _select.ResolveBug(backend, 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") + + return cmd +} + +func runCommentAdd(env *Env, opts commentAddOptions, args []string) error { + b, args, err := _select.ResolveBug(env.backend, args) if err != nil { 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(env.backend, "") if err == input.ErrEmptyMessage { - fmt.Println("Empty message, aborting.") + env.err.Println("Empty message, aborting.") return nil } if err != nil { @@ -46,31 +62,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..23f77e2d 100644 --- a/commands/deselect.go +++ b/commands/deselect.go @@ -1,41 +1,37 @@ package commands import ( - "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) - if err != nil { - return err - } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - err = _select.Clear(backend) - if err != nil { - return err - } + "github.com/MichaelMure/git-bug/commands/select" +) - return nil -} +func newDeselectCommand() *cobra.Command { + env := newEnv() -var deselectCmd = &cobra.Command{ - Use: "deselect", - Short: "Clear the implicitly selected bug.", - Example: `git bug select 2f15 + 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, - RunE: runDeselect, + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runDeselect(env) + }, + } + + return cmd } -func init() { - RootCmd.AddCommand(deselectCmd) - deselectCmd.Flags().SortFlags = false +func runDeselect(env *Env) error { + err := _select.Clear(env.backend) + if err != nil { + return err + } + + return nil } diff --git a/commands/env.go b/commands/env.go new file mode 100644 index 00000000..c3596c2d --- /dev/null +++ b/commands/env.go @@ -0,0 +1,150 @@ +package commands + +import ( + "fmt" + "io" + "os" + + "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/bug" + "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/identity" + "github.com/MichaelMure/git-bug/repository" + "github.com/MichaelMure/git-bug/util/interrupt" +) + +// Env is the environment of a command +type Env struct { + repo repository.ClockedRepo + backend *cache.RepoCache + 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...) +} + +// loadRepo is a pre-run function that load the repository for use in a command +func loadRepo(env *Env) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + cwd, err := os.Getwd() + if err != nil { + return fmt.Errorf("unable to get the current working directory: %q", err) + } + + 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 + } + + return nil + } +} + +// loadRepoEnsureUser is the same as loadRepo, but also ensure that the user has configured +// an identity. Use this pre-run function when an error after using the configured user won't +// do. +func loadRepoEnsureUser(env *Env) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + err := loadRepo(env)(cmd, args) + if err != nil { + return err + } + + _, err = identity.GetUserIdentity(env.repo) + if err != nil { + return err + } + + return nil + } +} + +// loadBackend is a pre-run function that load the repository and the backend for use in a command +// When using this function you also need to use closeBackend as a post-run +func loadBackend(env *Env) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + err := loadRepo(env)(cmd, args) + if err != nil { + return err + } + + env.backend, err = cache.NewRepoCache(env.repo) + if err != nil { + return err + } + + cleaner := func(env *Env) interrupt.CleanerFunc { + return func() error { + if env.backend != nil { + err := env.backend.Close() + env.backend = nil + return err + } + return nil + } + } + + // Cleanup properly on interrupt + interrupt.RegisterCleaner(cleaner(env)) + return nil + } +} + +// loadBackendEnsureUser is the same as loadBackend, but also ensure that the user has configured +// an identity. Use this pre-run function when an error after using the configured user won't +// do. +func loadBackendEnsureUser(env *Env) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + err := loadRepo(env)(cmd, args) + if err != nil { + return err + } + + _, err = identity.GetUserIdentity(env.repo) + if err != nil { + return err + } + + return nil + } +} + +// closeBackend is a post-run function that will close the backend properly +// if it has been opened. +func closeBackend(env *Env) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + if env.backend == nil { + return nil + } + err := env.backend.Close() + env.backend = nil + return err + } +} diff --git a/commands/label.go b/commands/label.go index a07e9efc..de7bdb3a 100644 --- a/commands/label.go +++ b/commands/label.go @@ -1,23 +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 runLabel(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newLabelCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "label [<id>]", + Short: "Display, add or remove labels to/from a bug.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runLabel(env, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - b, args, err := _select.ResolveBug(backend, args) + cmd.AddCommand(newLabelAddCommand()) + cmd.AddCommand(newLabelRmCommand()) + + return cmd +} + +func runLabel(env *Env, args []string) error { + b, args, err := _select.ResolveBug(env.backend, args) if err != nil { return err } @@ -25,21 +34,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..05a27948 100644 --- a/commands/label_add.go +++ b/commands/label_add.go @@ -1,23 +1,29 @@ 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) - if err != nil { - return err +func newLabelAddCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "add [<id>] <label>[...]", + Short: "Add a label to a bug.", + PreRunE: loadBackendEnsureUser(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runLabelAdd(env, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - b, args, err := _select.ResolveBug(backend, args) + return cmd +} + +func runLabelAdd(env *Env, args []string) error { + b, args, err := _select.ResolveBug(env.backend, args) if err != nil { return err } @@ -25,7 +31,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 +40,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..445a56a4 100644 --- a/commands/label_rm.go +++ b/commands/label_rm.go @@ -1,24 +1,29 @@ 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" ) -func runLabelRm(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newLabelRmCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "rm [<id>] <label>[...]", + Short: "Remove a label from a bug.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runLabelRm(env, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - b, args, err := _select.ResolveBug(backend, args) + return cmd +} + +func runLabelRm(env *Env, args []string) error { + b, args, err := _select.ResolveBug(env.backend, args) if err != nil { return err } @@ -26,7 +31,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 +40,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..bed6c057 100644 --- a/commands/ls-id.go +++ b/commands/ls-id.go @@ -1,44 +1,36 @@ 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 { - - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newLsIdCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "ls-id [<prefix>]", + Short: "List bug identifiers.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runLsId(env, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) + return cmd +} + +func runLsId(env *Env, args []string) error { var prefix = "" if len(args) != 0 { prefix = args[0] } - for _, id := range backend.AllBugsIds() { + for _, id := range env.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..a7c2fb6f 100644 --- a/commands/ls-labels.go +++ b/commands/ls-labels.go @@ -1,40 +1,34 @@ package commands import ( - "fmt" - - "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) - if err != nil { - return err - } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) +func newLsLabelCommand() *cobra.Command { + env := newEnv() - labels := backend.ValidLabels() + cmd := &cobra.Command{ + Use: "ls-label", + Short: "List valid labels.", + Long: `List valid labels. - for _, l := range labels { - fmt.Println(l) +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: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runLsLabel(env) + }, } - return nil + return cmd } -var lsLabelCmd = &cobra.Command{ - Use: "ls-label", - Short: "List valid labels.", - Long: `List valid labels. +func runLsLabel(env *Env) error { + labels := env.backend.ValidLabels() -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, -} + for _, l := range labels { + env.out.Println(l) + } -func init() { - RootCmd.AddCommand(lsLabelCmd) + return nil } diff --git a/commands/ls.go b/commands/ls.go index 34f1f982..ad61a852 100644 --- a/commands/ls.go +++ b/commands/ls.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "strings" + "time" text "github.com/MichaelMure/go-term-text" "github.com/spf13/cobra" @@ -12,28 +13,74 @@ import ( "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/query" "github.com/MichaelMure/git-bug/util/colors" - "github.com/MichaelMure/git-bug/util/interrupt" ) -var ( - lsQuery query.Query +type lsOptions struct { + query query.Query - lsStatusQuery []string - lsNoQuery []string - lsSortBy string - lsSortDirection string - lsOutputFormat string -) + 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 -func runLsBug(_ *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +List closed bugs sorted by creation with flags: +git bug ls --status closed --by creation +`, + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runLs(env, options, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) + + flags := cmd.Flags() + flags.SortFlags = false + + 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 { + time.Sleep(5 * time.Second) var q *query.Query + var err error + if len(args) >= 1 { q, err = query.Parse(strings.Join(args, " ")) @@ -41,35 +88,35 @@ 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) + allIds := env.backend.QueryBugs(q) bugExcerpt := make([]*cache.BugExcerpt, len(allIds)) for i, id := range allIds { - b, err := backend.ResolveBugExcerpt(id) + b, err := env.backend.ResolveBugExcerpt(id) if err != nil { return err } bugExcerpt[i] = b } - switch lsOutputFormat { + switch opts.outputFormat { case "org-mode": - return lsOrgmodeFormatter(backend, bugExcerpt) + return lsOrgmodeFormatter(env, bugExcerpt) case "plain": - return lsPlainFormatter(backend, bugExcerpt) + return lsPlainFormatter(env, bugExcerpt) case "json": - return lsJsonFormatter(backend, bugExcerpt) + return lsJsonFormatter(env, bugExcerpt) case "default": - return lsDefaultFormatter(backend, bugExcerpt) + return lsDefaultFormatter(env, bugExcerpt) default: - return fmt.Errorf("unknown format %s", lsOutputFormat) + return fmt.Errorf("unknown format %s", opts.outputFormat) } } @@ -90,7 +137,7 @@ type JSONBugExcerpt struct { Metadata map[string]string `json:"metadata"` } -func lsJsonFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error { +func lsJsonFormatter(env *Env, bugExcerpts []*cache.BugExcerpt) error { jsonBugs := make([]JSONBugExcerpt, len(bugExcerpts)) for i, b := range bugExcerpts { jsonBug := JSONBugExcerpt{ @@ -106,7 +153,7 @@ func lsJsonFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) } if b.AuthorId != "" { - author, err := backend.ResolveIdentityExcerpt(b.AuthorId) + author, err := env.backend.ResolveIdentityExcerpt(b.AuthorId) if err != nil { return err } @@ -117,7 +164,7 @@ func lsJsonFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) jsonBug.Actors = make([]JSONIdentity, len(b.Actors)) for i, element := range b.Actors { - actor, err := backend.ResolveIdentityExcerpt(element) + actor, err := env.backend.ResolveIdentityExcerpt(element) if err != nil { return err } @@ -126,7 +173,7 @@ func lsJsonFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) jsonBug.Participants = make([]JSONIdentity, len(b.Participants)) for i, element := range b.Participants { - participant, err := backend.ResolveIdentityExcerpt(element) + participant, err := env.backend.ResolveIdentityExcerpt(element) if err != nil { return err } @@ -136,15 +183,15 @@ 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, bugExcerpts []*cache.BugExcerpt) error { for _, b := range bugExcerpts { var name string if b.AuthorId != "" { - author, err := backend.ResolveIdentityExcerpt(b.AuthorId) + author, err := env.backend.ResolveIdentityExcerpt(b.AuthorId) if err != nil { return err } @@ -171,7 +218,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 +229,15 @@ func lsDefaultFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerp return nil } -func lsPlainFormatter(_ *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error { +func lsPlainFormatter(env *Env, 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, bugExcerpts []*cache.BugExcerpt) error { + env.out.Println("+TODO: OPEN | CLOSED") for _, b := range bugExcerpts { status := strings.Title(b.Status.String()) @@ -204,7 +251,7 @@ func lsOrgmodeFormatter(backend *cache.RepoCache, bugExcerpts []*cache.BugExcerp var name string if b.AuthorId != "" { - author, err := backend.ResolveIdentityExcerpt(b.AuthorId) + author, err := env.backend.ResolveIdentityExcerpt(b.AuthorId) if err != nil { return err } @@ -221,7 +268,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 +277,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) + actor, err := env.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) + participant, err := env.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 +310,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..3f032593 100644 --- a/commands/pull.go +++ b/commands/pull.go @@ -2,16 +2,29 @@ package commands import ( "errors" - "fmt" "github.com/spf13/cobra" - "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/entity" - "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: loadBackend(env), + PostRunE: closeBackend(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 +34,26 @@ func runPull(cmd *cobra.Command, args []string) error { remote = args[0] } - backend, err := cache.NewRepoCache(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) + stdout, err := env.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) { + for result := range env.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..f4b83fab 100644 --- a/commands/push.go +++ b/commands/push.go @@ -2,14 +2,27 @@ package commands import ( "errors" - "fmt" - "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: loadBackend(env), + PostRunE: closeBackend(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,31 +32,12 @@ func runPush(cmd *cobra.Command, args []string) error { remote = args[0] } - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err - } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - - stdout, err := backend.Push(remote) + stdout, err := env.backend.Push(remote) if err != nil { 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..a67fec1a 100644 --- a/commands/root.go +++ b/commands/root.go @@ -6,22 +6,20 @@ import ( "os" "github.com/spf13/cobra" - - "github.com/MichaelMure/git-bug/bug" - "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/repository" ) 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 +27,64 @@ 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 "$@" } `, -} - -func Execute() { - if err := RootCmd.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) } - 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 - } - - return nil + 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 } -// 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 - } - - _, err = identity.GetUserIdentity(repo) - if err != nil { - return err +func Execute() { + if err := NewRootCommand().Execute(); err != nil { + os.Exit(1) } - - return nil } diff --git a/commands/select.go b/commands/select.go index f2ae33ca..f2cf2d47 100644 --- a/commands/select.go +++ b/commands/select.go @@ -2,65 +2,59 @@ package commands import ( "errors" - "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" ) -func runSelect(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return errors.New("You must provide a bug id") +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: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runSelect(env, args) + }, } - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err + return cmd +} + +func runSelect(env *Env, args []string) error { + if len(args) == 0 { + return errors.New("You must provide a bug id") } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) prefix := args[0] - b, err := backend.ResolveBugPrefix(prefix) + b, err := env.backend.ResolveBugPrefix(prefix) if err != nil { return err } - err = _select.Select(backend, b.Id()) + err = _select.Select(env.backend, b.Id()) if err != nil { 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..f3995205 100644 --- a/commands/show.go +++ b/commands/show.go @@ -9,101 +9,117 @@ import ( "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/bug" - "github.com/MichaelMure/git-bug/cache" _select "github.com/MichaelMure/git-bug/commands/select" "github.com/MichaelMure/git-bug/util/colors" - "github.com/MichaelMure/git-bug/util/interrupt" ) -var ( - showFieldsQuery string - showOutputFormat string -) +type showOptions struct { + query string + format string +} -func runShowBug(_ *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newShowCommand() *cobra.Command { + env := newEnv() + options := showOptions{} + + cmd := &cobra.Command{ + Use: "show [<id>]", + Short: "Display the details of a bug.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runShow(env, options, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - b, args, err := _select.ResolveBug(backend, args) + flags := cmd.Flags() + flags.SortFlags = false + + 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 { + b, args, err := _select.ResolveBug(env.backend, args) if err != nil { 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 +129,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 +139,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 +149,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 +158,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 +171,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 +210,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 +238,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 +269,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 +285,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 +298,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 +315,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..57771bca 100644 --- a/commands/status.go +++ b/commands/status.go @@ -1,41 +1,39 @@ 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) - if err != nil { - return err +func newStatusCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "status [<id>]", + Short: "Display or change a bug status.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runStatus(env, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - b, args, err := _select.ResolveBug(backend, args) + cmd.AddCommand(newStatusCloseCommand()) + cmd.AddCommand(newStatusOpenCommand()) + + return cmd +} + +func runStatus(env *Env, args []string) error { + b, args, err := _select.ResolveBug(env.backend, args) if err != nil { return err } 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..29092a7b 100644 --- a/commands/status_close.go +++ b/commands/status_close.go @@ -1,21 +1,29 @@ package commands import ( - "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/commands/select" - "github.com/MichaelMure/git-bug/util/interrupt" "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/commands/select" ) -func runStatusClose(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newStatusCloseCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "close [<id>]", + Short: "Mark a bug as closed.", + PreRunE: loadBackendEnsureUser(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runStatusClose(env, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - b, args, err := _select.ResolveBug(backend, args) + return cmd +} + +func runStatusClose(env *Env, args []string) error { + b, args, err := _select.ResolveBug(env.backend, args) if err != nil { return err } @@ -27,14 +35,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..9dd9082c 100644 --- a/commands/status_open.go +++ b/commands/status_open.go @@ -1,21 +1,29 @@ package commands import ( - "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/commands/select" - "github.com/MichaelMure/git-bug/util/interrupt" "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/commands/select" ) -func runStatusOpen(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newStatusOpenCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "open [<id>]", + Short: "Mark a bug as open.", + PreRunE: loadBackendEnsureUser(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runStatusOpen(env, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - b, args, err := _select.ResolveBug(backend, args) + return cmd +} + +func runStatusOpen(env *Env, args []string) error { + b, args, err := _select.ResolveBug(env.backend, args) if err != nil { return err } @@ -27,14 +35,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..1470790f 100644 --- a/commands/termui.go +++ b/commands/termui.go @@ -1,31 +1,28 @@ package commands import ( - "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/termui" - "github.com/MichaelMure/git-bug/util/interrupt" "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/termui" ) -func runTermUI(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err - } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) +func newTermUICommand() *cobra.Command { + env := newEnv() - return termui.Run(backend) -} + cmd := &cobra.Command{ + Use: "termui", + Aliases: []string{"tui"}, + Short: "Launch the terminal UI.", + PreRunE: loadBackendEnsureUser(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runTermUI(env) + }, + } -var termUICmd = &cobra.Command{ - Use: "termui", - Aliases: []string{"tui"}, - Short: "Launch the terminal UI.", - PreRunE: loadRepoEnsureUser, - RunE: runTermUI, + return cmd } -func init() { - RootCmd.AddCommand(termUICmd) +func runTermUI(env *Env) error { + return termui.Run(env.backend) } diff --git a/commands/title.go b/commands/title.go index 66a9de7a..d11fb40e 100644 --- a/commands/title.go +++ b/commands/title.go @@ -1,43 +1,38 @@ 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) - if err != nil { - return err +func newTitleCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "title [<id>]", + Short: "Display or change a title of a bug.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runTitle(env, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - b, args, err := _select.ResolveBug(backend, args) + cmd.AddCommand(newTitleEditCommand()) + + return cmd +} + +func runTitle(env *Env, args []string) error { + b, args, err := _select.ResolveBug(env.backend, args) if err != nil { return err } 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..e2dbc245 100644 --- a/commands/title_edit.go +++ b/commands/title_edit.go @@ -1,38 +1,52 @@ 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 runTitleEdit(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newTitleEditCommand() *cobra.Command { + env := newEnv() + options := titleEditOptions{} + + cmd := &cobra.Command{ + Use: "edit [<id>]", + Short: "Edit a title of a bug.", + PreRunE: loadBackendEnsureUser(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runTitleEdit(env, options, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - b, args, err := _select.ResolveBug(backend, args) + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVarP(&options.title, "title", "t", "", + "Provide a title to describe the issue", + ) + + return cmd +} + +func runTitleEdit(env *Env, opts titleEditOptions, args []string) error { + b, args, err := _select.ResolveBug(env.backend, args) if err != nil { return err } 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 +54,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..8a6aef53 100644 --- a/commands/user.go +++ b/commands/user.go @@ -7,92 +7,97 @@ import ( "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/util/interrupt" ) -var ( - userFieldsQuery string -) +type userOptions struct { + fieldsQuery string +} -func runUser(cmd *cobra.Command, args []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newUserCommand() *cobra.Command { + env := newEnv() + options := userOptions{} + + cmd := &cobra.Command{ + Use: "user [<user-id>]", + Short: "Display or change the user identity.", + PreRunE: loadBackendEnsureUser(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runUser(env, options, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) + 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]") + + return cmd +} + +func runUser(env *Env, opts userOptions, args []string) error { if len(args) > 1 { return errors.New("only one identity can be displayed at a time") } var id *cache.IdentityCache + var err error if len(args) == 1 { - id, err = backend.ResolveIdentityPrefix(args[0]) + id, err = env.backend.ResolveIdentityPrefix(args[0]) } else { - id, err = backend.GetUserIdentity() + id, err = env.backend.GetUserIdentity() } if err != nil { 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..521f032f 100644 --- a/commands/user_adopt.go +++ b/commands/user_adopt.go @@ -1,49 +1,40 @@ 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) - if err != nil { - return err +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: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runUserAdopt(env, args) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) + return cmd +} + +func runUserAdopt(env *Env, args []string) error { prefix := args[0] - i, err := backend.ResolveIdentityPrefix(prefix) + i, err := env.backend.ResolveIdentityPrefix(prefix) if err != nil { return err } - err = backend.SetUserIdentity(i) + err = env.backend.SetUserIdentity(i) if err != nil { 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..3da712f3 100644 --- a/commands/user_create.go +++ b/commands/user_create.go @@ -1,24 +1,29 @@ 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) - if err != nil { - return err +func newUserCreateCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "create", + Short: "Create a new identity.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runUserCreate(env) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - preName, err := backend.GetUserName() + return cmd +} + +func runUserCreate(env *Env) error { + preName, err := env.backend.GetUserName() if err != nil { return err } @@ -28,7 +33,7 @@ func runUserCreate(cmd *cobra.Command, args []string) error { return err } - preEmail, err := backend.GetUserEmail() + preEmail, err := env.backend.GetUserEmail() if err != nil { return err } @@ -43,7 +48,7 @@ func runUserCreate(cmd *cobra.Command, args []string) error { return err } - id, err := backend.NewIdentityRaw(name, email, "", avatarURL, nil) + id, err := env.backend.NewIdentityRaw(name, email, "", avatarURL, nil) if err != nil { return err } @@ -53,32 +58,20 @@ func runUserCreate(cmd *cobra.Command, args []string) error { return err } - set, err := backend.IsUserIdentitySet() + set, err := env.backend.IsUserIdentitySet() if err != nil { return err } if !set { - err = backend.SetUserIdentity(id) + err = env.backend.SetUserIdentity(id) if err != nil { return err } } - _, _ = 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..05d343d6 100644 --- a/commands/user_ls.go +++ b/commands/user_ls.go @@ -8,44 +8,59 @@ import ( "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/util/colors" - "github.com/MichaelMure/git-bug/util/interrupt" ) -var ( - userLsOutputFormat string -) +type userLsOptions struct { + format string +} -func runUserLs(_ *cobra.Command, _ []string) error { - backend, err := cache.NewRepoCache(repo) - if err != nil { - return err +func newUserLsCommand() *cobra.Command { + env := newEnv() + options := userLsOptions{} + + cmd := &cobra.Command{ + Use: "ls", + Short: "List identities.", + PreRunE: loadBackend(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runUserLs(env, options) + }, } - defer backend.Close() - interrupt.RegisterCleaner(backend.Close) - ids := backend.AllIdentityIds() + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVarP(&options.format, "format", "f", "default", + "Select the output formatting style. Valid values are [default,json]") + + return cmd +} + +func runUserLs(env *Env, opts userLsOptions) error { + ids := env.backend.AllIdentityIds() var users []*cache.IdentityExcerpt for _, id := range ids { - user, err := backend.ResolveIdentityExcerpt(id) + user, err := env.backend.ResolveIdentityExcerpt(id) if err != nil { return err } 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 +69,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") -} |