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/go-git/go-billy/v5/memfs"
 13
 14	"github.com/git-bug/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	*mockRepoIndex
 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		mockRepoIndex:   newMockRepoIndex(),
 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/git-bug/git-bug",
119	}, nil
120}
121
122// GetPath returns an empty string for in-memory mock repos.
123func (r *mockRepoCommon) GetPath() string {
124	return ""
125}
126
127var _ RepoStorage = &mockRepoStorage{}
128
129type mockRepoStorage struct {
130	localFs LocalStorage
131}
132
133func NewMockRepoStorage() *mockRepoStorage {
134	return &mockRepoStorage{localFs: billyLocalStorage{Filesystem: memfs.New()}}
135}
136
137func (m *mockRepoStorage) LocalStorage() LocalStorage {
138	return m.localFs
139}
140
141var _ RepoIndex = &mockRepoIndex{}
142
143type mockRepoIndex struct {
144	indexesMutex sync.Mutex
145	indexes      map[string]Index
146}
147
148func newMockRepoIndex() *mockRepoIndex {
149	return &mockRepoIndex{
150		indexes: make(map[string]Index),
151	}
152}
153
154func (m *mockRepoIndex) GetIndex(name string) (Index, error) {
155	m.indexesMutex.Lock()
156	defer m.indexesMutex.Unlock()
157
158	if index, ok := m.indexes[name]; ok {
159		return index, nil
160	}
161
162	index := newIndex()
163	m.indexes[name] = index
164	return index, nil
165}
166
167var _ Index = &mockIndex{}
168
169type mockIndex map[string][]string
170
171func newIndex() *mockIndex {
172	m := make(map[string][]string)
173	return (*mockIndex)(&m)
174}
175
176func (m *mockIndex) IndexOne(id string, texts []string) error {
177	(*m)[id] = texts
178	return nil
179}
180
181func (m *mockIndex) IndexBatch() (indexer func(id string, texts []string) error, closer func() error) {
182	indexer = func(id string, texts []string) error {
183		(*m)[id] = texts
184		return nil
185	}
186	closer = func() error { return nil }
187	return indexer, closer
188}
189
190func (m *mockIndex) Search(terms []string) (ids []string, err error) {
191loop:
192	for id, texts := range *m {
193		for _, text := range texts {
194			for _, s := range strings.Fields(text) {
195				for _, term := range terms {
196					if s == term {
197						ids = append(ids, id)
198						continue loop
199					}
200				}
201			}
202		}
203	}
204	return ids, nil
205}
206
207func (m *mockIndex) DocCount() (uint64, error) {
208	return uint64(len(*m)), nil
209}
210
211func (m *mockIndex) Remove(id string) error {
212	delete(*m, id)
213	return nil
214}
215
216func (m *mockIndex) Clear() error {
217	for k, _ := range *m {
218		delete(*m, k)
219	}
220	return nil
221}
222
223func (m *mockIndex) Close() error {
224	return nil
225}
226
227var _ RepoData = &mockRepoData{}
228
229type commit struct {
230	treeHash Hash
231	parents  []Hash
232	sig      string
233}
234
235type mockRepoData struct {
236	blobs   map[Hash][]byte
237	trees   map[Hash]string
238	commits map[Hash]commit
239	refs    map[string]Hash
240}
241
242func NewMockRepoData() *mockRepoData {
243	return &mockRepoData{
244		blobs:   make(map[Hash][]byte),
245		trees:   make(map[Hash]string),
246		commits: make(map[Hash]commit),
247		refs:    make(map[string]Hash),
248	}
249}
250
251func (r *mockRepoData) FetchRefs(remote string, prefixes ...string) (string, error) {
252	panic("implement me")
253}
254
255// PushRefs push git refs to a remote
256func (r *mockRepoData) PushRefs(remote string, prefixes ...string) (string, error) {
257	panic("implement me")
258}
259
260func (r *mockRepoData) StoreData(data []byte) (Hash, error) {
261	rawHash := sha1.Sum(data)
262	hash := Hash(fmt.Sprintf("%x", rawHash))
263	r.blobs[hash] = data
264	return hash, nil
265}
266
267func (r *mockRepoData) ReadData(hash Hash) ([]byte, error) {
268	data, ok := r.blobs[hash]
269	if !ok {
270		return nil, ErrNotFound
271	}
272
273	return data, nil
274}
275
276func (r *mockRepoData) StoreTree(entries []TreeEntry) (Hash, error) {
277	buffer := prepareTreeEntries(entries)
278	rawHash := sha1.Sum(buffer.Bytes())
279	hash := Hash(fmt.Sprintf("%x", rawHash))
280	r.trees[hash] = buffer.String()
281
282	return hash, nil
283}
284
285func (r *mockRepoData) ReadTree(hash Hash) ([]TreeEntry, error) {
286	var data string
287
288	data, ok := r.trees[hash]
289
290	if !ok {
291		// Git will understand a commit hash to reach a tree
292		commit, ok := r.commits[hash]
293
294		if !ok {
295			return nil, ErrNotFound
296		}
297
298		data, ok = r.trees[commit.treeHash]
299
300		if !ok {
301			return nil, ErrNotFound
302		}
303	}
304
305	return readTreeEntries(data)
306}
307
308func (r *mockRepoData) StoreCommit(treeHash Hash, parents ...Hash) (Hash, error) {
309	return r.StoreSignedCommit(treeHash, nil, parents...)
310}
311
312func (r *mockRepoData) StoreSignedCommit(treeHash Hash, signKey *openpgp.Entity, parents ...Hash) (Hash, error) {
313	hasher := sha1.New()
314	hasher.Write([]byte(treeHash))
315	for _, parent := range parents {
316		hasher.Write([]byte(parent))
317	}
318	rawHash := hasher.Sum(nil)
319	hash := Hash(fmt.Sprintf("%x", rawHash))
320	c := commit{
321		treeHash: treeHash,
322		parents:  parents,
323	}
324	if signKey != nil {
325		// unlike go-git, we only sign the tree hash for simplicity instead of all the fields (parents ...)
326		var sig bytes.Buffer
327		if err := openpgp.DetachSign(&sig, signKey, strings.NewReader(string(treeHash)), nil); err != nil {
328			return "", err
329		}
330		c.sig = sig.String()
331	}
332	r.commits[hash] = c
333	return hash, nil
334}
335
336func (r *mockRepoData) ReadCommit(hash Hash) (Commit, error) {
337	c, ok := r.commits[hash]
338	if !ok {
339		return Commit{}, ErrNotFound
340	}
341
342	result := Commit{
343		Hash:     hash,
344		Parents:  c.parents,
345		TreeHash: c.treeHash,
346	}
347
348	if c.sig != "" {
349		// Note: this is actually incorrect as the signed data should be the full commit (+comment, +date ...)
350		// but only the tree hash work for our purpose here.
351		result.SignedData = strings.NewReader(string(c.treeHash))
352		result.Signature = strings.NewReader(c.sig)
353	}
354
355	return result, nil
356}
357
358func (r *mockRepoData) ResolveRef(ref string) (Hash, error) {
359	h, ok := r.refs[ref]
360	if !ok {
361		return "", ErrNotFound
362	}
363	return h, nil
364}
365
366func (r *mockRepoData) UpdateRef(ref string, hash Hash) error {
367	r.refs[ref] = hash
368	return nil
369}
370
371func (r *mockRepoData) RemoveRef(ref string) error {
372	delete(r.refs, ref)
373	return nil
374}
375
376func (r *mockRepoData) ListRefs(refPrefix string) ([]string, error) {
377	var keys []string
378
379	for k := range r.refs {
380		if strings.HasPrefix(k, refPrefix) {
381			keys = append(keys, k)
382		}
383	}
384
385	return keys, nil
386}
387
388func (r *mockRepoData) RefExist(ref string) (bool, error) {
389	_, exist := r.refs[ref]
390	return exist, nil
391}
392
393func (r *mockRepoData) CopyRef(source string, dest string) error {
394	hash, exist := r.refs[source]
395
396	if !exist {
397		return ErrNotFound
398	}
399
400	r.refs[dest] = hash
401	return nil
402}
403
404func (r *mockRepoData) ListCommits(ref string) ([]Hash, error) {
405	return nonNativeListCommits(r, ref)
406}
407
408var _ RepoClock = &mockRepoClock{}
409
410type mockRepoClock struct {
411	mu     sync.Mutex
412	clocks map[string]lamport.Clock
413}
414
415func NewMockRepoClock() *mockRepoClock {
416	return &mockRepoClock{
417		clocks: make(map[string]lamport.Clock),
418	}
419}
420
421func (r *mockRepoClock) AllClocks() (map[string]lamport.Clock, error) {
422	return r.clocks, nil
423}
424
425func (r *mockRepoClock) GetOrCreateClock(name string) (lamport.Clock, error) {
426	r.mu.Lock()
427	defer r.mu.Unlock()
428
429	if c, ok := r.clocks[name]; ok {
430		return c, nil
431	}
432
433	c := lamport.NewMemClock()
434	r.clocks[name] = c
435	return c, nil
436}
437
438func (r *mockRepoClock) Increment(name string) (lamport.Time, error) {
439	c, err := r.GetOrCreateClock(name)
440	if err != nil {
441		return lamport.Time(0), err
442	}
443	return c.Increment()
444}
445
446func (r *mockRepoClock) Witness(name string, time lamport.Time) error {
447	c, err := r.GetOrCreateClock(name)
448	if err != nil {
449		return err
450	}
451	return c.Witness(time)
452}
453
454var _ repoTest = &mockRepoTest{}
455
456type mockRepoTest struct{}
457
458func NewMockRepoTest() *mockRepoTest {
459	return &mockRepoTest{}
460}
461
462func (r *mockRepoTest) AddRemote(name string, url string) error {
463	panic("implement me")
464}
465
466func (r mockRepoTest) GetLocalRemote() string {
467	panic("implement me")
468}
469
470func (r mockRepoTest) EraseFromDisk() error {
471	// nothing to do
472	return nil
473}