1package execenv
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7
8 "github.com/spf13/cobra"
9 "github.com/vbauerster/mpb/v8"
10 "github.com/vbauerster/mpb/v8/decor"
11
12 "github.com/git-bug/git-bug/cache"
13 "github.com/git-bug/git-bug/entities/identity"
14 "github.com/git-bug/git-bug/repository"
15 "github.com/git-bug/git-bug/util/interrupt"
16)
17
18// LoadRepo is a pre-run function that load the repository for use in a command
19func LoadRepo(env *Env) func(*cobra.Command, []string) error {
20 return func(cmd *cobra.Command, args []string) error {
21 repoPath, err := getRepoPath(env)
22 if err != nil {
23 return err
24 }
25
26 // Note: we are not loading clocks here because we assume that LoadRepo is only used
27 // when we don't manipulate entities, or as a child call of LoadBackend which will
28 // read all clocks anyway.
29 env.Repo, err = repository.OpenGoGitRepo(repoPath, gitBugNamespace, nil)
30 if err == repository.ErrNotARepo {
31 return fmt.Errorf("%s must be run from within a git Repo", RootCommandName)
32 }
33 if err != nil {
34 return err
35 }
36
37 return nil
38 }
39}
40
41// LoadRepoEnsureUser is the same as LoadRepo, but also ensure that the user has configured
42// an identity. Use this pre-run function when an error after using the configured user won't
43// do.
44func LoadRepoEnsureUser(env *Env) func(*cobra.Command, []string) error {
45 return func(cmd *cobra.Command, args []string) error {
46 err := LoadRepo(env)(cmd, args)
47 if err != nil {
48 return err
49 }
50
51 _, err = identity.GetUserIdentity(env.Repo)
52 if err != nil {
53 return err
54 }
55
56 return nil
57 }
58}
59
60// LoadBackend is a pre-run function that load the repository and the Backend for use in a command
61// When using this function you also need to use CloseBackend as a post-run
62func LoadBackend(env *Env) func(*cobra.Command, []string) error {
63 return func(cmd *cobra.Command, args []string) error {
64 err := LoadRepo(env)(cmd, args)
65 if err != nil {
66 return err
67 }
68
69 var events chan cache.BuildEvent
70 env.Backend, events = cache.NewRepoCache(env.Repo)
71
72 err = CacheBuildProgressBar(env, events)
73 if err != nil {
74 return err
75 }
76
77 cleaner := func(env *Env) interrupt.CleanerFunc {
78 return func() error {
79 if env.Backend != nil {
80 err := env.Backend.Close()
81 env.Backend = nil
82 return err
83 }
84 return nil
85 }
86 }
87
88 // Cleanup properly on interrupt
89 interrupt.RegisterCleaner(cleaner(env))
90 return nil
91 }
92}
93
94// LoadBackendEnsureUser is the same as LoadBackend, but also ensure that the user has configured
95// an identity. Use this pre-run function when an error after using the configured user won't
96// do.
97func LoadBackendEnsureUser(env *Env) func(*cobra.Command, []string) error {
98 return func(cmd *cobra.Command, args []string) error {
99 err := LoadBackend(env)(cmd, args)
100 if err != nil {
101 return err
102 }
103
104 _, err = identity.GetUserIdentity(env.Repo)
105 if err != nil {
106 return err
107 }
108
109 return nil
110 }
111}
112
113// CloseBackend is a wrapper for a RunE function that will close the Backend properly
114// if it has been opened.
115// This wrapper style is necessary because a Cobra PostE function does not run if RunE return an error.
116func CloseBackend(env *Env, runE func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error {
117 return func(cmd *cobra.Command, args []string) error {
118 errRun := runE(cmd, args)
119
120 if env.Backend == nil {
121 return nil
122 }
123 err := env.Backend.Close()
124 env.Backend = nil
125
126 // prioritize the RunE error
127 if errRun != nil {
128 return errRun
129 }
130 return err
131 }
132}
133
134func CacheBuildProgressBar(env *Env, events chan cache.BuildEvent) error {
135 var progress *mpb.Progress
136 var bars = make(map[string]*mpb.Bar)
137
138 for event := range events {
139 if event.Err != nil {
140 return event.Err
141 }
142
143 if progress == nil {
144 progress = mpb.New(mpb.WithOutput(env.Err.Raw()))
145 }
146
147 switch event.Event {
148 case cache.BuildEventCacheIsBuilt:
149 env.Err.Println("Building cache... ")
150 case cache.BuildEventStarted:
151 bars[event.Typename] = progress.AddBar(-1,
152 mpb.BarRemoveOnComplete(),
153 mpb.PrependDecorators(
154 decor.Name(event.Typename, decor.WCSyncSpace),
155 decor.CountersNoUnit("%d / %d", decor.WCSyncSpace),
156 ),
157 mpb.AppendDecorators(decor.Percentage(decor.WCSyncSpace)),
158 )
159 case cache.BuildEventProgress:
160 bars[event.Typename].SetCurrent(event.Progress)
161 bars[event.Typename].SetTotal(event.Total, event.Progress == event.Total)
162 case cache.BuildEventFinished:
163 if bar := bars[event.Typename]; !bar.Completed() {
164 bar.SetTotal(0, true)
165 }
166 }
167 }
168
169 if progress != nil {
170 progress.Wait()
171 }
172
173 return nil
174}
175
176func getRepoPath(env *Env) (string, error) {
177 if len(env.RepoPath) > 0 {
178 return filepath.Join(env.RepoPath...), nil
179 }
180
181 cwd, err := os.Getwd()
182 if err != nil {
183 return "", fmt.Errorf("unable to get the current working directory: %q", err)
184 }
185 return cwd, nil
186}