commands: open and close the backend in a single place, simplify commands

Michael Muré created

Change summary

commands/add.go                  |  21 ++----
commands/bridge.go               |  18 +---
commands/bridge_auth.go          |  18 +---
commands/bridge_auth_addtoken.go |  20 ++---
commands/bridge_auth_show.go     |  16 +---
commands/bridge_configure.go     |  14 +--
commands/bridge_pull.go          |  20 ++---
commands/bridge_push.go          |  20 ++---
commands/bridge_rm.go            |  18 +---
commands/comment.go              |  18 +---
commands/comment_add.go          |  20 +----
commands/deselect.go             |  14 ---
commands/env.go                  | 116 +++++++++++++++++++++++++++++++++
commands/label.go                |  18 +---
commands/label_add.go            |  18 +---
commands/label_rm.go             |  18 +---
commands/ls-id.go                |  19 +----
commands/ls-labels.go            |  15 ---
commands/ls.go                   |  48 ++++++-------
commands/pull.go                 |  20 +----
commands/push.go                 |  19 +----
commands/root.go                 |  44 ------------
commands/select.go               |  16 +---
commands/show.go                 |  18 +---
commands/status.go               |  18 +---
commands/status_close.go         |  18 +---
commands/status_open.go          |  18 +---
commands/termui.go               |  20 +----
commands/title.go                |  18 +---
commands/title_edit.go           |  18 +---
commands/user.go                 |  20 ++---
commands/user_adopt.go           |  23 ++----
commands/user_create.go          |  26 ++----
commands/user_ls.go              |  19 +---
go.sum                           |  10 ++
35 files changed, 314 insertions(+), 480 deletions(-)

Detailed changes

commands/add.go 🔗

@@ -3,9 +3,7 @@ package commands
 import (
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/input"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 type addOptions struct {
@@ -19,9 +17,10 @@ func newAddCommand() *cobra.Command {
 	options := addOptions{}
 
 	cmd := &cobra.Command{
-		Use:     "add",
-		Short:   "Create a new bug.",
-		PreRunE: loadRepoEnsureUser(env),
+		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)
 		},
@@ -41,13 +40,7 @@ func newAddCommand() *cobra.Command {
 }
 
 func runAdd(env *Env, opts addOptions) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
+	var err error
 	if opts.messageFile != "" && opts.message == "" {
 		opts.title, opts.message, err = input.BugCreateFileInput(opts.messageFile)
 		if err != nil {
@@ -56,7 +49,7 @@ func runAdd(env *Env, opts addOptions) error {
 	}
 
 	if opts.messageFile == "" && (opts.message == "" || opts.title == "") {
-		opts.title, opts.message, err = input.BugCreateEditorInput(backend, opts.title, opts.message)
+		opts.title, opts.message, err = input.BugCreateEditorInput(env.backend, opts.title, opts.message)
 
 		if err == input.ErrEmptyTitle {
 			env.out.Println("Empty title, aborting.")
@@ -67,7 +60,7 @@ func runAdd(env *Env, opts addOptions) error {
 		}
 	}
 
-	b, _, err := backend.NewBug(opts.title, opts.message)
+	b, _, err := env.backend.NewBug(opts.title, opts.message)
 	if err != nil {
 		return err
 	}

commands/bridge.go 🔗

@@ -4,17 +4,16 @@ import (
 	"github.com/spf13/cobra"
 
 	"github.com/MichaelMure/git-bug/bridge"
-	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newBridgeCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "bridge",
-		Short:   "Configure and use bridges to other bug trackers.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -31,14 +30,7 @@ func newBridgeCommand() *cobra.Command {
 }
 
 func runBridge(env *Env) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	configured, err := bridge.ConfiguredBridges(backend)
+	configured, err := bridge.ConfiguredBridges(env.backend)
 	if err != nil {
 		return err
 	}

commands/bridge_auth.go 🔗

@@ -9,18 +9,17 @@ 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 newBridgeAuthCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "auth",
-		Short:   "List all known bridge authentication credentials.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -35,14 +34,7 @@ func newBridgeAuthCommand() *cobra.Command {
 }
 
 func runBridgeAuth(env *Env) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	creds, err := auth.List(backend)
+	creds, err := auth.List(env.backend)
 	if err != nil {
 		return err
 	}

commands/bridge_auth_addtoken.go 🔗

@@ -14,7 +14,6 @@ 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"
 )
 
 type bridgeAuthAddTokenOptions struct {
@@ -28,9 +27,10 @@ func newBridgeAuthAddTokenCommand() *cobra.Command {
 	options := bridgeAuthAddTokenOptions{}
 
 	cmd := &cobra.Command{
-		Use:     "add-token [<token>]",
-		Short:   "Store a new token",
-		PreRunE: loadRepoEnsureUser(env),
+		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)
 		},
@@ -64,13 +64,6 @@ func runBridgeAuthAddToken(env *Env, opts bridgeAuthAddTokenOptions, args []stri
 		return fmt.Errorf("flag --login is required")
 	}
 
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
 	if !core.TargetExist(opts.target) {
 		return fmt.Errorf("unknown target")
 	}
@@ -93,11 +86,12 @@ func runBridgeAuthAddToken(env *Env, opts bridgeAuthAddTokenOptions, args []stri
 	}
 
 	var user *cache.IdentityCache
+	var err error
 
 	if opts.user == "" {
-		user, err = backend.GetUserIdentity()
+		user, err = env.backend.GetUserIdentity()
 	} else {
-		user, err = backend.ResolveIdentityPrefix(opts.user)
+		user, err = env.backend.ResolveIdentityPrefix(opts.user)
 	}
 	if err != nil {
 		return err

commands/bridge_auth_show.go 🔗

@@ -9,17 +9,16 @@ 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 newBridgeAuthShow() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "show",
-		Short:   "Display an authentication credential.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -30,13 +29,6 @@ func newBridgeAuthShow() *cobra.Command {
 }
 
 func runBridgeAuthShow(env *Env, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
 	cred, err := auth.LoadWithPrefix(env.repo, args[0])
 	if err != nil {
 		return err

commands/bridge_configure.go 🔗

@@ -12,9 +12,7 @@ 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"
 )
 
 type bridgeConfigureOptions struct {
@@ -86,7 +84,8 @@ git bug bridge configure \
     --target=github \
     --url=https://github.com/michaelmure/git-bug \
     --token=$(TOKEN)`,
-		PreRunE: loadRepo(env),
+		PreRunE:  loadBackend(env),
+		PostRunE: closeBackend(env),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runBridgeConfigure(env, options)
 		},
@@ -111,12 +110,7 @@ git bug bridge configure \
 }
 
 func runBridgeConfigure(env *Env, opts bridgeConfigureOptions) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
+	var err error
 
 	if (opts.tokenStdin || opts.token != "" || opts.params.CredPrefix != "") &&
 		(opts.name == "" || opts.target == "") {
@@ -156,7 +150,7 @@ func runBridgeConfigure(env *Env, opts bridgeConfigureOptions) error {
 		}
 	}
 
-	b, err := bridge.NewBridge(backend, opts.target, opts.name)
+	b, err := bridge.NewBridge(env.backend, opts.target, opts.name)
 	if err != nil {
 		return err
 	}

commands/bridge_pull.go 🔗

@@ -13,7 +13,6 @@ 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"
 )
 
@@ -27,9 +26,10 @@ func newBridgePullCommand() *cobra.Command {
 	options := bridgePullOptions{}
 
 	cmd := &cobra.Command{
-		Use:     "pull [<name>]",
-		Short:   "Pull updates.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -50,19 +50,13 @@ func runBridgePull(env *Env, opts bridgePullOptions, args []string) error {
 		return fmt.Errorf("only one of --no-resume and --since flags should be used")
 	}
 
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	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 {

commands/bridge_push.go 🔗

@@ -10,7 +10,6 @@ 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"
 )
 
@@ -18,9 +17,10 @@ func newBridgePushCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "push [<name>]",
-		Short:   "Push updates.",
-		PreRunE: loadRepoEnsureUser(env),
+		Use:      "push [<name>]",
+		Short:    "Push updates.",
+		PreRunE:  loadBackendEnsureUser(env),
+		PostRunE: closeBackend(env),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runBridgePush(env, args)
 		},
@@ -31,19 +31,13 @@ func newBridgePushCommand() *cobra.Command {
 }
 
 func runBridgePush(env *Env, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	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 {

commands/bridge_rm.go 🔗

@@ -4,17 +4,16 @@ import (
 	"github.com/spf13/cobra"
 
 	"github.com/MichaelMure/git-bug/bridge"
-	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newBridgeRm() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "rm <name>",
-		Short:   "Delete a configured bridge.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -25,14 +24,7 @@ func newBridgeRm() *cobra.Command {
 }
 
 func runBridgeRm(env *Env, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	err = bridge.RemoveBridge(backend, args[0])
+	err := bridge.RemoveBridge(env.backend, args[0])
 	if err != nil {
 		return err
 	}

commands/comment.go 🔗

@@ -4,19 +4,18 @@ import (
 	text "github.com/MichaelMure/go-term-text"
 	"github.com/spf13/cobra"
 
-	"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 newCommentCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "comment [<id>]",
-		Short:   "Display or add comments to a bug.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -28,14 +27,7 @@ func newCommentCommand() *cobra.Command {
 }
 
 func runComment(env *Env, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	b, args, err := _select.ResolveBug(backend, args)
+	b, args, err := _select.ResolveBug(env.backend, args)
 	if err != nil {
 		return err
 	}

commands/comment_add.go 🔗

@@ -3,10 +3,8 @@ package commands
 import (
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/commands/select"
 	"github.com/MichaelMure/git-bug/input"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 type commentAddOptions struct {
@@ -19,9 +17,10 @@ func newCommentAddCommand() *cobra.Command {
 	options := commentAddOptions{}
 
 	cmd := &cobra.Command{
-		Use:     "add [<id>]",
-		Short:   "Add a new comment to a bug.",
-		PreRunE: loadRepoEnsureUser(env),
+		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)
 		},
@@ -40,14 +39,7 @@ func newCommentAddCommand() *cobra.Command {
 }
 
 func runCommentAdd(env *Env, opts commentAddOptions, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	b, args, err := _select.ResolveBug(backend, args)
+	b, args, err := _select.ResolveBug(env.backend, args)
 	if err != nil {
 		return err
 	}
@@ -60,7 +52,7 @@ func runCommentAdd(env *Env, opts commentAddOptions, args []string) error {
 	}
 
 	if opts.messageFile == "" && opts.message == "" {
-		opts.message, err = input.BugCommentEditorInput(backend, "")
+		opts.message, err = input.BugCommentEditorInput(env.backend, "")
 		if err == input.ErrEmptyMessage {
 			env.err.Println("Empty message, aborting.")
 			return nil

commands/deselect.go 🔗

@@ -3,9 +3,7 @@ package commands
 import (
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/commands/select"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newDeselectCommand() *cobra.Command {
@@ -19,7 +17,8 @@ git bug comment
 git bug status
 git bug deselect
 `,
-		PreRunE: loadRepo(env),
+		PreRunE:  loadBackend(env),
+		PostRunE: closeBackend(env),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runDeselect(env)
 		},
@@ -29,14 +28,7 @@ git bug deselect
 }
 
 func runDeselect(env *Env) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	err = _select.Clear(backend)
+	err := _select.Clear(env.backend)
 	if err != nil {
 		return err
 	}

commands/env.go 🔗

@@ -5,14 +5,21 @@ import (
 	"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
-	out  out
-	err  out
+	repo    repository.ClockedRepo
+	backend *cache.RepoCache
+	out     out
+	err     out
 }
 
 func newEnv() *Env {
@@ -38,3 +45,106 @@ func (o out) Print(a ...interface{}) {
 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
+	}
+}

commands/label.go 🔗

@@ -3,18 +3,17 @@ package commands
 import (
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/commands/select"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newLabelCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "label [<id>]",
-		Short:   "Display, add or remove labels to/from a bug.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -27,14 +26,7 @@ func newLabelCommand() *cobra.Command {
 }
 
 func runLabel(env *Env, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	b, args, err := _select.ResolveBug(backend, args)
+	b, args, err := _select.ResolveBug(env.backend, args)
 	if err != nil {
 		return err
 	}

commands/label_add.go 🔗

@@ -3,18 +3,17 @@ package commands
 import (
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/commands/select"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newLabelAddCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "add [<id>] <label>[...]",
-		Short:   "Add a label to a bug.",
-		PreRunE: loadRepoEnsureUser(env),
+		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)
 		},
@@ -24,14 +23,7 @@ func newLabelAddCommand() *cobra.Command {
 }
 
 func runLabelAdd(env *Env, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	b, args, err := _select.ResolveBug(backend, args)
+	b, args, err := _select.ResolveBug(env.backend, args)
 	if err != nil {
 		return err
 	}

commands/label_rm.go 🔗

@@ -3,18 +3,17 @@ package commands
 import (
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/commands/select"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newLabelRmCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "rm [<id>] <label>[...]",
-		Short:   "Remove a label from a bug.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -24,14 +23,7 @@ func newLabelRmCommand() *cobra.Command {
 }
 
 func runLabelRm(env *Env, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	b, args, err := _select.ResolveBug(backend, args)
+	b, args, err := _select.ResolveBug(env.backend, args)
 	if err != nil {
 		return err
 	}

commands/ls-id.go 🔗

@@ -2,18 +2,16 @@ package commands
 
 import (
 	"github.com/spf13/cobra"
-
-	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newLsIdCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "ls-id [<prefix>]",
-		Short:   "List bug identifiers.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -23,19 +21,12 @@ func newLsIdCommand() *cobra.Command {
 }
 
 func runLsId(env *Env, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
 	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) {
 			env.out.Println(id)
 		}

commands/ls-labels.go 🔗

@@ -2,9 +2,6 @@ package commands
 
 import (
 	"github.com/spf13/cobra"
-
-	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newLsLabelCommand() *cobra.Command {
@@ -16,7 +13,8 @@ func newLsLabelCommand() *cobra.Command {
 		Long: `List valid labels.
 
 Note: in the future, a proper label policy could be implemented where valid labels are defined in a configuration file. Until that, the default behavior is to return the list of labels already used.`,
-		PreRunE: loadRepo(env),
+		PreRunE:  loadBackend(env),
+		PostRunE: closeBackend(env),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runLsLabel(env)
 		},
@@ -26,14 +24,7 @@ Note: in the future, a proper label policy could be implemented where valid labe
 }
 
 func runLsLabel(env *Env) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	labels := backend.ValidLabels()
+	labels := env.backend.ValidLabels()
 
 	for _, l := range labels {
 		env.out.Println(l)

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,7 +13,6 @@ 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"
 )
 
 type lsOptions struct {
@@ -41,7 +41,8 @@ git bug ls status:open sort:edit-desc
 List closed bugs sorted by creation with flags:
 git bug ls --status closed --by creation
 `,
-		PreRunE: loadRepo(env),
+		PreRunE:  loadBackend(env),
+		PostRunE: closeBackend(env),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runLs(env, options, args)
 		},
@@ -75,14 +76,11 @@ git bug ls --status closed --by creation
 }
 
 func runLs(env *Env, opts lsOptions, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
+	time.Sleep(5 * time.Second)
 
 	var q *query.Query
+	var err error
+
 	if len(args) >= 1 {
 		q, err = query.Parse(strings.Join(args, " "))
 
@@ -97,11 +95,11 @@ func runLs(env *Env, opts lsOptions, args []string) error {
 		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
 		}
@@ -110,13 +108,13 @@ func runLs(env *Env, opts lsOptions, args []string) error {
 
 	switch opts.outputFormat {
 	case "org-mode":
-		return lsOrgmodeFormatter(env, backend, bugExcerpt)
+		return lsOrgmodeFormatter(env, bugExcerpt)
 	case "plain":
-		return lsPlainFormatter(env, backend, bugExcerpt)
+		return lsPlainFormatter(env, bugExcerpt)
 	case "json":
-		return lsJsonFormatter(env, backend, bugExcerpt)
+		return lsJsonFormatter(env, bugExcerpt)
 	case "default":
-		return lsDefaultFormatter(env, backend, bugExcerpt)
+		return lsDefaultFormatter(env, bugExcerpt)
 	default:
 		return fmt.Errorf("unknown format %s", opts.outputFormat)
 	}
@@ -139,7 +137,7 @@ type JSONBugExcerpt struct {
 	Metadata map[string]string `json:"metadata"`
 }
 
-func lsJsonFormatter(env *Env, 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{
@@ -155,7 +153,7 @@ func lsJsonFormatter(env *Env, backend *cache.RepoCache, bugExcerpts []*cache.Bu
 		}
 
 		if b.AuthorId != "" {
-			author, err := backend.ResolveIdentityExcerpt(b.AuthorId)
+			author, err := env.backend.ResolveIdentityExcerpt(b.AuthorId)
 			if err != nil {
 				return err
 			}
@@ -166,7 +164,7 @@ func lsJsonFormatter(env *Env, backend *cache.RepoCache, bugExcerpts []*cache.Bu
 
 		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
 			}
@@ -175,7 +173,7 @@ func lsJsonFormatter(env *Env, backend *cache.RepoCache, bugExcerpts []*cache.Bu
 
 		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
 			}
@@ -189,11 +187,11 @@ func lsJsonFormatter(env *Env, backend *cache.RepoCache, bugExcerpts []*cache.Bu
 	return nil
 }
 
-func lsDefaultFormatter(env *Env, 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
 			}
@@ -231,14 +229,14 @@ func lsDefaultFormatter(env *Env, backend *cache.RepoCache, bugExcerpts []*cache
 	return nil
 }
 
-func lsPlainFormatter(env *Env, _ *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error {
+func lsPlainFormatter(env *Env, bugExcerpts []*cache.BugExcerpt) error {
 	for _, b := range bugExcerpts {
 		env.out.Printf("%s [%s] %s\n", b.Id.Human(), b.Status, b.Title)
 	}
 	return nil
 }
 
-func lsOrgmodeFormatter(env *Env, backend *cache.RepoCache, bugExcerpts []*cache.BugExcerpt) error {
+func lsOrgmodeFormatter(env *Env, bugExcerpts []*cache.BugExcerpt) error {
 	env.out.Println("+TODO: OPEN | CLOSED")
 
 	for _, b := range bugExcerpts {
@@ -253,7 +251,7 @@ func lsOrgmodeFormatter(env *Env, backend *cache.RepoCache, bugExcerpts []*cache
 
 		var name string
 		if b.AuthorId != "" {
-			author, err := backend.ResolveIdentityExcerpt(b.AuthorId)
+			author, err := env.backend.ResolveIdentityExcerpt(b.AuthorId)
 			if err != nil {
 				return err
 			}
@@ -283,7 +281,7 @@ func lsOrgmodeFormatter(env *Env, backend *cache.RepoCache, bugExcerpts []*cache
 
 		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
 			}
@@ -296,7 +294,7 @@ func lsOrgmodeFormatter(env *Env, backend *cache.RepoCache, bugExcerpts []*cache
 
 		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
 			}

commands/pull.go 🔗

@@ -5,18 +5,17 @@ import (
 
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/entity"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newPullCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "pull [<remote>]",
-		Short:   "Pull bugs update from a git remote.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -35,16 +34,9 @@ func runPull(env *Env, args []string) error {
 		remote = args[0]
 	}
 
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
 	env.out.Println("Fetching remote ...")
 
-	stdout, err := backend.Fetch(remote)
+	stdout, err := env.backend.Fetch(remote)
 	if err != nil {
 		return err
 	}
@@ -53,7 +45,7 @@ func runPull(env *Env, args []string) error {
 
 	env.out.Println("Merging data ...")
 
-	for result := range backend.MergeAll(remote) {
+	for result := range env.backend.MergeAll(remote) {
 		if result.Err != nil {
 			env.err.Println(result.Err)
 		}

commands/push.go 🔗

@@ -4,18 +4,16 @@ import (
 	"errors"
 
 	"github.com/spf13/cobra"
-
-	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newPushCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "push [<remote>]",
-		Short:   "Push bugs update to a git remote.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -34,14 +32,7 @@ func runPush(env *Env, args []string) error {
 		remote = args[0]
 	}
 
-	backend, err := cache.NewRepoCache(env.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
 	}

commands/root.go 🔗

@@ -6,10 +6,6 @@ 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"
@@ -92,43 +88,3 @@ func Execute() {
 		os.Exit(1)
 	}
 }
-
-// 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
-	}
-}

commands/select.go 🔗

@@ -5,9 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/commands/select"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newSelectCommand() *cobra.Command {
@@ -29,7 +27,8 @@ instead of
 
 The complementary command is "git bug deselect" performing the opposite operation.
 `,
-		PreRunE: loadRepo(env),
+		PreRunE:  loadBackend(env),
+		PostRunE: closeBackend(env),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runSelect(env, args)
 		},
@@ -43,21 +42,14 @@ func runSelect(env *Env, args []string) error {
 		return errors.New("You must provide a bug id")
 	}
 
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	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
 	}

commands/show.go 🔗

@@ -9,10 +9,8 @@ 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"
 )
 
 type showOptions struct {
@@ -25,9 +23,10 @@ func newShowCommand() *cobra.Command {
 	options := showOptions{}
 
 	cmd := &cobra.Command{
-		Use:     "show [<id>]",
-		Short:   "Display the details of a bug.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -45,14 +44,7 @@ func newShowCommand() *cobra.Command {
 }
 
 func runShow(env *Env, opts showOptions, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	b, args, err := _select.ResolveBug(backend, args)
+	b, args, err := _select.ResolveBug(env.backend, args)
 	if err != nil {
 		return err
 	}

commands/status.go 🔗

@@ -3,18 +3,17 @@ package commands
 import (
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/commands/select"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newStatusCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "status [<id>]",
-		Short:   "Display or change a bug status.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -27,14 +26,7 @@ func newStatusCommand() *cobra.Command {
 }
 
 func runStatus(env *Env, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	b, args, err := _select.ResolveBug(backend, args)
+	b, args, err := _select.ResolveBug(env.backend, args)
 	if err != nil {
 		return err
 	}

commands/status_close.go 🔗

@@ -3,18 +3,17 @@ package commands
 import (
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/commands/select"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newStatusCloseCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "close [<id>]",
-		Short:   "Mark a bug as closed.",
-		PreRunE: loadRepoEnsureUser(env),
+		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)
 		},
@@ -24,14 +23,7 @@ func newStatusCloseCommand() *cobra.Command {
 }
 
 func runStatusClose(env *Env, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	b, args, err := _select.ResolveBug(backend, args)
+	b, args, err := _select.ResolveBug(env.backend, args)
 	if err != nil {
 		return err
 	}

commands/status_open.go 🔗

@@ -3,18 +3,17 @@ package commands
 import (
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/commands/select"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newStatusOpenCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "open [<id>]",
-		Short:   "Mark a bug as open.",
-		PreRunE: loadRepoEnsureUser(env),
+		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)
 		},
@@ -24,14 +23,7 @@ func newStatusOpenCommand() *cobra.Command {
 }
 
 func runStatusOpen(env *Env, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	b, args, err := _select.ResolveBug(backend, args)
+	b, args, err := _select.ResolveBug(env.backend, args)
 	if err != nil {
 		return err
 	}

commands/termui.go 🔗

@@ -3,19 +3,18 @@ package commands
 import (
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/termui"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newTermUICommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "termui",
-		Aliases: []string{"tui"},
-		Short:   "Launch the terminal UI.",
-		PreRunE: loadRepoEnsureUser(env),
+		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)
 		},
@@ -25,12 +24,5 @@ func newTermUICommand() *cobra.Command {
 }
 
 func runTermUI(env *Env) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	return termui.Run(backend)
+	return termui.Run(env.backend)
 }

commands/title.go 🔗

@@ -3,18 +3,17 @@ package commands
 import (
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/commands/select"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newTitleCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "title [<id>]",
-		Short:   "Display or change a title of a bug.",
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -26,14 +25,7 @@ func newTitleCommand() *cobra.Command {
 }
 
 func runTitle(env *Env, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	b, args, err := _select.ResolveBug(backend, args)
+	b, args, err := _select.ResolveBug(env.backend, args)
 	if err != nil {
 		return err
 	}

commands/title_edit.go 🔗

@@ -3,10 +3,8 @@ package commands
 import (
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/commands/select"
 	"github.com/MichaelMure/git-bug/input"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 type titleEditOptions struct {
@@ -18,9 +16,10 @@ func newTitleEditCommand() *cobra.Command {
 	options := titleEditOptions{}
 
 	cmd := &cobra.Command{
-		Use:     "edit [<id>]",
-		Short:   "Edit a title of a bug.",
-		PreRunE: loadRepoEnsureUser(env),
+		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)
 		},
@@ -37,14 +36,7 @@ func newTitleEditCommand() *cobra.Command {
 }
 
 func runTitleEdit(env *Env, opts titleEditOptions, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	b, args, err := _select.ResolveBug(backend, args)
+	b, args, err := _select.ResolveBug(env.backend, args)
 	if err != nil {
 		return err
 	}

commands/user.go 🔗

@@ -7,7 +7,6 @@ import (
 	"github.com/spf13/cobra"
 
 	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 type userOptions struct {
@@ -19,9 +18,10 @@ func newUserCommand() *cobra.Command {
 	options := userOptions{}
 
 	cmd := &cobra.Command{
-		Use:     "user [<user-id>]",
-		Short:   "Display or change the user identity.",
-		PreRunE: loadRepoEnsureUser(env),
+		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)
 		},
@@ -41,22 +41,16 @@ func newUserCommand() *cobra.Command {
 }
 
 func runUser(env *Env, opts userOptions, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
 	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 {

commands/user_adopt.go 🔗

@@ -2,19 +2,17 @@ package commands
 
 import (
 	"github.com/spf13/cobra"
-
-	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newUserAdoptCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "adopt <user-id>",
-		Short:   "Adopt an existing identity as your own.",
-		Args:    cobra.ExactArgs(1),
-		PreRunE: loadRepo(env),
+		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)
 		},
@@ -24,21 +22,14 @@ func newUserAdoptCommand() *cobra.Command {
 }
 
 func runUserAdopt(env *Env, args []string) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
 	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
 	}

commands/user_create.go 🔗

@@ -3,18 +3,17 @@ package commands
 import (
 	"github.com/spf13/cobra"
 
-	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/input"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 func newUserCreateCommand() *cobra.Command {
 	env := newEnv()
 
 	cmd := &cobra.Command{
-		Use:     "create",
-		Short:   "Create a new identity.",
-		PreRunE: loadRepo(env),
+		Use:      "create",
+		Short:    "Create a new identity.",
+		PreRunE:  loadBackend(env),
+		PostRunE: closeBackend(env),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runUserCreate(env)
 		},
@@ -24,14 +23,7 @@ func newUserCreateCommand() *cobra.Command {
 }
 
 func runUserCreate(env *Env) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	preName, err := backend.GetUserName()
+	preName, err := env.backend.GetUserName()
 	if err != nil {
 		return err
 	}
@@ -41,7 +33,7 @@ func runUserCreate(env *Env) error {
 		return err
 	}
 
-	preEmail, err := backend.GetUserEmail()
+	preEmail, err := env.backend.GetUserEmail()
 	if err != nil {
 		return err
 	}
@@ -56,7 +48,7 @@ func runUserCreate(env *Env) 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
 	}
@@ -66,13 +58,13 @@ func runUserCreate(env *Env) 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
 		}

commands/user_ls.go 🔗

@@ -8,7 +8,6 @@ import (
 
 	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/util/colors"
-	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 type userLsOptions struct {
@@ -20,9 +19,10 @@ func newUserLsCommand() *cobra.Command {
 	options := userLsOptions{}
 
 	cmd := &cobra.Command{
-		Use:     "ls",
-		Short:   "List identities.",
-		PreRunE: loadRepo(env),
+		Use:      "ls",
+		Short:    "List identities.",
+		PreRunE:  loadBackend(env),
+		PostRunE: closeBackend(env),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runUserLs(env, options)
 		},
@@ -38,17 +38,10 @@ func newUserLsCommand() *cobra.Command {
 }
 
 func runUserLs(env *Env, opts userLsOptions) error {
-	backend, err := cache.NewRepoCache(env.repo)
-	if err != nil {
-		return err
-	}
-	defer backend.Close()
-	interrupt.RegisterCleaner(backend.Close)
-
-	ids := backend.AllIdentityIds()
+	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
 		}

go.sum 🔗

@@ -28,6 +28,7 @@ github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986/go.mod h1:1Q
 github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0=
 github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
@@ -36,11 +37,15 @@ github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9 h1:a1zrFsLFac2xoM
 github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
+github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/corpix/uarand v0.1.1 h1:RMr1TWc9F4n5jiPDzFHtmaUXLKLNUFK0SgCLo4BhX/U=
 github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU=
@@ -139,6 +144,7 @@ github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuuj
 github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@@ -160,12 +166,16 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=