2020-06-26 20:25:17 +03:00
package repository
import (
"bytes"
"fmt"
"io/ioutil"
"os"
2020-09-29 23:00:35 +03:00
"os/exec"
2020-06-26 20:25:17 +03:00
"path/filepath"
2020-10-25 01:24:26 +03:00
"sort"
2020-09-08 15:31:40 +03:00
"strings"
2020-06-26 20:25:17 +03:00
"sync"
2020-09-08 06:00:36 +03:00
"time"
2020-06-26 20:25:17 +03:00
2020-12-08 16:42:13 +03:00
"github.com/blevesearch/bleve"
2020-12-01 23:19:23 +03:00
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/osfs"
2020-06-26 20:25:17 +03:00
gogit "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
2020-09-02 15:48:28 +03:00
"github.com/go-git/go-git/v5/plumbing/filemode"
"github.com/go-git/go-git/v5/plumbing/object"
2020-06-26 20:25:17 +03:00
"github.com/MichaelMure/git-bug/util/lamport"
)
2020-11-08 19:54:28 +03:00
const clockPath = "clocks"
2020-06-26 20:25:17 +03:00
var _ ClockedRepo = & GoGitRepo { }
2020-12-01 23:19:23 +03:00
var _ TestedRepo = & GoGitRepo { }
2020-06-26 20:25:17 +03:00
type GoGitRepo struct {
2020-12-05 23:40:48 +03:00
r * gogit . Repository
path string
2020-06-26 20:25:17 +03:00
clocksMutex sync . Mutex
clocks map [ string ] lamport . Clock
2020-07-27 01:14:01 +03:00
2020-12-08 16:42:13 +03:00
indexesMutex sync . Mutex
indexes map [ string ] bleve . Index
2020-12-05 05:08:54 +03:00
keyring Keyring
localStorage billy . Filesystem
2020-06-26 20:25:17 +03:00
}
2020-12-05 23:40:48 +03:00
// OpenGoGitRepo open an already existing repo at the given path
2020-12-05 05:08:54 +03:00
func OpenGoGitRepo ( path string , clockLoaders [ ] ClockLoader ) ( * GoGitRepo , error ) {
2020-06-26 20:25:17 +03:00
path , err := detectGitPath ( path )
if err != nil {
return nil , err
}
r , err := gogit . PlainOpen ( path )
if err != nil {
return nil , err
}
2020-07-27 01:14:01 +03:00
k , err := defaultKeyring ( )
if err != nil {
return nil , err
}
2020-06-26 20:25:17 +03:00
repo := & GoGitRepo {
2020-12-05 05:08:54 +03:00
r : r ,
path : path ,
clocks : make ( map [ string ] lamport . Clock ) ,
2020-12-08 16:42:13 +03:00
indexes : make ( map [ string ] bleve . Index ) ,
2020-12-05 05:08:54 +03:00
keyring : k ,
localStorage : osfs . New ( filepath . Join ( path , "git-bug" ) ) ,
2020-06-26 20:25:17 +03:00
}
for _ , loader := range clockLoaders {
allExist := true
for _ , name := range loader . Clocks {
if _ , err := repo . getClock ( name ) ; err != nil {
allExist = false
}
}
if ! allExist {
err = loader . Witnesser ( repo )
if err != nil {
return nil , err
}
}
}
return repo , nil
}
2020-12-05 05:08:54 +03:00
// InitGoGitRepo create a new empty git repo at the given path
func InitGoGitRepo ( path string ) ( * GoGitRepo , error ) {
r , err := gogit . PlainInit ( path , false )
if err != nil {
return nil , err
}
k , err := defaultKeyring ( )
if err != nil {
return nil , err
}
return & GoGitRepo {
r : r ,
path : filepath . Join ( path , ".git" ) ,
clocks : make ( map [ string ] lamport . Clock ) ,
2020-12-08 16:42:13 +03:00
indexes : make ( map [ string ] bleve . Index ) ,
2020-12-05 05:08:54 +03:00
keyring : k ,
localStorage : osfs . New ( filepath . Join ( path , ".git" , "git-bug" ) ) ,
} , nil
}
// InitBareGoGitRepo create a new --bare empty git repo at the given path
func InitBareGoGitRepo ( path string ) ( * GoGitRepo , error ) {
r , err := gogit . PlainInit ( path , true )
if err != nil {
return nil , err
}
k , err := defaultKeyring ( )
if err != nil {
return nil , err
}
return & GoGitRepo {
r : r ,
path : path ,
clocks : make ( map [ string ] lamport . Clock ) ,
2020-12-08 16:42:13 +03:00
indexes : make ( map [ string ] bleve . Index ) ,
2020-12-05 05:08:54 +03:00
keyring : k ,
localStorage : osfs . New ( filepath . Join ( path , "git-bug" ) ) ,
} , nil
}
2020-06-26 20:25:17 +03:00
func detectGitPath ( path string ) ( string , error ) {
// normalize the path
path , err := filepath . Abs ( path )
if err != nil {
return "" , err
}
for {
2020-12-05 05:08:54 +03:00
fi , err := os . Stat ( filepath . Join ( path , ".git" ) )
2020-06-26 20:25:17 +03:00
if err == nil {
if ! fi . IsDir ( ) {
return "" , fmt . Errorf ( ".git exist but is not a directory" )
}
2020-12-05 05:08:54 +03:00
return filepath . Join ( path , ".git" ) , nil
2020-06-26 20:25:17 +03:00
}
if ! os . IsNotExist ( err ) {
// unknown error
return "" , err
}
// detect bare repo
ok , err := isGitDir ( path )
if err != nil {
return "" , err
}
if ok {
return path , nil
}
if parent := filepath . Dir ( path ) ; parent == path {
return "" , fmt . Errorf ( ".git not found" )
} else {
path = parent
}
}
}
func isGitDir ( path string ) ( bool , error ) {
markers := [ ] string { "HEAD" , "objects" , "refs" }
for _ , marker := range markers {
2020-12-05 05:08:54 +03:00
_ , err := os . Stat ( filepath . Join ( path , marker ) )
2020-06-26 20:25:17 +03:00
if err == nil {
continue
}
if ! os . IsNotExist ( err ) {
// unknown error
return false , err
} else {
return false , nil
}
}
return true , nil
}
2020-12-08 16:42:13 +03:00
func ( repo * GoGitRepo ) Close ( ) error {
var firstErr error
for _ , index := range repo . indexes {
err := index . Close ( )
if err != nil && firstErr == nil {
firstErr = err
}
}
return firstErr
}
2020-09-27 01:54:14 +03:00
// LocalConfig give access to the repository scoped configuration
2020-06-26 20:25:17 +03:00
func ( repo * GoGitRepo ) LocalConfig ( ) Config {
2020-09-27 21:31:09 +03:00
return newGoGitLocalConfig ( repo . r )
2020-06-26 20:25:17 +03:00
}
2020-09-27 01:54:14 +03:00
// GlobalConfig give access to the global scoped configuration
2020-06-26 20:25:17 +03:00
func ( repo * GoGitRepo ) GlobalConfig ( ) Config {
2020-12-05 05:08:54 +03:00
return newGoGitGlobalConfig ( )
2020-06-26 20:25:17 +03:00
}
2020-09-27 01:54:14 +03:00
// AnyConfig give access to a merged local/global configuration
func ( repo * GoGitRepo ) AnyConfig ( ) ConfigRead {
return mergeConfig ( repo . LocalConfig ( ) , repo . GlobalConfig ( ) )
}
// Keyring give access to a user-wide storage for secrets
2020-07-27 01:14:01 +03:00
func ( repo * GoGitRepo ) Keyring ( ) Keyring {
return repo . keyring
}
2020-06-26 20:25:17 +03:00
// GetUserName returns the name the the user has used to configure git
func ( repo * GoGitRepo ) GetUserName ( ) ( string , error ) {
2020-10-31 00:20:19 +03:00
return repo . AnyConfig ( ) . ReadString ( "user.name" )
2020-06-26 20:25:17 +03:00
}
// GetUserEmail returns the email address that the user has used to configure git.
func ( repo * GoGitRepo ) GetUserEmail ( ) ( string , error ) {
2020-10-31 00:20:19 +03:00
return repo . AnyConfig ( ) . ReadString ( "user.email" )
2020-06-26 20:25:17 +03:00
}
// GetCoreEditor returns the name of the editor that the user has used to configure git.
func ( repo * GoGitRepo ) GetCoreEditor ( ) ( string , error ) {
2020-09-28 00:14:51 +03:00
// See https://git-scm.com/docs/git-var
// The order of preference is the $GIT_EDITOR environment variable, then core.editor configuration, then $VISUAL, then $EDITOR, and then the default chosen at compile time, which is usually vi.
2020-06-26 20:25:17 +03:00
2020-09-28 00:14:51 +03:00
if val , ok := os . LookupEnv ( "GIT_EDITOR" ) ; ok {
return val , nil
}
val , err := repo . AnyConfig ( ) . ReadString ( "core.editor" )
if err == nil && val != "" {
return val , nil
}
if err != nil && err != ErrNoConfigEntry {
return "" , err
}
if val , ok := os . LookupEnv ( "VISUAL" ) ; ok {
return val , nil
}
if val , ok := os . LookupEnv ( "EDITOR" ) ; ok {
return val , nil
}
2020-09-29 23:00:35 +03:00
priorities := [ ] string {
"editor" ,
"nano" ,
"vim" ,
"vi" ,
"emacs" ,
}
for _ , cmd := range priorities {
if _ , err = exec . LookPath ( cmd ) ; err == nil {
return cmd , nil
}
}
return "ed" , nil
2020-06-26 20:25:17 +03:00
}
// GetRemotes returns the configured remotes repositories.
func ( repo * GoGitRepo ) GetRemotes ( ) ( map [ string ] string , error ) {
cfg , err := repo . r . Config ( )
if err != nil {
return nil , err
}
result := make ( map [ string ] string , len ( cfg . Remotes ) )
for name , remote := range cfg . Remotes {
if len ( remote . URLs ) > 0 {
result [ name ] = remote . URLs [ 0 ]
}
}
return result , nil
}
2020-12-01 23:19:23 +03:00
// LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug
func ( repo * GoGitRepo ) LocalStorage ( ) billy . Filesystem {
2020-12-05 05:08:54 +03:00
return repo . localStorage
2020-12-01 23:19:23 +03:00
}
2020-12-08 16:42:13 +03:00
// GetBleveIndex return a bleve.Index that can be used to index documents
func ( repo * GoGitRepo ) GetBleveIndex ( name string ) ( bleve . Index , error ) {
repo . indexesMutex . Lock ( )
defer repo . indexesMutex . Unlock ( )
if index , ok := repo . indexes [ name ] ; ok {
return index , nil
}
path := filepath . Join ( repo . path , "git-bug" , "indexes" , name )
index , err := bleve . Open ( path )
if err == nil {
repo . indexes [ name ] = index
return index , nil
}
err = os . MkdirAll ( path , os . ModePerm )
if err != nil {
return nil , err
}
mapping := bleve . NewIndexMapping ( )
mapping . DefaultAnalyzer = "en"
index , err = bleve . New ( path , mapping )
if err != nil {
return nil , err
}
repo . indexes [ name ] = index
return index , nil
}
// ClearBleveIndex will wipe the given index
func ( repo * GoGitRepo ) ClearBleveIndex ( name string ) error {
repo . indexesMutex . Lock ( )
defer repo . indexesMutex . Unlock ( )
path := filepath . Join ( repo . path , "indexes" , name )
err := os . RemoveAll ( path )
if err != nil {
return err
}
if index , ok := repo . indexes [ name ] ; ok {
err = index . Close ( )
if err != nil {
return err
}
delete ( repo . indexes , name )
}
return nil
}
2020-06-26 20:25:17 +03:00
// FetchRefs fetch git refs from a remote
func ( repo * GoGitRepo ) FetchRefs ( remote string , refSpec string ) ( string , error ) {
buf := bytes . NewBuffer ( nil )
err := repo . r . Fetch ( & gogit . FetchOptions {
RemoteName : remote ,
RefSpecs : [ ] config . RefSpec { config . RefSpec ( refSpec ) } ,
Progress : buf ,
} )
2020-10-04 20:56:16 +03:00
if err == gogit . NoErrAlreadyUpToDate {
return "already up-to-date" , nil
}
2020-06-26 20:25:17 +03:00
if err != nil {
return "" , err
}
return buf . String ( ) , nil
}
// PushRefs push git refs to a remote
func ( repo * GoGitRepo ) PushRefs ( remote string , refSpec string ) ( string , error ) {
buf := bytes . NewBuffer ( nil )
err := repo . r . Push ( & gogit . PushOptions {
RemoteName : remote ,
RefSpecs : [ ] config . RefSpec { config . RefSpec ( refSpec ) } ,
Progress : buf ,
} )
2020-10-04 20:56:16 +03:00
if err == gogit . NoErrAlreadyUpToDate {
return "already up-to-date" , nil
}
2020-06-26 20:25:17 +03:00
if err != nil {
return "" , err
}
return buf . String ( ) , nil
}
// StoreData will store arbitrary data and return the corresponding hash
func ( repo * GoGitRepo ) StoreData ( data [ ] byte ) ( Hash , error ) {
obj := repo . r . Storer . NewEncodedObject ( )
obj . SetType ( plumbing . BlobObject )
w , err := obj . Writer ( )
if err != nil {
return "" , err
}
_ , err = w . Write ( data )
if err != nil {
return "" , err
}
h , err := repo . r . Storer . SetEncodedObject ( obj )
if err != nil {
return "" , err
}
return Hash ( h . String ( ) ) , nil
}
// ReadData will attempt to read arbitrary data from the given hash
func ( repo * GoGitRepo ) ReadData ( hash Hash ) ( [ ] byte , error ) {
obj , err := repo . r . BlobObject ( plumbing . NewHash ( hash . String ( ) ) )
if err != nil {
return nil , err
}
r , err := obj . Reader ( )
if err != nil {
return nil , err
}
return ioutil . ReadAll ( r )
}
2020-09-27 01:54:14 +03:00
// StoreTree will store a mapping key-->Hash as a Git tree
2020-06-26 20:25:17 +03:00
func ( repo * GoGitRepo ) StoreTree ( mapping [ ] TreeEntry ) ( Hash , error ) {
2020-09-02 15:48:28 +03:00
var tree object . Tree
2020-10-25 01:24:26 +03:00
// TODO: can be removed once https://github.com/go-git/go-git/issues/193 is resolved
sorted := make ( [ ] TreeEntry , len ( mapping ) )
copy ( sorted , mapping )
sort . Slice ( sorted , func ( i , j int ) bool {
nameI := sorted [ i ] . Name
if sorted [ i ] . ObjectType == Tree {
nameI += "/"
}
nameJ := sorted [ j ] . Name
if sorted [ j ] . ObjectType == Tree {
nameJ += "/"
}
return nameI < nameJ
} )
for _ , entry := range sorted {
2020-09-02 15:48:28 +03:00
mode := filemode . Regular
if entry . ObjectType == Tree {
mode = filemode . Dir
}
tree . Entries = append ( tree . Entries , object . TreeEntry {
Name : entry . Name ,
Mode : mode ,
Hash : plumbing . NewHash ( entry . Hash . String ( ) ) ,
} )
}
obj := repo . r . Storer . NewEncodedObject ( )
2020-09-08 06:00:36 +03:00
obj . SetType ( plumbing . TreeObject )
2020-09-02 15:48:28 +03:00
err := tree . Encode ( obj )
if err != nil {
return "" , err
}
hash , err := repo . r . Storer . SetEncodedObject ( obj )
if err != nil {
return "" , err
}
return Hash ( hash . String ( ) ) , nil
2020-06-26 20:25:17 +03:00
}
2020-09-27 01:54:14 +03:00
// ReadTree will return the list of entries in a Git tree
2020-06-26 20:25:17 +03:00
func ( repo * GoGitRepo ) ReadTree ( hash Hash ) ( [ ] TreeEntry , error ) {
2020-09-27 22:14:25 +03:00
h := plumbing . NewHash ( hash . String ( ) )
// the given hash could be a tree or a commit
obj , err := repo . r . Storer . EncodedObject ( plumbing . AnyObject , h )
if err != nil {
return nil , err
}
var tree * object . Tree
switch obj . Type ( ) {
case plumbing . TreeObject :
tree , err = object . DecodeTree ( repo . r . Storer , obj )
case plumbing . CommitObject :
var commit * object . Commit
commit , err = object . DecodeCommit ( repo . r . Storer , obj )
if err != nil {
return nil , err
}
tree , err = commit . Tree ( )
default :
return nil , fmt . Errorf ( "given hash is not a tree" )
}
2020-09-08 06:00:36 +03:00
if err != nil {
return nil , err
}
2020-09-27 22:14:25 +03:00
treeEntries := make ( [ ] TreeEntry , len ( tree . Entries ) )
for i , entry := range tree . Entries {
2020-09-08 06:00:36 +03:00
objType := Blob
if entry . Mode == filemode . Dir {
objType = Tree
}
treeEntries [ i ] = TreeEntry {
ObjectType : objType ,
Hash : Hash ( entry . Hash . String ( ) ) ,
Name : entry . Name ,
}
}
return treeEntries , nil
2020-06-26 20:25:17 +03:00
}
2020-09-27 01:54:14 +03:00
// StoreCommit will store a Git commit with the given Git tree
2020-06-26 20:25:17 +03:00
func ( repo * GoGitRepo ) StoreCommit ( treeHash Hash ) ( Hash , error ) {
2020-09-08 06:00:36 +03:00
return repo . StoreCommitWithParent ( treeHash , "" )
2020-06-26 20:25:17 +03:00
}
2020-09-27 01:54:14 +03:00
// StoreCommit will store a Git commit with the given Git tree
2020-06-26 20:25:17 +03:00
func ( repo * GoGitRepo ) StoreCommitWithParent ( treeHash Hash , parent Hash ) ( Hash , error ) {
2020-09-08 06:00:36 +03:00
cfg , err := repo . r . Config ( )
if err != nil {
return "" , err
}
commit := object . Commit {
Author : object . Signature {
2020-10-04 21:46:38 +03:00
Name : cfg . Author . Name ,
Email : cfg . Author . Email ,
When : time . Now ( ) ,
2020-09-08 06:00:36 +03:00
} ,
Committer : object . Signature {
2020-10-04 21:46:38 +03:00
Name : cfg . Committer . Name ,
Email : cfg . Committer . Email ,
When : time . Now ( ) ,
2020-09-08 06:00:36 +03:00
} ,
Message : "" ,
TreeHash : plumbing . NewHash ( treeHash . String ( ) ) ,
}
if parent != "" {
commit . ParentHashes = [ ] plumbing . Hash { plumbing . NewHash ( parent . String ( ) ) }
}
obj := repo . r . Storer . NewEncodedObject ( )
obj . SetType ( plumbing . CommitObject )
err = commit . Encode ( obj )
if err != nil {
return "" , err
}
hash , err := repo . r . Storer . SetEncodedObject ( obj )
if err != nil {
return "" , err
}
return Hash ( hash . String ( ) ) , nil
2020-06-26 20:25:17 +03:00
}
2020-09-27 01:54:14 +03:00
// GetTreeHash return the git tree hash referenced in a commit
2020-06-26 20:25:17 +03:00
func ( repo * GoGitRepo ) GetTreeHash ( commit Hash ) ( Hash , error ) {
2020-09-08 06:00:36 +03:00
obj , err := repo . r . CommitObject ( plumbing . NewHash ( commit . String ( ) ) )
if err != nil {
return "" , err
}
return Hash ( obj . TreeHash . String ( ) ) , nil
2020-06-26 20:25:17 +03:00
}
2020-09-27 01:54:14 +03:00
// FindCommonAncestor will return the last common ancestor of two chain of commit
2020-06-26 20:25:17 +03:00
func ( repo * GoGitRepo ) FindCommonAncestor ( commit1 Hash , commit2 Hash ) ( Hash , error ) {
2020-09-08 06:00:36 +03:00
obj1 , err := repo . r . CommitObject ( plumbing . NewHash ( commit1 . String ( ) ) )
if err != nil {
return "" , err
}
obj2 , err := repo . r . CommitObject ( plumbing . NewHash ( commit2 . String ( ) ) )
if err != nil {
return "" , err
}
commits , err := obj1 . MergeBase ( obj2 )
if err != nil {
return "" , err
}
return Hash ( commits [ 0 ] . Hash . String ( ) ) , nil
2020-06-26 20:25:17 +03:00
}
2020-09-27 01:54:14 +03:00
// UpdateRef will create or update a Git reference
2020-06-26 20:25:17 +03:00
func ( repo * GoGitRepo ) UpdateRef ( ref string , hash Hash ) error {
2020-09-08 06:00:36 +03:00
return repo . r . Storer . SetReference ( plumbing . NewHashReference ( plumbing . ReferenceName ( ref ) , plumbing . NewHash ( hash . String ( ) ) ) )
2020-06-26 20:25:17 +03:00
}
2020-09-27 01:54:14 +03:00
// RemoveRef will remove a Git reference
2020-06-26 20:25:17 +03:00
func ( repo * GoGitRepo ) RemoveRef ( ref string ) error {
2020-09-02 15:48:28 +03:00
return repo . r . Storer . RemoveReference ( plumbing . ReferenceName ( ref ) )
2020-06-26 20:25:17 +03:00
}
2020-09-27 01:54:14 +03:00
// ListRefs will return a list of Git ref matching the given refspec
2020-09-08 15:31:40 +03:00
func ( repo * GoGitRepo ) ListRefs ( refPrefix string ) ( [ ] string , error ) {
2020-09-08 06:00:36 +03:00
refIter , err := repo . r . References ( )
if err != nil {
return nil , err
}
refs := make ( [ ] string , 0 )
2020-09-08 15:31:40 +03:00
err = refIter . ForEach ( func ( ref * plumbing . Reference ) error {
if strings . HasPrefix ( ref . Name ( ) . String ( ) , refPrefix ) {
refs = append ( refs , ref . Name ( ) . String ( ) )
}
return nil
} )
if err != nil {
return nil , err
2020-09-08 06:00:36 +03:00
}
2020-09-08 15:31:40 +03:00
2020-09-08 06:00:36 +03:00
return refs , nil
2020-06-26 20:25:17 +03:00
}
2020-09-27 01:54:14 +03:00
// RefExist will check if a reference exist in Git
2020-06-26 20:25:17 +03:00
func ( repo * GoGitRepo ) RefExist ( ref string ) ( bool , error ) {
2020-09-08 06:00:36 +03:00
_ , err := repo . r . Reference ( plumbing . ReferenceName ( ref ) , false )
if err == nil {
return true , nil
} else if err == plumbing . ErrReferenceNotFound {
return false , nil
}
return false , err
2020-06-26 20:25:17 +03:00
}
2020-09-27 01:54:14 +03:00
// CopyRef will create a new reference with the same value as another one
2020-06-26 20:25:17 +03:00
func ( repo * GoGitRepo ) CopyRef ( source string , dest string ) error {
2020-09-08 15:45:03 +03:00
r , err := repo . r . Reference ( plumbing . ReferenceName ( source ) , false )
if err != nil {
return err
}
return repo . r . Storer . SetReference ( plumbing . NewHashReference ( plumbing . ReferenceName ( dest ) , r . Hash ( ) ) )
2020-06-26 20:25:17 +03:00
}
2020-09-27 01:54:14 +03:00
// ListCommits will return the list of tree hashes of a ref, in chronological order
2020-06-26 20:25:17 +03:00
func ( repo * GoGitRepo ) ListCommits ( ref string ) ( [ ] Hash , error ) {
2020-09-08 15:45:03 +03:00
r , err := repo . r . Reference ( plumbing . ReferenceName ( ref ) , false )
2020-09-08 06:00:36 +03:00
if err != nil {
return nil , err
}
2020-09-08 15:45:03 +03:00
commit , err := repo . r . CommitObject ( r . Hash ( ) )
2020-09-08 15:31:40 +03:00
if err != nil {
return nil , err
2020-09-08 06:00:36 +03:00
}
2020-09-29 21:16:15 +03:00
hashes := [ ] Hash { Hash ( commit . Hash . String ( ) ) }
2020-09-08 15:45:03 +03:00
for {
commit , err = commit . Parent ( 0 )
2020-09-29 21:16:15 +03:00
if err == object . ErrParentNotFound {
break
}
2020-09-08 15:45:03 +03:00
if err != nil {
return nil , err
}
if commit . NumParents ( ) > 1 {
return nil , fmt . Errorf ( "multiple parents" )
}
2020-09-29 21:16:15 +03:00
hashes = append ( [ ] Hash { Hash ( commit . Hash . String ( ) ) } , hashes ... )
2020-09-08 15:45:03 +03:00
}
2020-09-08 15:31:40 +03:00
2020-09-29 21:16:15 +03:00
return hashes , nil
2020-06-26 20:25:17 +03:00
}
2020-11-08 19:54:28 +03:00
func ( repo * GoGitRepo ) AllClocks ( ) ( map [ string ] lamport . Clock , error ) {
repo . clocksMutex . Lock ( )
defer repo . clocksMutex . Unlock ( )
result := make ( map [ string ] lamport . Clock )
files , err := ioutil . ReadDir ( filepath . Join ( repo . path , "git-bug" , clockPath ) )
if os . IsNotExist ( err ) {
return nil , nil
}
if err != nil {
return nil , err
}
for _ , file := range files {
name := file . Name ( )
if c , ok := repo . clocks [ name ] ; ok {
result [ name ] = c
} else {
c , err := lamport . LoadPersistedClock ( repo . LocalStorage ( ) , filepath . Join ( clockPath , name ) )
if err != nil {
return nil , err
}
repo . clocks [ name ] = c
result [ name ] = c
}
}
return result , nil
}
2020-06-26 20:25:17 +03:00
// GetOrCreateClock return a Lamport clock stored in the Repo.
// If the clock doesn't exist, it's created.
func ( repo * GoGitRepo ) GetOrCreateClock ( name string ) ( lamport . Clock , error ) {
2020-12-08 15:15:21 +03:00
repo . clocksMutex . Lock ( )
defer repo . clocksMutex . Unlock ( )
2020-06-26 20:25:17 +03:00
c , err := repo . getClock ( name )
if err == nil {
return c , nil
}
if err != ErrClockNotExist {
return nil , err
}
2020-11-08 19:54:28 +03:00
c , err = lamport . NewPersistedClock ( repo . LocalStorage ( ) , filepath . Join ( clockPath , name ) )
2020-06-26 20:25:17 +03:00
if err != nil {
return nil , err
}
repo . clocks [ name ] = c
return c , nil
}
func ( repo * GoGitRepo ) getClock ( name string ) ( lamport . Clock , error ) {
if c , ok := repo . clocks [ name ] ; ok {
return c , nil
}
2020-11-08 19:54:28 +03:00
c , err := lamport . LoadPersistedClock ( repo . LocalStorage ( ) , filepath . Join ( clockPath , name ) )
2020-06-26 20:25:17 +03:00
if err == nil {
repo . clocks [ name ] = c
return c , nil
}
if err == lamport . ErrClockNotExist {
return nil , ErrClockNotExist
}
return nil , err
}
2020-11-08 19:54:28 +03:00
// Increment is equivalent to c = GetOrCreateClock(name) + c.Increment()
func ( repo * GoGitRepo ) Increment ( name string ) ( lamport . Time , error ) {
c , err := repo . GetOrCreateClock ( name )
if err != nil {
return lamport . Time ( 0 ) , err
}
return c . Increment ( )
}
// Witness is equivalent to c = GetOrCreateClock(name) + c.Witness(time)
func ( repo * GoGitRepo ) Witness ( name string , time lamport . Time ) error {
c , err := repo . GetOrCreateClock ( name )
if err != nil {
return err
}
return c . Witness ( time )
}
2020-06-26 20:25:17 +03:00
// AddRemote add a new remote to the repository
// Not in the interface because it's only used for testing
func ( repo * GoGitRepo ) AddRemote ( name string , url string ) error {
_ , err := repo . r . CreateRemote ( & config . RemoteConfig {
Name : name ,
URLs : [ ] string { url } ,
} )
return err
}
2020-12-01 23:19:23 +03:00
// GetLocalRemote return the URL to use to add this repo as a local remote
func ( repo * GoGitRepo ) GetLocalRemote ( ) string {
return repo . path
}
// EraseFromDisk delete this repository entirely from the disk
func ( repo * GoGitRepo ) EraseFromDisk ( ) error {
2020-12-08 17:09:58 +03:00
err := repo . Close ( )
if err != nil {
return err
}
2020-12-01 23:19:23 +03:00
path := filepath . Clean ( strings . TrimSuffix ( repo . path , string ( filepath . Separator ) + ".git" ) )
// fmt.Println("Cleaning repo:", path)
return os . RemoveAll ( path )
}