1package repository
2
3import (
4 "bytes"
5 "crypto/sha1"
6 "fmt"
7 "strings"
8 "sync"
9
10 "github.com/99designs/keyring"
11 "github.com/blevesearch/bleve"
12 "github.com/go-git/go-billy/v5"
13 "github.com/go-git/go-billy/v5/memfs"
14 "golang.org/x/crypto/openpgp"
15
16 "github.com/MichaelMure/git-bug/util/lamport"
17)
18
19var _ ClockedRepo = &mockRepo{}
20var _ TestedRepo = &mockRepo{}
21
22// mockRepo defines an instance of Repo that can be used for testing.
23type mockRepo struct {
24 *mockRepoConfig
25 *mockRepoKeyring
26 *mockRepoCommon
27 *mockRepoStorage
28 *mockRepoBleve
29 *mockRepoData
30 *mockRepoClock
31 *mockRepoTest
32}
33
34func (m *mockRepo) Close() error { return nil }
35
36func NewMockRepo() *mockRepo {
37 return &mockRepo{
38 mockRepoConfig: NewMockRepoConfig(),
39 mockRepoKeyring: NewMockRepoKeyring(),
40 mockRepoCommon: NewMockRepoCommon(),
41 mockRepoStorage: NewMockRepoStorage(),
42 mockRepoBleve: newMockRepoBleve(),
43 mockRepoData: NewMockRepoData(),
44 mockRepoClock: NewMockRepoClock(),
45 mockRepoTest: NewMockRepoTest(),
46 }
47}
48
49var _ RepoConfig = &mockRepoConfig{}
50
51type mockRepoConfig struct {
52 localConfig *MemConfig
53 globalConfig *MemConfig
54}
55
56func NewMockRepoConfig() *mockRepoConfig {
57 return &mockRepoConfig{
58 localConfig: NewMemConfig(),
59 globalConfig: NewMemConfig(),
60 }
61}
62
63// LocalConfig give access to the repository scoped configuration
64func (r *mockRepoConfig) LocalConfig() Config {
65 return r.localConfig
66}
67
68// GlobalConfig give access to the git global configuration
69func (r *mockRepoConfig) GlobalConfig() Config {
70 return r.globalConfig
71}
72
73// AnyConfig give access to a merged local/global configuration
74func (r *mockRepoConfig) AnyConfig() ConfigRead {
75 return mergeConfig(r.localConfig, r.globalConfig)
76}
77
78var _ RepoKeyring = &mockRepoKeyring{}
79
80type mockRepoKeyring struct {
81 keyring *keyring.ArrayKeyring
82}
83
84func NewMockRepoKeyring() *mockRepoKeyring {
85 return &mockRepoKeyring{
86 keyring: keyring.NewArrayKeyring(nil),
87 }
88}
89
90// Keyring give access to a user-wide storage for secrets
91func (r *mockRepoKeyring) Keyring() Keyring {
92 return r.keyring
93}
94
95var _ RepoCommon = &mockRepoCommon{}
96
97type mockRepoCommon struct{}
98
99func NewMockRepoCommon() *mockRepoCommon {
100 return &mockRepoCommon{}
101}
102
103func (r *mockRepoCommon) GetUserName() (string, error) {
104 return "René Descartes", nil
105}
106
107// GetUserEmail returns the email address that the user has used to configure git.
108func (r *mockRepoCommon) GetUserEmail() (string, error) {
109 return "user@example.com", nil
110}
111
112// GetCoreEditor returns the name of the editor that the user has used to configure git.
113func (r *mockRepoCommon) GetCoreEditor() (string, error) {
114 return "vi", nil
115}
116
117// GetRemotes returns the configured remotes repositories.
118func (r *mockRepoCommon) GetRemotes() (map[string]string, error) {
119 return map[string]string{
120 "origin": "git://github.com/MichaelMure/git-bug",
121 }, nil
122}
123
124var _ RepoStorage = &mockRepoStorage{}
125
126type mockRepoStorage struct {
127 localFs billy.Filesystem
128}
129
130func NewMockRepoStorage() *mockRepoStorage {
131 return &mockRepoStorage{localFs: memfs.New()}
132}
133
134func (m *mockRepoStorage) LocalStorage() billy.Filesystem {
135 return m.localFs
136}
137
138var _ RepoBleve = &mockRepoBleve{}
139
140type mockRepoBleve struct {
141 indexesMutex sync.Mutex
142 indexes map[string]bleve.Index
143}
144
145func newMockRepoBleve() *mockRepoBleve {
146 return &mockRepoBleve{
147 indexes: make(map[string]bleve.Index),
148 }
149}
150
151func (m *mockRepoBleve) GetBleveIndex(name string) (bleve.Index, error) {
152 m.indexesMutex.Lock()
153 defer m.indexesMutex.Unlock()
154
155 if index, ok := m.indexes[name]; ok {
156 return index, nil
157 }
158
159 mapping := bleve.NewIndexMapping()
160 mapping.DefaultAnalyzer = "en"
161
162 index, err := bleve.NewMemOnly(mapping)
163 if err != nil {
164 return nil, err
165 }
166
167 m.indexes[name] = index
168
169 return index, nil
170}
171
172func (m *mockRepoBleve) ClearBleveIndex(name string) error {
173 m.indexesMutex.Lock()
174 defer m.indexesMutex.Unlock()
175
176 delete(m.indexes, name)
177 return nil
178}
179
180var _ RepoData = &mockRepoData{}
181
182type commit struct {
183 treeHash Hash
184 parents []Hash
185 sig string
186}
187
188type mockRepoData struct {
189 blobs map[Hash][]byte
190 trees map[Hash]string
191 commits map[Hash]commit
192 refs map[string]Hash
193}
194
195func NewMockRepoData() *mockRepoData {
196 return &mockRepoData{
197 blobs: make(map[Hash][]byte),
198 trees: make(map[Hash]string),
199 commits: make(map[Hash]commit),
200 refs: make(map[string]Hash),
201 }
202}
203
204func (r *mockRepoData) FetchRefs(remote string, refSpec string) (string, error) {
205 return "", nil
206}
207
208// PushRefs push git refs to a remote
209func (r *mockRepoData) PushRefs(remote string, refSpec string) (string, error) {
210 return "", nil
211}
212
213func (r *mockRepoData) StoreData(data []byte) (Hash, error) {
214 rawHash := sha1.Sum(data)
215 hash := Hash(fmt.Sprintf("%x", rawHash))
216 r.blobs[hash] = data
217 return hash, nil
218}
219
220func (r *mockRepoData) ReadData(hash Hash) ([]byte, error) {
221 data, ok := r.blobs[hash]
222 if !ok {
223 return nil, fmt.Errorf("unknown hash")
224 }
225
226 return data, nil
227}
228
229func (r *mockRepoData) StoreTree(entries []TreeEntry) (Hash, error) {
230 buffer := prepareTreeEntries(entries)
231 rawHash := sha1.Sum(buffer.Bytes())
232 hash := Hash(fmt.Sprintf("%x", rawHash))
233 r.trees[hash] = buffer.String()
234
235 return hash, nil
236}
237
238func (r *mockRepoData) ReadTree(hash Hash) ([]TreeEntry, error) {
239 var data string
240
241 data, ok := r.trees[hash]
242
243 if !ok {
244 // Git will understand a commit hash to reach a tree
245 commit, ok := r.commits[hash]
246
247 if !ok {
248 return nil, fmt.Errorf("unknown hash")
249 }
250
251 data, ok = r.trees[commit.treeHash]
252
253 if !ok {
254 return nil, fmt.Errorf("unknown hash")
255 }
256 }
257
258 return readTreeEntries(data)
259}
260
261func (r *mockRepoData) StoreCommit(treeHash Hash, parents ...Hash) (Hash, error) {
262 return r.StoreSignedCommit(treeHash, nil, parents...)
263}
264
265func (r *mockRepoData) StoreSignedCommit(treeHash Hash, signKey *openpgp.Entity, parents ...Hash) (Hash, error) {
266 hasher := sha1.New()
267 hasher.Write([]byte(treeHash))
268 for _, parent := range parents {
269 hasher.Write([]byte(parent))
270 }
271 rawHash := hasher.Sum(nil)
272 hash := Hash(fmt.Sprintf("%x", rawHash))
273 c := commit{
274 treeHash: treeHash,
275 parents: parents,
276 }
277 if signKey != nil {
278 // unlike go-git, we only sign the tree hash for simplicity instead of all the fields (parents ...)
279 var sig bytes.Buffer
280 if err := openpgp.DetachSign(&sig, signKey, strings.NewReader(string(treeHash)), nil); err != nil {
281 return "", err
282 }
283 c.sig = sig.String()
284 }
285 r.commits[hash] = c
286 return hash, nil
287}
288
289func (r *mockRepoData) ReadCommit(hash Hash) (Commit, error) {
290 c, ok := r.commits[hash]
291 if !ok {
292 return Commit{}, fmt.Errorf("unknown commit")
293 }
294
295 result := Commit{
296 Hash: hash,
297 Parents: c.parents,
298 TreeHash: c.treeHash,
299 }
300
301 if c.sig != "" {
302 result.SignedData = strings.NewReader(string(c.treeHash))
303 result.Signature = strings.NewReader(c.sig)
304 }
305
306 return result, nil
307}
308
309func (r *mockRepoData) GetTreeHash(commit Hash) (Hash, error) {
310 c, ok := r.commits[commit]
311 if !ok {
312 return "", fmt.Errorf("unknown commit")
313 }
314
315 return c.treeHash, nil
316}
317
318func (r *mockRepoData) ResolveRef(ref string) (Hash, error) {
319 h, ok := r.refs[ref]
320 if !ok {
321 return "", fmt.Errorf("unknown ref")
322 }
323 return h, nil
324}
325
326func (r *mockRepoData) UpdateRef(ref string, hash Hash) error {
327 r.refs[ref] = hash
328 return nil
329}
330
331func (r *mockRepoData) RemoveRef(ref string) error {
332 delete(r.refs, ref)
333 return nil
334}
335
336func (r *mockRepoData) ListRefs(refPrefix string) ([]string, error) {
337 var keys []string
338
339 for k := range r.refs {
340 if strings.HasPrefix(k, refPrefix) {
341 keys = append(keys, k)
342 }
343 }
344
345 return keys, nil
346}
347
348func (r *mockRepoData) RefExist(ref string) (bool, error) {
349 _, exist := r.refs[ref]
350 return exist, nil
351}
352
353func (r *mockRepoData) CopyRef(source string, dest string) error {
354 hash, exist := r.refs[source]
355
356 if !exist {
357 return fmt.Errorf("Unknown ref")
358 }
359
360 r.refs[dest] = hash
361 return nil
362}
363
364func (r *mockRepoData) FindCommonAncestor(hash1 Hash, hash2 Hash) (Hash, error) {
365 ancestor1 := []Hash{hash1}
366
367 for hash1 != "" {
368 c, ok := r.commits[hash1]
369 if !ok {
370 return "", fmt.Errorf("unknown commit %v", hash1)
371 }
372 if len(c.parents) == 0 {
373 break
374 }
375 ancestor1 = append(ancestor1, c.parents[0])
376 hash1 = c.parents[0]
377 }
378
379 for {
380 for _, ancestor := range ancestor1 {
381 if ancestor == hash2 {
382 return ancestor, nil
383 }
384 }
385
386 c, ok := r.commits[hash2]
387 if !ok {
388 return "", fmt.Errorf("unknown commit %v", hash1)
389 }
390
391 if c.parents[0] == "" {
392 return "", fmt.Errorf("no ancestor found")
393 }
394
395 hash2 = c.parents[0]
396 }
397}
398
399func (r *mockRepoData) ListCommits(ref string) ([]Hash, error) {
400 return nonNativeListCommits(r, ref)
401}
402
403var _ RepoClock = &mockRepoClock{}
404
405type mockRepoClock struct {
406 mu sync.Mutex
407 clocks map[string]lamport.Clock
408}
409
410func NewMockRepoClock() *mockRepoClock {
411 return &mockRepoClock{
412 clocks: make(map[string]lamport.Clock),
413 }
414}
415
416func (r *mockRepoClock) AllClocks() (map[string]lamport.Clock, error) {
417 return r.clocks, nil
418}
419
420func (r *mockRepoClock) GetOrCreateClock(name string) (lamport.Clock, error) {
421 r.mu.Lock()
422 defer r.mu.Unlock()
423
424 if c, ok := r.clocks[name]; ok {
425 return c, nil
426 }
427
428 c := lamport.NewMemClock()
429 r.clocks[name] = c
430 return c, nil
431}
432
433func (r *mockRepoClock) Increment(name string) (lamport.Time, error) {
434 c, err := r.GetOrCreateClock(name)
435 if err != nil {
436 return lamport.Time(0), err
437 }
438 return c.Increment()
439}
440
441func (r *mockRepoClock) Witness(name string, time lamport.Time) error {
442 c, err := r.GetOrCreateClock(name)
443 if err != nil {
444 return err
445 }
446 return c.Witness(time)
447}
448
449var _ repoTest = &mockRepoTest{}
450
451type mockRepoTest struct{}
452
453func NewMockRepoTest() *mockRepoTest {
454 return &mockRepoTest{}
455}
456
457func (r *mockRepoTest) AddRemote(name string, url string) error {
458 panic("implement me")
459}
460
461func (r mockRepoTest) GetLocalRemote() string {
462 panic("implement me")
463}
464
465func (r mockRepoTest) EraseFromDisk() error {
466 // nothing to do
467 return nil
468}