env.go

  1package commands
  2
  3import (
  4	"fmt"
  5	"io"
  6	"os"
  7
  8	"github.com/spf13/cobra"
  9
 10	"github.com/MichaelMure/git-bug/bug"
 11	"github.com/MichaelMure/git-bug/cache"
 12	"github.com/MichaelMure/git-bug/identity"
 13	"github.com/MichaelMure/git-bug/repository"
 14	"github.com/MichaelMure/git-bug/util/interrupt"
 15)
 16
 17const GitBugNamespace = "git-bug"
 18
 19// Env is the environment of a command
 20type Env struct {
 21	repo    repository.ClockedRepo
 22	backend *cache.RepoCache
 23	out     out
 24	err     out
 25}
 26
 27func newEnv() *Env {
 28	return &Env{
 29		repo: nil,
 30		out:  out{Writer: os.Stdout},
 31		err:  out{Writer: os.Stderr},
 32	}
 33}
 34
 35type out struct {
 36	io.Writer
 37}
 38
 39func (o out) Printf(format string, a ...interface{}) {
 40	_, _ = fmt.Fprintf(o, format, a...)
 41}
 42
 43func (o out) Print(a ...interface{}) {
 44	_, _ = fmt.Fprint(o, a...)
 45}
 46
 47func (o out) Println(a ...interface{}) {
 48	_, _ = fmt.Fprintln(o, a...)
 49}
 50
 51// getCWD returns the current working directory.  Normal operation simply
 52// returns the working directory reported by the OS (as an OS-compatible
 53// filepath.)  During tests, temporary repositories are created outside
 54// the test execution's CWD.  In this case, it's possible to provide an
 55// alternate CWD filepath by adding a value to the command's context
 56// with the key "cwd".
 57func getCWD(cmd *cobra.Command) (string, error) {
 58	cwd, ok := cmd.Context().Value("cwd").(string)
 59	if cwd != "" && ok {
 60		return cwd, nil
 61	}
 62
 63	cwd, err := os.Getwd()
 64	if err != nil {
 65		return "", fmt.Errorf("unable to get the current working directory: %q", err)
 66	}
 67
 68	return cwd, nil
 69}
 70
 71// loadRepo is a pre-run function that load the repository for use in a command
 72func loadRepo(env *Env) func(*cobra.Command, []string) error {
 73	return func(cmd *cobra.Command, args []string) error {
 74		cwd, err := getCWD(cmd)
 75		if err != nil {
 76			return err
 77		}
 78
 79		env.repo, err = repository.OpenGoGitRepo(cwd, GitBugNamespace, []repository.ClockLoader{bug.ClockLoader})
 80		if err == repository.ErrNotARepo {
 81			return fmt.Errorf("%s must be run from within a git repo", rootCommandName)
 82		}
 83
 84		if err != nil {
 85			return err
 86		}
 87
 88		return nil
 89	}
 90}
 91
 92// loadRepoEnsureUser is the same as loadRepo, but also ensure that the user has configured
 93// an identity. Use this pre-run function when an error after using the configured user won't
 94// do.
 95func loadRepoEnsureUser(env *Env) func(*cobra.Command, []string) error {
 96	return func(cmd *cobra.Command, args []string) error {
 97		err := loadRepo(env)(cmd, args)
 98		if err != nil {
 99			return err
100		}
101
102		_, err = identity.GetUserIdentity(env.repo)
103		if err != nil {
104			return err
105		}
106
107		return nil
108	}
109}
110
111// loadBackend is a pre-run function that load the repository and the backend for use in a command
112// When using this function you also need to use closeBackend as a post-run
113func loadBackend(env *Env) func(*cobra.Command, []string) error {
114	return func(cmd *cobra.Command, args []string) error {
115		err := loadRepo(env)(cmd, args)
116		if err != nil {
117			return err
118		}
119
120		env.backend, err = cache.NewRepoCache(env.repo)
121		if err != nil {
122			return err
123		}
124
125		cleaner := func(env *Env) interrupt.CleanerFunc {
126			return func() error {
127				if env.backend != nil {
128					err := env.backend.Close()
129					env.backend = nil
130					return err
131				}
132				return nil
133			}
134		}
135
136		// Cleanup properly on interrupt
137		interrupt.RegisterCleaner(cleaner(env))
138		return nil
139	}
140}
141
142// loadBackendEnsureUser is the same as loadBackend, but also ensure that the user has configured
143// an identity. Use this pre-run function when an error after using the configured user won't
144// do.
145func loadBackendEnsureUser(env *Env) func(*cobra.Command, []string) error {
146	return func(cmd *cobra.Command, args []string) error {
147		err := loadBackend(env)(cmd, args)
148		if err != nil {
149			return err
150		}
151
152		_, err = identity.GetUserIdentity(env.repo)
153		if err != nil {
154			return err
155		}
156
157		return nil
158	}
159}
160
161// closeBackend is a wrapper for a RunE function that will close the backend properly
162// if it has been opened.
163// This wrapper style is necessary because a Cobra PostE function does not run if RunE return an error.
164func closeBackend(env *Env, runE func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error {
165	return func(cmd *cobra.Command, args []string) error {
166		env.err = out{Writer: cmd.ErrOrStderr()}
167		env.out = out{Writer: cmd.OutOrStdout()}
168
169		errRun := runE(cmd, args)
170
171		if env.backend == nil {
172			return nil
173		}
174		err := env.backend.Close()
175		env.backend = nil
176
177		// prioritize the RunE error
178		if errRun != nil {
179			return errRun
180		}
181		return err
182	}
183}