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