aboutsummaryrefslogtreecommitdiffstats
path: root/repository/gogit.go
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2021-04-09 13:01:14 +0200
committerMichael Muré <batolettre@gmail.com>2021-04-09 13:01:14 +0200
commit1520f678f7a2bc6e01d9b01df5ce49f2f46be7d7 (patch)
treef6d71c1f29cf06ccab9e4ae434b19ab17caa4385 /repository/gogit.go
parent0fd570171d171aa574d7f01d6033a9c01d668465 (diff)
parentbc5f618eba812859bf87ce2c31b278bd518d4555 (diff)
downloadgit-bug-1520f678f7a2bc6e01d9b01df5ce49f2f46be7d7.tar.gz
Merge remote-tracking branch 'origin/master' into dev-gh-bridge
Diffstat (limited to 'repository/gogit.go')
-rw-r--r--repository/gogit.go193
1 files changed, 160 insertions, 33 deletions
diff --git a/repository/gogit.go b/repository/gogit.go
index bdac259d..20454bd7 100644
--- a/repository/gogit.go
+++ b/repository/gogit.go
@@ -5,7 +5,6 @@ import (
"fmt"
"io/ioutil"
"os"
- "os/exec"
"path/filepath"
"sort"
"strings"
@@ -20,10 +19,14 @@ import (
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/filemode"
"github.com/go-git/go-git/v5/plumbing/object"
+ "golang.org/x/crypto/openpgp"
+ "golang.org/x/sys/execabs"
"github.com/MichaelMure/git-bug/util/lamport"
)
+const clockPath = "clocks"
+
var _ ClockedRepo = &GoGitRepo{}
var _ TestedRepo = &GoGitRepo{}
@@ -261,7 +264,7 @@ func (repo *GoGitRepo) GetCoreEditor() (string, error) {
}
for _, cmd := range priorities {
- if _, err = exec.LookPath(cmd); err == nil {
+ if _, err = execabs.LookPath(cmd); err == nil {
return cmd, nil
}
@@ -332,7 +335,7 @@ func (repo *GoGitRepo) ClearBleveIndex(name string) error {
repo.indexesMutex.Lock()
defer repo.indexesMutex.Unlock()
- path := filepath.Join(repo.path, "indexes", name)
+ path := filepath.Join(repo.path, "git-bug", "indexes", name)
err := os.RemoveAll(path)
if err != nil {
@@ -350,13 +353,17 @@ func (repo *GoGitRepo) ClearBleveIndex(name string) error {
return nil
}
-// FetchRefs fetch git refs from a remote
-func (repo *GoGitRepo) FetchRefs(remote string, refSpec string) (string, error) {
+// FetchRefs fetch git refs matching a directory prefix to a remote
+// Ex: prefix="foo" will fetch any remote refs matching "refs/foo/*" locally.
+// The equivalent git refspec would be "refs/foo/*:refs/remotes/<remote>/foo/*"
+func (repo *GoGitRepo) FetchRefs(remote string, prefix string) (string, error) {
+ refspec := fmt.Sprintf("refs/%s/*:refs/remotes/%s/%s/*", prefix, remote, prefix)
+
buf := bytes.NewBuffer(nil)
err := repo.r.Fetch(&gogit.FetchOptions{
RemoteName: remote,
- RefSpecs: []config.RefSpec{config.RefSpec(refSpec)},
+ RefSpecs: []config.RefSpec{config.RefSpec(refspec)},
Progress: buf,
})
if err == gogit.NoErrAlreadyUpToDate {
@@ -369,13 +376,41 @@ func (repo *GoGitRepo) FetchRefs(remote string, refSpec string) (string, error)
return buf.String(), nil
}
-// PushRefs push git refs to a remote
-func (repo *GoGitRepo) PushRefs(remote string, refSpec string) (string, error) {
+// PushRefs push git refs matching a directory prefix to a remote
+// Ex: prefix="foo" will push any local refs matching "refs/foo/*" to the remote.
+// The equivalent git refspec would be "refs/foo/*:refs/foo/*"
+//
+// Additionally, PushRefs will update the local references in refs/remotes/<remote>/foo to match
+// the remote state.
+func (repo *GoGitRepo) PushRefs(remote string, prefix string) (string, error) {
+ refspec := fmt.Sprintf("refs/%s/*:refs/%s/*", prefix, prefix)
+
+ remo, err := repo.r.Remote(remote)
+ if err != nil {
+ return "", err
+ }
+
+ // to make sure that the push also create the corresponding refs/remotes/<remote>/... references,
+ // we need to have a default fetch refspec configured on the remote, to make our refs "track" the remote ones.
+ // This does not change the config on disk, only on memory.
+ hasCustomFetch := false
+ fetchRefspec := fmt.Sprintf("refs/%s/*:refs/remotes/%s/%s/*", prefix, remote, prefix)
+ for _, r := range remo.Config().Fetch {
+ if string(r) == fetchRefspec {
+ hasCustomFetch = true
+ break
+ }
+ }
+
+ if !hasCustomFetch {
+ remo.Config().Fetch = append(remo.Config().Fetch, config.RefSpec(fetchRefspec))
+ }
+
buf := bytes.NewBuffer(nil)
- err := repo.r.Push(&gogit.PushOptions{
+ err = remo.Push(&gogit.PushOptions{
RemoteName: remote,
- RefSpecs: []config.RefSpec{config.RefSpec(refSpec)},
+ RefSpecs: []config.RefSpec{config.RefSpec(refspec)},
Progress: buf,
})
if err == gogit.NoErrAlreadyUpToDate {
@@ -519,12 +554,13 @@ func (repo *GoGitRepo) ReadTree(hash Hash) ([]TreeEntry, error) {
}
// StoreCommit will store a Git commit with the given Git tree
-func (repo *GoGitRepo) StoreCommit(treeHash Hash) (Hash, error) {
- return repo.StoreCommitWithParent(treeHash, "")
+func (repo *GoGitRepo) StoreCommit(treeHash Hash, parents ...Hash) (Hash, error) {
+ return repo.StoreSignedCommit(treeHash, nil, parents...)
}
-// StoreCommit will store a Git commit with the given Git tree
-func (repo *GoGitRepo) StoreCommitWithParent(treeHash Hash, parent Hash) (Hash, error) {
+// StoreCommit will store a Git commit with the given Git tree. If signKey is not nil, the commit
+// will be signed accordingly.
+func (repo *GoGitRepo) StoreSignedCommit(treeHash Hash, signKey *openpgp.Entity, parents ...Hash) (Hash, error) {
cfg, err := repo.r.Config()
if err != nil {
return "", err
@@ -545,8 +581,28 @@ func (repo *GoGitRepo) StoreCommitWithParent(treeHash Hash, parent Hash) (Hash,
TreeHash: plumbing.NewHash(treeHash.String()),
}
- if parent != "" {
- commit.ParentHashes = []plumbing.Hash{plumbing.NewHash(parent.String())}
+ for _, parent := range parents {
+ commit.ParentHashes = append(commit.ParentHashes, plumbing.NewHash(parent.String()))
+ }
+
+ // Compute the signature if needed
+ if signKey != nil {
+ // first get the serialized commit
+ encoded := &plumbing.MemoryObject{}
+ if err := commit.Encode(encoded); err != nil {
+ return "", err
+ }
+ r, err := encoded.Reader()
+ if err != nil {
+ return "", err
+ }
+
+ // sign the data
+ var sig bytes.Buffer
+ if err := openpgp.ArmoredDetachSign(&sig, signKey, r, nil); err != nil {
+ return "", err
+ }
+ commit.PGPSignature = sig.String()
}
obj := repo.r.Storer.NewEncodedObject()
@@ -593,6 +649,14 @@ func (repo *GoGitRepo) FindCommonAncestor(commit1 Hash, commit2 Hash) (Hash, err
return Hash(commits[0].Hash.String()), nil
}
+func (repo *GoGitRepo) ResolveRef(ref string) (Hash, error) {
+ r, err := repo.r.Reference(plumbing.ReferenceName(ref), false)
+ if err != nil {
+ return "", err
+ }
+ return Hash(r.Hash().String()), nil
+}
+
// UpdateRef will create or update a Git reference
func (repo *GoGitRepo) UpdateRef(ref string, hash Hash) error {
return repo.r.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName(ref), plumbing.NewHash(hash.String())))
@@ -647,34 +711,79 @@ func (repo *GoGitRepo) CopyRef(source string, dest string) error {
// ListCommits will return the list of tree hashes of a ref, in chronological order
func (repo *GoGitRepo) ListCommits(ref string) ([]Hash, error) {
- r, err := repo.r.Reference(plumbing.ReferenceName(ref), false)
+ return nonNativeListCommits(repo, ref)
+}
+
+func (repo *GoGitRepo) ReadCommit(hash Hash) (Commit, error) {
+ commit, err := repo.r.CommitObject(plumbing.NewHash(hash.String()))
if err != nil {
- return nil, err
+ return Commit{}, err
}
- commit, err := repo.r.CommitObject(r.Hash())
- if err != nil {
- return nil, err
+ parents := make([]Hash, len(commit.ParentHashes))
+ for i, parentHash := range commit.ParentHashes {
+ parents[i] = Hash(parentHash.String())
}
- hashes := []Hash{Hash(commit.Hash.String())}
- for {
- commit, err = commit.Parent(0)
- if err == object.ErrParentNotFound {
- break
+ result := Commit{
+ Hash: hash,
+ Parents: parents,
+ TreeHash: Hash(commit.TreeHash.String()),
+ }
+
+ if commit.PGPSignature != "" {
+ // I can't find a way to just remove the signature when reading the encoded commit so we need to
+ // re-encode the commit without signature.
+
+ encoded := &plumbing.MemoryObject{}
+ err := commit.EncodeWithoutSignature(encoded)
+ if err != nil {
+ return Commit{}, err
}
+
+ result.SignedData, err = encoded.Reader()
if err != nil {
- return nil, err
+ return Commit{}, err
}
- if commit.NumParents() > 1 {
- return nil, fmt.Errorf("multiple parents")
+ result.Signature, err = deArmorSignature(strings.NewReader(commit.PGPSignature))
+ if err != nil {
+ return Commit{}, err
}
+ }
- hashes = append([]Hash{Hash(commit.Hash.String())}, hashes...)
+ return result, nil
+}
+
+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
}
- return hashes, nil
+ 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
}
// GetOrCreateClock return a Lamport clock stored in the Repo.
@@ -691,7 +800,7 @@ func (repo *GoGitRepo) GetOrCreateClock(name string) (lamport.Clock, error) {
return nil, err
}
- c, err = lamport.NewPersistedClock(repo.localStorage, name+"-clock")
+ c, err = lamport.NewPersistedClock(repo.LocalStorage(), filepath.Join(clockPath, name))
if err != nil {
return nil, err
}
@@ -705,7 +814,7 @@ func (repo *GoGitRepo) getClock(name string) (lamport.Clock, error) {
return c, nil
}
- c, err := lamport.LoadPersistedClock(repo.localStorage, name+"-clock")
+ c, err := lamport.LoadPersistedClock(repo.LocalStorage(), filepath.Join(clockPath, name))
if err == nil {
repo.clocks[name] = c
return c, nil
@@ -716,6 +825,24 @@ func (repo *GoGitRepo) getClock(name string) (lamport.Clock, error) {
return nil, err
}
+// 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)
+}
+
// 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 {