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
51func getCWD(cmd *cobra.Command, args []string) (string, error) {
52 cwd, ok := cmd.Context().Value("cwd").(string)
53 if cwd != "" && ok {
54 return cwd, nil
55 }
56
57 cwd, err := os.Getwd()
58 if err != nil {
59 return "", fmt.Errorf("unable to get the current working directory: %q", err)
60 }
61
62 return cwd, nil
63}
64
65// loadRepo is a pre-run function that load the repository for use in a command
66func loadRepo(env *Env) func(*cobra.Command, []string) error {
67 return func(cmd *cobra.Command, args []string) error {
68 cwd, err := getCWD(cmd, args)
69 if err != nil {
70 return err
71 }
72
73 env.repo, err = repository.OpenGoGitRepo(cwd, gitBugNamespace, []repository.ClockLoader{bug.ClockLoader})
74 if err == repository.ErrNotARepo {
75 return fmt.Errorf("%s must be run from within a git repo", rootCommandName)
76 }
77
78 if err != nil {
79 return err
80 }
81
82 return nil
83 }
84}
85
86// loadRepoEnsureUser is the same as loadRepo, but also ensure that the user has configured
87// an identity. Use this pre-run function when an error after using the configured user won't
88// do.
89func loadRepoEnsureUser(env *Env) func(*cobra.Command, []string) error {
90 return func(cmd *cobra.Command, args []string) error {
91 err := loadRepo(env)(cmd, args)
92 if err != nil {
93 return err
94 }
95
96 _, err = identity.GetUserIdentity(env.repo)
97 if err != nil {
98 return err
99 }
100
101 return nil
102 }
103}
104
105// loadBackend is a pre-run function that load the repository and the backend for use in a command
106// When using this function you also need to use closeBackend as a post-run
107func loadBackend(env *Env) func(*cobra.Command, []string) error {
108 return func(cmd *cobra.Command, args []string) error {
109 err := loadRepo(env)(cmd, args)
110 if err != nil {
111 return err
112 }
113
114 env.backend, err = cache.NewRepoCache(env.repo)
115 if err != nil {
116 return err
117 }
118
119 cleaner := func(env *Env) interrupt.CleanerFunc {
120 return func() error {
121 if env.backend != nil {
122 err := env.backend.Close()
123 env.backend = nil
124 return err
125 }
126 return nil
127 }
128 }
129
130 // Cleanup properly on interrupt
131 interrupt.RegisterCleaner(cleaner(env))
132 return nil
133 }
134}
135
136// loadBackendEnsureUser is the same as loadBackend, but also ensure that the user has configured
137// an identity. Use this pre-run function when an error after using the configured user won't
138// do.
139func loadBackendEnsureUser(env *Env) func(*cobra.Command, []string) error {
140 return func(cmd *cobra.Command, args []string) error {
141 err := loadBackend(env)(cmd, args)
142 if err != nil {
143 return err
144 }
145
146 _, err = identity.GetUserIdentity(env.repo)
147 if err != nil {
148 return err
149 }
150
151 return nil
152 }
153}
154
155// closeBackend is a wrapper for a RunE function that will close the backend properly
156// if it has been opened.
157// This wrapper style is necessary because a Cobra PostE function does not run if RunE return an error.
158func closeBackend(env *Env, runE func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error {
159 return func(cmd *cobra.Command, args []string) error {
160 env.err = out{Writer: cmd.ErrOrStderr()}
161 env.out = out{Writer: cmd.OutOrStdout()}
162
163 errRun := runE(cmd, args)
164
165 if env.backend == nil {
166 return nil
167 }
168 err := env.backend.Close()
169 env.backend = nil
170
171 // prioritize the RunE error
172 if errRun != nil {
173 return errRun
174 }
175 return err
176 }
177}