aboutsummaryrefslogtreecommitdiffstats
path: root/commands/execenv/loading.go
blob: 7838ea6a21001bc5e8d1ca710560e7909c9bc7c6 (plain) (blame)
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
package execenv

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
	"github.com/vbauerster/mpb/v8"
	"github.com/vbauerster/mpb/v8/decor"

	"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"
)

// 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)

		err = CacheBuildProgressBar(env, events)
		if err != nil {
			return err
		}

		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
	}
}

func CacheBuildProgressBar(env *Env, events chan cache.BuildEvent) error {
	var progress *mpb.Progress
	var bars = make(map[string]*mpb.Bar)

	for event := range events {
		if event.Err != nil {
			return event.Err
		}

		if progress == nil {
			progress = mpb.New(mpb.WithOutput(env.Err.Raw()))
		}

		switch event.Event {
		case cache.BuildEventCacheIsBuilt:
			env.Err.Println("Building cache... ")
		case cache.BuildEventStarted:
			bars[event.Typename] = progress.AddBar(-1,
				mpb.BarRemoveOnComplete(),
				mpb.PrependDecorators(
					decor.Name(event.Typename, decor.WCSyncSpace),
					decor.CountersNoUnit("%d / %d", decor.WCSyncSpace),
				),
				mpb.AppendDecorators(decor.Percentage(decor.WCSyncSpace)),
			)
		case cache.BuildEventProgress:
			bars[event.Typename].SetCurrent(event.Progress)
			bars[event.Typename].SetTotal(event.Total, event.Progress == event.Total)
		case cache.BuildEventFinished:
			if bar := bars[event.Typename]; !bar.Completed() {
				bar.SetTotal(0, true)
			}
		}
	}

	if progress != nil {
		progress.Wait()
	}

	return nil
}