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