mock_repo.go

  1package repository
  2
  3import (
  4	"bytes"
  5	"crypto/sha1"
  6	"fmt"
  7	"strings"
  8	"sync"
  9
 10	"github.com/99designs/keyring"
 11	"github.com/ProtonMail/go-crypto/openpgp"
 12	"github.com/blevesearch/bleve"
 13	"github.com/go-git/go-billy/v5"
 14	"github.com/go-git/go-billy/v5/memfs"
 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}