1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
package execenv
import (
"encoding/json"
"fmt"
"io"
"os"
"github.com/spf13/cobra"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/interrupt"
)
const RootCommandName = "git-bug"
const gitBugNamespace = "git-bug"
// 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 interface {
io.Writer
Printf(format string, a ...interface{})
Print(a ...interface{})
Println(a ...interface{})
PrintJSON(v interface{}) error
// String returns what have been written in the output before, as a string.
// This only works in test scenario.
String() string
// Bytes returns what have been written in the output before, as []byte.
// This only works in test scenario.
Bytes() []byte
// Reset clear what has been recorded as written in the output before.
// This only works in test scenario.
Reset()
}
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...)
}
func (o out) PrintJSON(v interface{}) error {
raw, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err
}
o.Println(string(raw))
return nil
}
func (o out) String() string {
panic("only work with a test env")
}
func (o out) Bytes() []byte {
panic("only work with a test env")
}
func (o out) Reset() {
panic("only work with a test env")
}
// 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)
}
// Note: we are not loading clocks here because we assume that LoadRepo is only used
// when we don't manipulate entities, or as a child call of LoadBackend which will
// read all clocks anyway.
env.Repo, err = repository.OpenGoGitRepo(cwd, gitBugNamespace, nil)
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
}
var events chan cache.BuildEvent
env.Backend, events = cache.NewRepoCache(env.Repo)
for event := range events {
if event.Err != nil {
return event.Err
}
switch event.Event {
case cache.BuildEventCacheIsBuilt:
env.Err.Println("Building cache... ")
case cache.BuildEventStarted:
env.Err.Printf("[%s] started\n", event.Typename)
case cache.BuildEventFinished:
env.Err.Printf("[%s] done\n", event.Typename)
}
}
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 := LoadBackend(env)(cmd, args)
if err != nil {
return err
}
_, err = identity.GetUserIdentity(env.Repo)
if err != nil {
return err
}
return nil
}
}
// CloseBackend is a wrapper for a RunE function that will close the Backend properly
// if it has been opened.
// This wrapper style is necessary because a Cobra PostE function does not run if RunE return an error.
func CloseBackend(env *Env, runE func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
errRun := runE(cmd, args)
if env.Backend == nil {
return nil
}
err := env.Backend.Close()
env.Backend = nil
// prioritize the RunE error
if errRun != nil {
return errRun
}
return err
}
}
|