aboutsummaryrefslogblamecommitdiffstats
path: root/repository/mock_repo.go
blob: f869795df70ad90a88c8def67fe95537e789d9dd (plain) (tree)
1
2
3
4
5
6
7
8
9


                  
               

                     
                 
              
 
                                      
                                                 

                                             
 
                                                     

 

                               
 

                                                                     


                        
                        
                      

                      
                     
 
 
                                               
 

                              


                                                      
                                                      
                                                    

                                                    
                                                   





                                    
                               




                                          
                                             
                                             
         

 
                                                                 
                                               
                            

 
                                                           
                                                
                             

 




                                                               











                                                      
                                                         
                                             


                        







                                          
                                                        
                                     


                                                                                  
                                                         

                                      

                                                                                        
                                                          

                        
 
                                                          
                                                                  




                                                                 













                                                           
                                  
 
                           
                               
                                     

 


                                                


         
                                                              






                                             



                               
 
                          
 
                                  
 


                                      

 








































                                                                                                       
 
                                   


                  



                                
                       
                       

















                                               
                                                                                
                             
 
 
                                     
                                                                               
                             

 
                                                             
                                 
                                                



                            
                                                            
                                 
                
                                                      


                        

 
                                                                     

                                             
                                                


                                       

 

















                                                                    
         





                                                                                  

 






                                                                                                                 
                                                
                    
                                   








                                                                                                                   
         
                           
                        

 












                                                              

                                                                                                                      















                                                                         







                                                             
                                                               
                          

                  
 
                                                    



                           
                                                                     
                         
 
                               
                                                    

                                              




                        


                                                           

 

                                                                  
 

                                                

         

                           
 
 
                                                                                 
                                  





                                                                         




                                                           













                                                                         
                                       


                                                                  
                                    
         

 

                                                                
 
 
                                  
 
                           
                         








                                                       



                                                                       
                                                                              


                           


                                        
 


                                  
 




































                                                                       
package repository

import (
	"bytes"
	"crypto/sha1"
	"fmt"
	"strings"
	"sync"

	"github.com/99designs/keyring"
	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/go-git/go-billy/v5"
	"github.com/go-git/go-billy/v5/memfs"

	"github.com/MichaelMure/git-bug/util/lamport"
)

var _ ClockedRepo = &mockRepo{}
var _ TestedRepo = &mockRepo{}

// mockRepo defines an instance of Repo that can be used for testing.
type mockRepo struct {
	*mockRepoConfig
	*mockRepoKeyring
	*mockRepoCommon
	*mockRepoStorage
	*mockRepoIndex
	*mockRepoData
	*mockRepoClock
	*mockRepoTest
}

func (m *mockRepo) Close() error { return nil }

func NewMockRepo() *mockRepo {
	return &mockRepo{
		mockRepoConfig:  NewMockRepoConfig(),
		mockRepoKeyring: NewMockRepoKeyring(),
		mockRepoCommon:  NewMockRepoCommon(),
		mockRepoStorage: NewMockRepoStorage(),
		mockRepoIndex:   newMockRepoIndex(),
		mockRepoData:    NewMockRepoData(),
		mockRepoClock:   NewMockRepoClock(),
		mockRepoTest:    NewMockRepoTest(),
	}
}

var _ RepoConfig = &mockRepoConfig{}

type mockRepoConfig struct {
	localConfig  *MemConfig
	globalConfig *MemConfig
}

func NewMockRepoConfig() *mockRepoConfig {
	return &mockRepoConfig{
		localConfig:  NewMemConfig(),
		globalConfig: NewMemConfig(),
	}
}

// LocalConfig give access to the repository scoped configuration
func (r *mockRepoConfig) LocalConfig() Config {
	return r.localConfig
}

// GlobalConfig give access to the git global configuration
func (r *mockRepoConfig) GlobalConfig() Config {
	return r.globalConfig
}

// AnyConfig give access to a merged local/global configuration
func (r *mockRepoConfig) AnyConfig() ConfigRead {
	return mergeConfig(r.localConfig, r.globalConfig)
}

var _ RepoKeyring = &mockRepoKeyring{}

type mockRepoKeyring struct {
	keyring *keyring.ArrayKeyring
}

func NewMockRepoKeyring() *mockRepoKeyring {
	return &mockRepoKeyring{
		keyring: keyring.NewArrayKeyring(nil),
	}
}

// Keyring give access to a user-wide storage for secrets
func (r *mockRepoKeyring) Keyring() Keyring {
	return r.keyring
}

var _ RepoCommon = &mockRepoCommon{}

type mockRepoCommon struct{}

func NewMockRepoCommon() *mockRepoCommon {
	return &mockRepoCommon{}
}

func (r *mockRepoCommon) GetUserName() (string, error) {
	return "René Descartes", nil
}

// GetUserEmail returns the email address that the user has used to configure git.
func (r *mockRepoCommon) GetUserEmail() (string, error) {
	return "user@example.com", nil
}

// GetCoreEditor returns the name of the editor that the user has used to configure git.
func (r *mockRepoCommon) GetCoreEditor() (string, error) {
	return "vi", nil
}

// GetRemotes returns the configured remotes repositories.
func (r *mockRepoCommon) GetRemotes() (map[string]string, error) {
	return map[string]string{
		"origin": "git://github.com/MichaelMure/git-bug",
	}, nil
}

var _ RepoStorage = &mockRepoStorage{}

type mockRepoStorage struct {
	localFs billy.Filesystem
}

func NewMockRepoStorage() *mockRepoStorage {
	return &mockRepoStorage{localFs: memfs.New()}
}

func (m *mockRepoStorage) LocalStorage() billy.Filesystem {
	return m.localFs
}

var _ RepoIndex = &mockRepoIndex{}

type mockRepoIndex struct {
	indexesMutex sync.Mutex
	indexes      map[string]Index
}

func newMockRepoIndex() *mockRepoIndex {
	return &mockRepoIndex{
		indexes: make(map[string]Index),
	}
}

func (m *mockRepoIndex) GetIndex(name string) (Index, error) {
	m.indexesMutex.Lock()
	defer m.indexesMutex.Unlock()

	if index, ok := m.indexes[name]; ok {
		return index, nil
	}

	index := newIndex()
	m.indexes[name] = index
	return index, nil
}

var _ Index = &mockIndex{}

type mockIndex map[string][]string

func newIndex() *mockIndex {
	m := make(map[string][]string)
	return (*mockIndex)(&m)
}

func (m *mockIndex) IndexOne(id string, texts []string) error {
	(*m)[id] = texts
	return nil
}

func (m *mockIndex) IndexBatch() (indexer func(id string, texts []string) error, closer func() error) {
	indexer = func(id string, texts []string) error {
		(*m)[id] = texts
		return nil
	}
	closer = func() error { return nil }
	return indexer, closer
}

func (m *mockIndex) Search(terms []string) (ids []string, err error) {
loop:
	for id, texts := range *m {
		for _, text := range texts {
			for _, s := range strings.Fields(text) {
				for _, term := range terms {
					if s == term {
						ids = append(ids, id)
						continue loop
					}
				}
			}
		}
	}
	return ids, nil
}

func (m *mockIndex) DocCount() (uint64, error) {
	return uint64(len(*m)), nil
}

func (m *mockIndex) Clear() error {
	for k, _ := range *m {
		delete(*m, k)
	}
	return nil
}

func (m *mockIndex) Close() error {
	return nil
}

var _ RepoData = &mockRepoData{}

type commit struct {
	treeHash Hash
	parents  []Hash
	sig      string
}

type mockRepoData struct {
	blobs   map[Hash][]byte
	trees   map[Hash]string
	commits map[Hash]commit
	refs    map[string]Hash
}

func NewMockRepoData() *mockRepoData {
	return &mockRepoData{
		blobs:   make(map[Hash][]byte),
		trees:   make(map[Hash]string),
		commits: make(map[Hash]commit),
		refs:    make(map[string]Hash),
	}
}

func (r *mockRepoData) FetchRefs(remote string, prefix string) (string, error) {
	panic("implement me")
}

// PushRefs push git refs to a remote
func (r *mockRepoData) PushRefs(remote string, prefix string) (string, error) {
	panic("implement me")
}

func (r *mockRepoData) StoreData(data []byte) (Hash, error) {
	rawHash := sha1.Sum(data)
	hash := Hash(fmt.Sprintf("%x", rawHash))
	r.blobs[hash] = data
	return hash, nil
}

func (r *mockRepoData) ReadData(hash Hash) ([]byte, error) {
	data, ok := r.blobs[hash]
	if !ok {
		return nil, fmt.Errorf("unknown hash")
	}

	return data, nil
}

func (r *mockRepoData) StoreTree(entries []TreeEntry) (Hash, error) {
	buffer := prepareTreeEntries(entries)
	rawHash := sha1.Sum(buffer.Bytes())
	hash := Hash(fmt.Sprintf("%x", rawHash))
	r.trees[hash] = buffer.String()

	return hash, nil
}

func (r *mockRepoData) ReadTree(hash Hash) ([]TreeEntry, error) {
	var data string

	data, ok := r.trees[hash]

	if !ok {
		// Git will understand a commit hash to reach a tree
		commit, ok := r.commits[hash]

		if !ok {
			return nil, fmt.Errorf("unknown hash")
		}

		data, ok = r.trees[commit.treeHash]

		if !ok {
			return nil, fmt.Errorf("unknown hash")
		}
	}

	return readTreeEntries(data)
}

func (r *mockRepoData) StoreCommit(treeHash Hash, parents ...Hash) (Hash, error) {
	return r.StoreSignedCommit(treeHash, nil, parents...)
}

func (r *mockRepoData) StoreSignedCommit(treeHash Hash, signKey *openpgp.Entity, parents ...Hash) (Hash, error) {
	hasher := sha1.New()
	hasher.Write([]byte(treeHash))
	for _, parent := range parents {
		hasher.Write([]byte(parent))
	}
	rawHash := hasher.Sum(nil)
	hash := Hash(fmt.Sprintf("%x", rawHash))
	c := commit{
		treeHash: treeHash,
		parents:  parents,
	}
	if signKey != nil {
		// unlike go-git, we only sign the tree hash for simplicity instead of all the fields (parents ...)
		var sig bytes.Buffer
		if err := openpgp.DetachSign(&sig, signKey, strings.NewReader(string(treeHash)), nil); err != nil {
			return "", err
		}
		c.sig = sig.String()
	}
	r.commits[hash] = c
	return hash, nil
}

func (r *mockRepoData) ReadCommit(hash Hash) (Commit, error) {
	c, ok := r.commits[hash]
	if !ok {
		return Commit{}, fmt.Errorf("unknown commit")
	}

	result := Commit{
		Hash:     hash,
		Parents:  c.parents,
		TreeHash: c.treeHash,
	}

	if c.sig != "" {
		// Note: this is actually incorrect as the signed data should be the full commit (+comment, +date ...)
		// but only the tree hash work for our purpose here.
		result.SignedData = strings.NewReader(string(c.treeHash))
		result.Signature = strings.NewReader(c.sig)
	}

	return result, nil
}

func (r *mockRepoData) GetTreeHash(commit Hash) (Hash, error) {
	c, ok := r.commits[commit]
	if !ok {
		return "", fmt.Errorf("unknown commit")
	}

	return c.treeHash, nil
}

func (r *mockRepoData) ResolveRef(ref string) (Hash, error) {
	h, ok := r.refs[ref]
	if !ok {
		return "", fmt.Errorf("unknown ref")
	}
	return h, nil
}

func (r *mockRepoData) UpdateRef(ref string, hash Hash) error {
	r.refs[ref] = hash
	return nil
}

func (r *mockRepoData) RemoveRef(ref string) error {
	delete(r.refs, ref)
	return nil
}

func (r *mockRepoData) ListRefs(refPrefix string) ([]string, error) {
	var keys []string

	for k := range r.refs {
		if strings.HasPrefix(k, refPrefix) {
			keys = append(keys, k)
		}
	}

	return keys, nil
}

func (r *mockRepoData) RefExist(ref string) (bool, error) {
	_, exist := r.refs[ref]
	return exist, nil
}

func (r *mockRepoData) CopyRef(source string, dest string) error {
	hash, exist := r.refs[source]

	if !exist {
		return fmt.Errorf("Unknown ref")
	}

	r.refs[dest] = hash
	return nil
}

func (r *mockRepoData) FindCommonAncestor(hash1 Hash, hash2 Hash) (Hash, error) {
	ancestor1 := []Hash{hash1}

	for hash1 != "" {
		c, ok := r.commits[hash1]
		if !ok {
			return "", fmt.Errorf("unknown commit %v", hash1)
		}
		if len(c.parents) == 0 {
			break
		}
		ancestor1 = append(ancestor1, c.parents[0])
		hash1 = c.parents[0]
	}

	for {
		for _, ancestor := range ancestor1 {
			if ancestor == hash2 {
				return ancestor, nil
			}
		}

		c, ok := r.commits[hash2]
		if !ok {
			return "", fmt.Errorf("unknown commit %v", hash1)
		}

		if c.parents[0] == "" {
			return "", fmt.Errorf("no ancestor found")
		}

		hash2 = c.parents[0]
	}
}

func (r *mockRepoData) ListCommits(ref string) ([]Hash, error) {
	return nonNativeListCommits(r, ref)
}

var _ RepoClock = &mockRepoClock{}

type mockRepoClock struct {
	mu     sync.Mutex
	clocks map[string]lamport.Clock
}

func NewMockRepoClock() *mockRepoClock {
	return &mockRepoClock{
		clocks: make(map[string]lamport.Clock),
	}
}

func (r *mockRepoClock) AllClocks() (map[string]lamport.Clock, error) {
	return r.clocks, nil
}

func (r *mockRepoClock) GetOrCreateClock(name string) (lamport.Clock, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	if c, ok := r.clocks[name]; ok {
		return c, nil
	}

	c := lamport.NewMemClock()
	r.clocks[name] = c
	return c, nil
}

func (r *mockRepoClock) Increment(name string) (lamport.Time, error) {
	c, err := r.GetOrCreateClock(name)
	if err != nil {
		return lamport.Time(0), err
	}
	return c.Increment()
}

func (r *mockRepoClock) Witness(name string, time lamport.Time) error {
	c, err := r.GetOrCreateClock(name)
	if err != nil {
		return err
	}
	return c.Witness(time)
}

var _ repoTest = &mockRepoTest{}

type mockRepoTest struct{}

func NewMockRepoTest() *mockRepoTest {
	return &mockRepoTest{}
}

func (r *mockRepoTest) AddRemote(name string, url string) error {
	panic("implement me")
}

func (r mockRepoTest) GetLocalRemote() string {
	panic("implement me")
}

func (r mockRepoTest) EraseFromDisk() error {
	// nothing to do
	return nil
}