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