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}