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