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}