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, prefix string) (string, error) {
205 panic("implement me")
206}
207
208// PushRefs push git refs to a remote
209func (r *mockRepoData) PushRefs(remote string, prefix string) (string, error) {
210 panic("implement me")
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 // Note: this is actually incorrect as the signed data should be the full commit (+comment, +date ...)
303 // but only the tree hash work for our purpose here.
304 result.SignedData = strings.NewReader(string(c.treeHash))
305 result.Signature = strings.NewReader(c.sig)
306 }
307
308 return result, nil
309}
310
311func (r *mockRepoData) GetTreeHash(commit Hash) (Hash, error) {
312 c, ok := r.commits[commit]
313 if !ok {
314 return "", fmt.Errorf("unknown commit")
315 }
316
317 return c.treeHash, nil
318}
319
320func (r *mockRepoData) ResolveRef(ref string) (Hash, error) {
321 h, ok := r.refs[ref]
322 if !ok {
323 return "", fmt.Errorf("unknown ref")
324 }
325 return h, nil
326}
327
328func (r *mockRepoData) UpdateRef(ref string, hash Hash) error {
329 r.refs[ref] = hash
330 return nil
331}
332
333func (r *mockRepoData) RemoveRef(ref string) error {
334 delete(r.refs, ref)
335 return nil
336}
337
338func (r *mockRepoData) ListRefs(refPrefix string) ([]string, error) {
339 var keys []string
340
341 for k := range r.refs {
342 if strings.HasPrefix(k, refPrefix) {
343 keys = append(keys, k)
344 }
345 }
346
347 return keys, nil
348}
349
350func (r *mockRepoData) RefExist(ref string) (bool, error) {
351 _, exist := r.refs[ref]
352 return exist, nil
353}
354
355func (r *mockRepoData) CopyRef(source string, dest string) error {
356 hash, exist := r.refs[source]
357
358 if !exist {
359 return fmt.Errorf("Unknown ref")
360 }
361
362 r.refs[dest] = hash
363 return nil
364}
365
366func (r *mockRepoData) FindCommonAncestor(hash1 Hash, hash2 Hash) (Hash, error) {
367 ancestor1 := []Hash{hash1}
368
369 for hash1 != "" {
370 c, ok := r.commits[hash1]
371 if !ok {
372 return "", fmt.Errorf("unknown commit %v", hash1)
373 }
374 if len(c.parents) == 0 {
375 break
376 }
377 ancestor1 = append(ancestor1, c.parents[0])
378 hash1 = c.parents[0]
379 }
380
381 for {
382 for _, ancestor := range ancestor1 {
383 if ancestor == hash2 {
384 return ancestor, nil
385 }
386 }
387
388 c, ok := r.commits[hash2]
389 if !ok {
390 return "", fmt.Errorf("unknown commit %v", hash1)
391 }
392
393 if c.parents[0] == "" {
394 return "", fmt.Errorf("no ancestor found")
395 }
396
397 hash2 = c.parents[0]
398 }
399}
400
401func (r *mockRepoData) ListCommits(ref string) ([]Hash, error) {
402 return nonNativeListCommits(r, ref)
403}
404
405var _ RepoClock = &mockRepoClock{}
406
407type mockRepoClock struct {
408 mu sync.Mutex
409 clocks map[string]lamport.Clock
410}
411
412func NewMockRepoClock() *mockRepoClock {
413 return &mockRepoClock{
414 clocks: make(map[string]lamport.Clock),
415 }
416}
417
418func (r *mockRepoClock) AllClocks() (map[string]lamport.Clock, error) {
419 return r.clocks, nil
420}
421
422func (r *mockRepoClock) GetOrCreateClock(name string) (lamport.Clock, error) {
423 r.mu.Lock()
424 defer r.mu.Unlock()
425
426 if c, ok := r.clocks[name]; ok {
427 return c, nil
428 }
429
430 c := lamport.NewMemClock()
431 r.clocks[name] = c
432 return c, nil
433}
434
435func (r *mockRepoClock) Increment(name string) (lamport.Time, error) {
436 c, err := r.GetOrCreateClock(name)
437 if err != nil {
438 return lamport.Time(0), err
439 }
440 return c.Increment()
441}
442
443func (r *mockRepoClock) Witness(name string, time lamport.Time) error {
444 c, err := r.GetOrCreateClock(name)
445 if err != nil {
446 return err
447 }
448 return c.Witness(time)
449}
450
451var _ repoTest = &mockRepoTest{}
452
453type mockRepoTest struct{}
454
455func NewMockRepoTest() *mockRepoTest {
456 return &mockRepoTest{}
457}
458
459func (r *mockRepoTest) AddRemote(name string, url string) error {
460 panic("implement me")
461}
462
463func (r mockRepoTest) GetLocalRemote() string {
464 panic("implement me")
465}
466
467func (r mockRepoTest) EraseFromDisk() error {
468 // nothing to do
469 return nil
470}