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