mock_repo.go

  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}