aboutsummaryrefslogtreecommitdiffstats
path: root/repository
diff options
context:
space:
mode:
Diffstat (limited to 'repository')
-rw-r--r--repository/common.go120
-rw-r--r--repository/git.go570
-rw-r--r--repository/git_cli.go56
-rw-r--r--repository/git_config.go221
-rw-r--r--repository/git_test.go6
-rw-r--r--repository/git_testing.go72
-rw-r--r--repository/gogit.go92
-rw-r--r--repository/gogit_testing.go8
-rw-r--r--repository/keyring.go10
-rw-r--r--repository/mock_repo.go177
-rw-r--r--repository/mock_repo_test.go4
-rw-r--r--repository/repo.go27
-rw-r--r--repository/repo_testing.go24
13 files changed, 325 insertions, 1062 deletions
diff --git a/repository/common.go b/repository/common.go
new file mode 100644
index 00000000..7fd7ae19
--- /dev/null
+++ b/repository/common.go
@@ -0,0 +1,120 @@
+package repository
+
+import (
+ "io"
+
+ "golang.org/x/crypto/openpgp"
+ "golang.org/x/crypto/openpgp/armor"
+ "golang.org/x/crypto/openpgp/errors"
+)
+
+// nonNativeMerge is an implementation of a branch merge, for the case where
+// the underlying git implementation doesn't support it natively.
+func nonNativeMerge(repo RepoData, ref string, otherRef string, treeHashFn func() Hash) error {
+ commit, err := repo.ResolveRef(ref)
+ if err != nil {
+ return err
+ }
+
+ otherCommit, err := repo.ResolveRef(otherRef)
+ if err != nil {
+ return err
+ }
+
+ if commit == otherCommit {
+ // nothing to merge
+ return nil
+ }
+
+ // fast-forward is possible if otherRef include ref
+
+ otherCommits, err := repo.ListCommits(otherRef)
+ if err != nil {
+ return err
+ }
+
+ fastForwardPossible := false
+ for _, hash := range otherCommits {
+ if hash == commit {
+ fastForwardPossible = true
+ break
+ }
+ }
+
+ if fastForwardPossible {
+ return repo.UpdateRef(ref, otherCommit)
+ }
+
+ // fast-forward is not possible, we need to create a merge commit
+
+ // we need a Tree to make the commit, an empty Tree will do
+ emptyTreeHash, err := repo.StoreTree(nil)
+ if err != nil {
+ return err
+ }
+
+ newHash, err := repo.StoreCommit(emptyTreeHash, commit, otherCommit)
+ if err != nil {
+ return err
+ }
+
+ return repo.UpdateRef(ref, newHash)
+}
+
+// nonNativeListCommits is an implementation for ListCommits, for the case where
+// the underlying git implementation doesn't support if natively.
+func nonNativeListCommits(repo RepoData, ref string) ([]Hash, error) {
+ var result []Hash
+
+ stack := make([]Hash, 0, 32)
+ visited := make(map[Hash]struct{})
+
+ hash, err := repo.ResolveRef(ref)
+ if err != nil {
+ return nil, err
+ }
+
+ stack = append(stack, hash)
+
+ for len(stack) > 0 {
+ // pop
+ hash := stack[len(stack)-1]
+ stack = stack[:len(stack)-1]
+
+ if _, ok := visited[hash]; ok {
+ continue
+ }
+
+ // mark as visited
+ visited[hash] = struct{}{}
+ result = append(result, hash)
+
+ commit, err := repo.ReadCommit(hash)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, parent := range commit.Parents {
+ stack = append(stack, parent)
+ }
+ }
+
+ // reverse
+ for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
+ result[i], result[j] = result[j], result[i]
+ }
+
+ return result, nil
+}
+
+// deArmorSignature convert an armored (text serialized) signature into raw binary
+func deArmorSignature(armoredSig io.Reader) (io.Reader, error) {
+ block, err := armor.Decode(armoredSig)
+ if err != nil {
+ return nil, err
+ }
+ if block.Type != openpgp.SignatureType {
+ return nil, errors.InvalidArgumentError("expected '" + openpgp.SignatureType + "', got: " + block.Type)
+ }
+ return block.Body, nil
+}
diff --git a/repository/git.go b/repository/git.go
deleted file mode 100644
index e89bae87..00000000
--- a/repository/git.go
+++ /dev/null
@@ -1,570 +0,0 @@
-// Package repository contains helper methods for working with the Git repo.
-package repository
-
-import (
- "bytes"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
- "sync"
-
- "github.com/blevesearch/bleve"
- "github.com/go-git/go-billy/v5"
- "github.com/go-git/go-billy/v5/osfs"
-
- "github.com/MichaelMure/git-bug/util/lamport"
-)
-
-var _ ClockedRepo = &GitRepo{}
-var _ TestedRepo = &GitRepo{}
-
-// GitRepo represents an instance of a (local) git repository.
-type GitRepo struct {
- gitCli
- path string
-
- clocksMutex sync.Mutex
- clocks map[string]lamport.Clock
-
- indexesMutex sync.Mutex
- indexes map[string]bleve.Index
-
- keyring Keyring
- localStorage billy.Filesystem
-}
-
-func (repo *GitRepo) ReadCommit(hash Hash) (Commit, error) {
- panic("implement me")
-}
-
-func (repo *GitRepo) ResolveRef(ref string) (Hash, error) {
- panic("implement me")
-}
-
-// OpenGitRepo determines if the given working directory is inside of a git repository,
-// and returns the corresponding GitRepo instance if it is.
-func OpenGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) {
- k, err := defaultKeyring()
- if err != nil {
- return nil, err
- }
-
- repo := &GitRepo{
- gitCli: gitCli{path: path},
- path: path,
- clocks: make(map[string]lamport.Clock),
- indexes: make(map[string]bleve.Index),
- keyring: k,
- }
-
- // Check the repo and retrieve the root path
- stdout, err := repo.runGitCommand("rev-parse", "--absolute-git-dir")
-
- // Now dir is fetched with "git rev-parse --git-dir". May be it can
- // still return nothing in some cases. Then empty stdout check is
- // kept.
- if err != nil || stdout == "" {
- return nil, ErrNotARepo
- }
-
- // Fix the path to be sure we are at the root
- repo.path = stdout
- repo.gitCli.path = stdout
- repo.localStorage = osfs.New(filepath.Join(path, "git-bug"))
-
- 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
-}
-
-// InitGitRepo create a new empty git repo at the given path
-func InitGitRepo(path string) (*GitRepo, error) {
- k, err := defaultKeyring()
- if err != nil {
- return nil, err
- }
-
- repo := &GitRepo{
- gitCli: gitCli{path: path},
- path: filepath.Join(path, ".git"),
- clocks: make(map[string]lamport.Clock),
- indexes: make(map[string]bleve.Index),
- keyring: k,
- localStorage: osfs.New(filepath.Join(path, ".git", "git-bug")),
- }
-
- _, err = repo.runGitCommand("init", path)
- if err != nil {
- return nil, err
- }
-
- return repo, nil
-}
-
-// InitBareGitRepo create a new --bare empty git repo at the given path
-func InitBareGitRepo(path string) (*GitRepo, error) {
- k, err := defaultKeyring()
- if err != nil {
- return nil, err
- }
-
- repo := &GitRepo{
- gitCli: gitCli{path: path},
- path: path,
- clocks: make(map[string]lamport.Clock),
- indexes: make(map[string]bleve.Index),
- keyring: k,
- localStorage: osfs.New(filepath.Join(path, "git-bug")),
- }
-
- _, err = repo.runGitCommand("init", "--bare", path)
- if err != nil {
- return nil, err
- }
-
- return repo, nil
-}
-
-func (repo *GitRepo) Close() error {
- var firstErr error
- for _, index := range repo.indexes {
- err := index.Close()
- if err != nil && firstErr == nil {
- firstErr = err
- }
- }
- return firstErr
-}
-
-// LocalConfig give access to the repository scoped configuration
-func (repo *GitRepo) LocalConfig() Config {
- return newGitConfig(repo.gitCli, false)
-}
-
-// GlobalConfig give access to the global scoped configuration
-func (repo *GitRepo) GlobalConfig() Config {
- return newGitConfig(repo.gitCli, true)
-}
-
-// AnyConfig give access to a merged local/global configuration
-func (repo *GitRepo) AnyConfig() ConfigRead {
- return mergeConfig(repo.LocalConfig(), repo.GlobalConfig())
-}
-
-// Keyring give access to a user-wide storage for secrets
-func (repo *GitRepo) Keyring() Keyring {
- return repo.keyring
-}
-
-// GetPath returns the path to the repo.
-func (repo *GitRepo) GetPath() string {
- return repo.path
-}
-
-// GetUserName returns the name the the user has used to configure git
-func (repo *GitRepo) GetUserName() (string, error) {
- return repo.runGitCommand("config", "user.name")
-}
-
-// GetUserEmail returns the email address that the user has used to configure git.
-func (repo *GitRepo) GetUserEmail() (string, error) {
- return repo.runGitCommand("config", "user.email")
-}
-
-// GetCoreEditor returns the name of the editor that the user has used to configure git.
-func (repo *GitRepo) GetCoreEditor() (string, error) {
- return repo.runGitCommand("var", "GIT_EDITOR")
-}
-
-// GetRemotes returns the configured remotes repositories.
-func (repo *GitRepo) GetRemotes() (map[string]string, error) {
- stdout, err := repo.runGitCommand("remote", "--verbose")
- if err != nil {
- return nil, err
- }
-
- lines := strings.Split(stdout, "\n")
- remotes := make(map[string]string, len(lines))
-
- for _, line := range lines {
- if strings.TrimSpace(line) == "" {
- continue
- }
- elements := strings.Fields(line)
- if len(elements) != 3 {
- return nil, fmt.Errorf("git remote: unexpected output format: %s", line)
- }
-
- remotes[elements[0]] = elements[1]
- }
-
- return remotes, nil
-}
-
-// LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug
-func (repo *GitRepo) LocalStorage() billy.Filesystem {
- return repo.localStorage
-}
-
-// GetBleveIndex return a bleve.Index that can be used to index documents
-func (repo *GitRepo) 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, "indexes", name)
-
- index, err := bleve.Open(path)
- if err == nil {
- repo.indexes[name] = index
- return index, nil
- }
-
- err = os.MkdirAll(path, os.ModeDir)
- 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 *GitRepo) 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
- }
-
- delete(repo.indexes, name)
-
- return nil
-}
-
-// FetchRefs fetch git refs from a remote
-func (repo *GitRepo) FetchRefs(remote, refSpec string) (string, error) {
- stdout, err := repo.runGitCommand("fetch", remote, refSpec)
-
- if err != nil {
- return stdout, fmt.Errorf("failed to fetch from the remote '%s': %v", remote, err)
- }
-
- return stdout, err
-}
-
-// PushRefs push git refs to a remote
-func (repo *GitRepo) PushRefs(remote string, refSpec string) (string, error) {
- stdout, stderr, err := repo.runGitCommandRaw(nil, "push", remote, refSpec)
-
- if err != nil {
- return stdout + stderr, fmt.Errorf("failed to push to the remote '%s': %v", remote, stderr)
- }
- return stdout + stderr, nil
-}
-
-// StoreData will store arbitrary data and return the corresponding hash
-func (repo *GitRepo) StoreData(data []byte) (Hash, error) {
- var stdin = bytes.NewReader(data)
-
- stdout, err := repo.runGitCommandWithStdin(stdin, "hash-object", "--stdin", "-w")
-
- return Hash(stdout), err
-}
-
-// ReadData will attempt to read arbitrary data from the given hash
-func (repo *GitRepo) ReadData(hash Hash) ([]byte, error) {
- var stdout bytes.Buffer
- var stderr bytes.Buffer
-
- err := repo.runGitCommandWithIO(nil, &stdout, &stderr, "cat-file", "-p", string(hash))
-
- if err != nil {
- return []byte{}, err
- }
-
- return stdout.Bytes(), nil
-}
-
-// StoreTree will store a mapping key-->Hash as a Git tree
-func (repo *GitRepo) StoreTree(entries []TreeEntry) (Hash, error) {
- buffer := prepareTreeEntries(entries)
-
- stdout, err := repo.runGitCommandWithStdin(&buffer, "mktree")
-
- if err != nil {
- return "", err
- }
-
- return Hash(stdout), nil
-}
-
-// StoreCommit will store a Git commit with the given Git tree
-func (repo *GitRepo) StoreCommit(treeHash Hash) (Hash, error) {
- stdout, err := repo.runGitCommand("commit-tree", string(treeHash))
-
- if err != nil {
- return "", err
- }
-
- return Hash(stdout), nil
-}
-
-// StoreCommitWithParent will store a Git commit with the given Git tree
-func (repo *GitRepo) StoreCommitWithParent(treeHash Hash, parent Hash) (Hash, error) {
- stdout, err := repo.runGitCommand("commit-tree", string(treeHash),
- "-p", string(parent))
-
- if err != nil {
- return "", err
- }
-
- return Hash(stdout), nil
-}
-
-// UpdateRef will create or update a Git reference
-func (repo *GitRepo) UpdateRef(ref string, hash Hash) error {
- _, err := repo.runGitCommand("update-ref", ref, string(hash))
-
- return err
-}
-
-// RemoveRef will remove a Git reference
-func (repo *GitRepo) RemoveRef(ref string) error {
- _, err := repo.runGitCommand("update-ref", "-d", ref)
-
- return err
-}
-
-// ListRefs will return a list of Git ref matching the given refspec
-func (repo *GitRepo) ListRefs(refPrefix string) ([]string, error) {
- stdout, err := repo.runGitCommand("for-each-ref", "--format=%(refname)", refPrefix)
-
- if err != nil {
- return nil, err
- }
-
- split := strings.Split(stdout, "\n")
-
- if len(split) == 1 && split[0] == "" {
- return []string{}, nil
- }
-
- return split, nil
-}
-
-// RefExist will check if a reference exist in Git
-func (repo *GitRepo) RefExist(ref string) (bool, error) {
- stdout, err := repo.runGitCommand("for-each-ref", ref)
-
- if err != nil {
- return false, err
- }
-
- return stdout != "", nil
-}
-
-// CopyRef will create a new reference with the same value as another one
-func (repo *GitRepo) CopyRef(source string, dest string) error {
- _, err := repo.runGitCommand("update-ref", dest, source)
-
- return err
-}
-
-// ListCommits will return the list of commit hashes of a ref, in chronological order
-func (repo *GitRepo) ListCommits(ref string) ([]Hash, error) {
- stdout, err := repo.runGitCommand("rev-list", "--first-parent", "--reverse", ref)
-
- if err != nil {
- return nil, err
- }
-
- split := strings.Split(stdout, "\n")
-
- casted := make([]Hash, len(split))
- for i, line := range split {
- casted[i] = Hash(line)
- }
-
- return casted, nil
-
-}
-
-// ReadTree will return the list of entries in a Git tree
-func (repo *GitRepo) ReadTree(hash Hash) ([]TreeEntry, error) {
- stdout, err := repo.runGitCommand("ls-tree", string(hash))
-
- if err != nil {
- return nil, err
- }
-
- return readTreeEntries(stdout)
-}
-
-// FindCommonAncestor will return the last common ancestor of two chain of commit
-func (repo *GitRepo) FindCommonAncestor(hash1 Hash, hash2 Hash) (Hash, error) {
- stdout, err := repo.runGitCommand("merge-base", string(hash1), string(hash2))
-
- if err != nil {
- return "", err
- }
-
- return Hash(stdout), nil
-}
-
-// GetTreeHash return the git tree hash referenced in a commit
-func (repo *GitRepo) GetTreeHash(commit Hash) (Hash, error) {
- stdout, err := repo.runGitCommand("rev-parse", string(commit)+"^{tree}")
-
- if err != nil {
- return "", err
- }
-
- return Hash(stdout), nil
-}
-
-func (repo *GitRepo) 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
-}
-
-// GetOrCreateClock return a Lamport clock stored in the Repo.
-// If the clock doesn't exist, it's created.
-func (repo *GitRepo) GetOrCreateClock(name string) (lamport.Clock, error) {
- repo.clocksMutex.Lock()
- defer repo.clocksMutex.Unlock()
-
- c, err := repo.getClock(name)
- if err == nil {
- return c, nil
- }
- if err != ErrClockNotExist {
- return nil, err
- }
-
- c, err = lamport.NewPersistedClock(repo.LocalStorage(), filepath.Join(clockPath, name))
- if err != nil {
- return nil, err
- }
-
- repo.clocks[name] = c
- return c, nil
-}
-
-func (repo *GitRepo) getClock(name string) (lamport.Clock, error) {
- if c, ok := repo.clocks[name]; ok {
- return c, nil
- }
-
- c, err := lamport.LoadPersistedClock(repo.LocalStorage(), filepath.Join(clockPath, name))
- if err == nil {
- repo.clocks[name] = c
- return c, nil
- }
- if err == lamport.ErrClockNotExist {
- return nil, ErrClockNotExist
- }
- return nil, err
-}
-
-// Increment is equivalent to c = GetOrCreateClock(name) + c.Increment()
-func (repo *GitRepo) 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 *GitRepo) 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 *GitRepo) AddRemote(name string, url string) error {
- _, err := repo.runGitCommand("remote", "add", name, url)
-
- return err
-}
-
-// GetLocalRemote return the URL to use to add this repo as a local remote
-func (repo *GitRepo) GetLocalRemote() string {
- return repo.path
-}
-
-// EraseFromDisk delete this repository entirely from the disk
-func (repo *GitRepo) EraseFromDisk() error {
- err := repo.Close()
- if err != nil {
- return err
- }
-
- path := filepath.Clean(strings.TrimSuffix(repo.path, string(filepath.Separator)+".git"))
-
- // fmt.Println("Cleaning repo:", path)
- return os.RemoveAll(path)
-}
diff --git a/repository/git_cli.go b/repository/git_cli.go
deleted file mode 100644
index 085b1cda..00000000
--- a/repository/git_cli.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package repository
-
-import (
- "bytes"
- "fmt"
- "io"
- "os/exec"
- "strings"
-)
-
-// gitCli is a helper to launch CLI git commands
-type gitCli struct {
- path string
-}
-
-// Run the given git command with the given I/O reader/writers, returning an error if it fails.
-func (cli gitCli) runGitCommandWithIO(stdin io.Reader, stdout, stderr io.Writer, args ...string) error {
- // make sure that the working directory for the command
- // always exist, in particular when running "git init".
- path := strings.TrimSuffix(cli.path, ".git")
-
- // fmt.Printf("[%s] Running git %s\n", path, strings.Join(args, " "))
-
- cmd := exec.Command("git", args...)
- cmd.Dir = path
- cmd.Stdin = stdin
- cmd.Stdout = stdout
- cmd.Stderr = stderr
-
- return cmd.Run()
-}
-
-// Run the given git command and return its stdout, or an error if the command fails.
-func (cli gitCli) runGitCommandRaw(stdin io.Reader, args ...string) (string, string, error) {
- var stdout bytes.Buffer
- var stderr bytes.Buffer
- err := cli.runGitCommandWithIO(stdin, &stdout, &stderr, args...)
- return strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), err
-}
-
-// Run the given git command and return its stdout, or an error if the command fails.
-func (cli gitCli) runGitCommandWithStdin(stdin io.Reader, args ...string) (string, error) {
- stdout, stderr, err := cli.runGitCommandRaw(stdin, args...)
- if err != nil {
- if stderr == "" {
- stderr = "Error running git command: " + strings.Join(args, " ")
- }
- err = fmt.Errorf(stderr)
- }
- return stdout, err
-}
-
-// Run the given git command and return its stdout, or an error if the command fails.
-func (cli gitCli) runGitCommand(args ...string) (string, error) {
- return cli.runGitCommandWithStdin(nil, args...)
-}
diff --git a/repository/git_config.go b/repository/git_config.go
deleted file mode 100644
index b46cc69b..00000000
--- a/repository/git_config.go
+++ /dev/null
@@ -1,221 +0,0 @@
-package repository
-
-import (
- "fmt"
- "regexp"
- "strconv"
- "strings"
- "time"
-
- "github.com/blang/semver"
- "github.com/pkg/errors"
-)
-
-var _ Config = &gitConfig{}
-
-type gitConfig struct {
- cli gitCli
- localityFlag string
-}
-
-func newGitConfig(cli gitCli, global bool) *gitConfig {
- localityFlag := "--local"
- if global {
- localityFlag = "--global"
- }
- return &gitConfig{
- cli: cli,
- localityFlag: localityFlag,
- }
-}
-
-// StoreString store a single key/value pair in the config of the repo
-func (gc *gitConfig) StoreString(key string, value string) error {
- _, err := gc.cli.runGitCommand("config", gc.localityFlag, "--replace-all", key, value)
- return err
-}
-
-func (gc *gitConfig) StoreBool(key string, value bool) error {
- return gc.StoreString(key, strconv.FormatBool(value))
-}
-
-func (gc *gitConfig) StoreTimestamp(key string, value time.Time) error {
- return gc.StoreString(key, strconv.Itoa(int(value.Unix())))
-}
-
-// ReadAll read all key/value pair matching the key prefix
-func (gc *gitConfig) ReadAll(keyPrefix string) (map[string]string, error) {
- stdout, err := gc.cli.runGitCommand("config", gc.localityFlag, "--includes", "--get-regexp", keyPrefix)
-
- // / \
- // / ! \
- // -------
- //
- // There can be a legitimate error here, but I see no portable way to
- // distinguish them from the git error that say "no matching value exist"
- if err != nil {
- return nil, nil
- }
-
- lines := strings.Split(stdout, "\n")
-
- result := make(map[string]string, len(lines))
-
- for _, line := range lines {
- if strings.TrimSpace(line) == "" {
- continue
- }
-
- parts := strings.SplitN(line, " ", 2)
- result[parts[0]] = parts[1]
- }
-
- return result, nil
-}
-
-func (gc *gitConfig) ReadString(key string) (string, error) {
- stdout, err := gc.cli.runGitCommand("config", gc.localityFlag, "--includes", "--get-all", key)
-
- // / \
- // / ! \
- // -------
- //
- // There can be a legitimate error here, but I see no portable way to
- // distinguish them from the git error that say "no matching value exist"
- if err != nil {
- return "", ErrNoConfigEntry
- }
-
- lines := strings.Split(stdout, "\n")
-
- if len(lines) == 0 {
- return "", ErrNoConfigEntry
- }
- if len(lines) > 1 {
- return "", ErrMultipleConfigEntry
- }
-
- return lines[0], nil
-}
-
-func (gc *gitConfig) ReadBool(key string) (bool, error) {
- val, err := gc.ReadString(key)
- if err != nil {
- return false, err
- }
-
- return strconv.ParseBool(val)
-}
-
-func (gc *gitConfig) ReadTimestamp(key string) (time.Time, error) {
- value, err := gc.ReadString(key)
- if err != nil {
- return time.Time{}, err
- }
- return ParseTimestamp(value)
-}
-
-func (gc *gitConfig) rmSection(keyPrefix string) error {
- _, err := gc.cli.runGitCommand("config", gc.localityFlag, "--remove-section", keyPrefix)
- return err
-}
-
-func (gc *gitConfig) unsetAll(keyPrefix string) error {
- _, err := gc.cli.runGitCommand("config", gc.localityFlag, "--unset-all", keyPrefix)
- return err
-}
-
-// return keyPrefix section
-// example: sectionFromKey(a.b.c.d) return a.b.c
-func sectionFromKey(keyPrefix string) string {
- s := strings.Split(keyPrefix, ".")
- if len(s) == 1 {
- return keyPrefix
- }
-
- return strings.Join(s[:len(s)-1], ".")
-}
-
-// rmConfigs with git version lesser than 2.18
-func (gc *gitConfig) rmConfigsGitVersionLT218(keyPrefix string) error {
- // try to remove key/value pair by key
- err := gc.unsetAll(keyPrefix)
- if err != nil {
- return gc.rmSection(keyPrefix)
- }
-
- m, err := gc.ReadAll(sectionFromKey(keyPrefix))
- if err != nil {
- return err
- }
-
- // if section doesn't have any left key/value remove the section
- if len(m) == 0 {
- return gc.rmSection(sectionFromKey(keyPrefix))
- }
-
- return nil
-}
-
-// RmConfigs remove all key/value pair matching the key prefix
-func (gc *gitConfig) RemoveAll(keyPrefix string) error {
- // starting from git 2.18.0 sections are automatically deleted when the last existing
- // key/value is removed. Before 2.18.0 we should remove the section
- // see https://github.com/git/git/blob/master/Documentation/RelNotes/2.18.0.txt#L379
- lt218, err := gc.gitVersionLT218()
- if err != nil {
- return errors.Wrap(err, "getting git version")
- }
-
- if lt218 {
- return gc.rmConfigsGitVersionLT218(keyPrefix)
- }
-
- err = gc.unsetAll(keyPrefix)
- if err != nil {
- return gc.rmSection(keyPrefix)
- }
-
- return nil
-}
-
-func (gc *gitConfig) gitVersion() (*semver.Version, error) {
- versionOut, err := gc.cli.runGitCommand("version")
- if err != nil {
- return nil, err
- }
- return parseGitVersion(versionOut)
-}
-
-func parseGitVersion(versionOut string) (*semver.Version, error) {
- // extract the version and truncate potential bad parts
- // ex: 2.23.0.rc1 instead of 2.23.0-rc1
- r := regexp.MustCompile(`(\d+\.){1,2}\d+`)
-
- extracted := r.FindString(versionOut)
- if extracted == "" {
- return nil, fmt.Errorf("unreadable git version %s", versionOut)
- }
-
- version, err := semver.Make(extracted)
- if err != nil {
- return nil, err
- }
-
- return &version, nil
-}
-
-func (gc *gitConfig) gitVersionLT218() (bool, error) {
- version, err := gc.gitVersion()
- if err != nil {
- return false, err
- }
-
- version218string := "2.18.0"
- gitVersion218, err := semver.Make(version218string)
- if err != nil {
- return false, err
- }
-
- return version.LT(gitVersion218), nil
-}
diff --git a/repository/git_test.go b/repository/git_test.go
deleted file mode 100644
index 6603e339..00000000
--- a/repository/git_test.go
+++ /dev/null
@@ -1,6 +0,0 @@
-// Package repository contains helper methods for working with the Git repo.
-package repository
-
-// func TestGitRepo(t *testing.T) {
-// RepoTest(t, CreateTestRepo, CleanupTestRepos)
-// }
diff --git a/repository/git_testing.go b/repository/git_testing.go
deleted file mode 100644
index 2168d53e..00000000
--- a/repository/git_testing.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package repository
-
-import (
- "io/ioutil"
- "log"
-
- "github.com/99designs/keyring"
-)
-
-// This is intended for testing only
-
-func CreateTestRepo(bare bool) TestedRepo {
- dir, err := ioutil.TempDir("", "")
- if err != nil {
- log.Fatal(err)
- }
-
- var creator func(string) (*GitRepo, error)
-
- if bare {
- creator = InitBareGitRepo
- } else {
- creator = InitGitRepo
- }
-
- repo, err := creator(dir)
- if err != nil {
- log.Fatal(err)
- }
-
- config := repo.LocalConfig()
- if err := config.StoreString("user.name", "testuser"); err != nil {
- log.Fatal("failed to set user.name for test repository: ", err)
- }
- if err := config.StoreString("user.email", "testuser@example.com"); err != nil {
- log.Fatal("failed to set user.email for test repository: ", err)
- }
-
- // make sure we use a mock keyring for testing to not interact with the global system
- return &replaceKeyring{
- TestedRepo: repo,
- keyring: keyring.NewArrayKeyring(nil),
- }
-}
-
-func SetupReposAndRemote() (repoA, repoB, remote TestedRepo) {
- repoA = CreateGoGitTestRepo(false)
- repoB = CreateGoGitTestRepo(false)
- remote = CreateGoGitTestRepo(true)
-
- err := repoA.AddRemote("origin", remote.GetLocalRemote())
- if err != nil {
- log.Fatal(err)
- }
-
- err = repoB.AddRemote("origin", remote.GetLocalRemote())
- if err != nil {
- log.Fatal(err)
- }
-
- return repoA, repoB, remote
-}
-
-// replaceKeyring allow to replace the Keyring of the underlying repo
-type replaceKeyring struct {
- TestedRepo
- keyring Keyring
-}
-
-func (rk replaceKeyring) Keyring() Keyring {
- return rk.keyring
-}
diff --git a/repository/gogit.go b/repository/gogit.go
index 64ccb773..d6eb8621 100644
--- a/repository/gogit.go
+++ b/repository/gogit.go
@@ -20,6 +20,7 @@ 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"
"github.com/MichaelMure/git-bug/util/lamport"
)
@@ -521,12 +522,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
@@ -547,8 +549,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()
@@ -608,6 +630,13 @@ func (repo *GoGitRepo) UpdateRef(ref string, hash Hash) error {
return repo.r.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName(ref), plumbing.NewHash(hash.String())))
}
+// MergeRef merge other into ref and update the reference
+// If the update is not fast-forward, the callback treeHashFn will be called for the caller to generate
+// the Tree to store in the merge commit.
+func (repo *GoGitRepo) MergeRef(ref string, otherRef string, treeHashFn func() Hash) error {
+ return nonNativeMerge(repo, ref, otherRef, treeHashFn)
+}
+
// RemoveRef will remove a Git reference
func (repo *GoGitRepo) RemoveRef(ref string) error {
return repo.r.Storer.RemoveReference(plumbing.ReferenceName(ref))
@@ -657,38 +686,16 @@ 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)
- if err != nil {
- return nil, err
- }
+ return nonNativeListCommits(repo, ref)
+}
- commit, err := repo.r.CommitObject(r.Hash())
+func (repo *GoGitRepo) ReadCommit(hash Hash) (Commit, error) {
+ encoded, err := repo.r.Storer.EncodedObject(plumbing.CommitObject, plumbing.NewHash(hash.String()))
if err != nil {
- return nil, err
- }
- hashes := []Hash{Hash(commit.Hash.String())}
-
- for {
- commit, err = commit.Parent(0)
- if err == object.ErrParentNotFound {
- break
- }
- if err != nil {
- return nil, err
- }
-
- if commit.NumParents() > 1 {
- return nil, fmt.Errorf("multiple parents")
- }
-
- hashes = append([]Hash{Hash(commit.Hash.String())}, hashes...)
+ return Commit{}, err
}
- return hashes, nil
-}
-
-func (repo *GoGitRepo) ReadCommit(hash Hash) (Commit, error) {
- commit, err := repo.r.CommitObject(plumbing.NewHash(hash.String()))
+ commit, err := object.DecodeCommit(repo.r.Storer, encoded)
if err != nil {
return Commit{}, err
}
@@ -698,12 +705,25 @@ func (repo *GoGitRepo) ReadCommit(hash Hash) (Commit, error) {
parents[i] = Hash(parentHash.String())
}
- return Commit{
+ result := Commit{
Hash: hash,
Parents: parents,
TreeHash: Hash(commit.TreeHash.String()),
- }, nil
+ }
+
+ if commit.PGPSignature != "" {
+ result.SignedData, err = encoded.Reader()
+ if err != nil {
+ return Commit{}, err
+ }
+ result.Signature, err = deArmorSignature(strings.NewReader(commit.PGPSignature))
+ if err != nil {
+ return Commit{}, err
+ }
+ }
+
+ return result, nil
}
func (repo *GoGitRepo) AllClocks() (map[string]lamport.Clock, error) {
diff --git a/repository/gogit_testing.go b/repository/gogit_testing.go
index a8bff41e..cad776b3 100644
--- a/repository/gogit_testing.go
+++ b/repository/gogit_testing.go
@@ -3,6 +3,8 @@ package repository
import (
"io/ioutil"
"log"
+
+ "github.com/99designs/keyring"
)
// This is intended for testing only
@@ -34,7 +36,11 @@ func CreateGoGitTestRepo(bare bool) TestedRepo {
log.Fatal("failed to set user.email for test repository: ", err)
}
- return repo
+ // make sure we use a mock keyring for testing to not interact with the global system
+ return &replaceKeyring{
+ TestedRepo: repo,
+ keyring: keyring.NewArrayKeyring(nil),
+ }
}
func SetupGoGitReposAndRemote() (repoA, repoB, remote TestedRepo) {
diff --git a/repository/keyring.go b/repository/keyring.go
index 4cb3c9ff..64365c39 100644
--- a/repository/keyring.go
+++ b/repository/keyring.go
@@ -48,3 +48,13 @@ func defaultKeyring() (Keyring, error) {
},
})
}
+
+// replaceKeyring allow to replace the Keyring of the underlying repo
+type replaceKeyring struct {
+ TestedRepo
+ keyring Keyring
+}
+
+func (rk replaceKeyring) Keyring() Keyring {
+ return rk.keyring
+}
diff --git a/repository/mock_repo.go b/repository/mock_repo.go
index 227e0f2c..095ad61c 100644
--- a/repository/mock_repo.go
+++ b/repository/mock_repo.go
@@ -1,6 +1,7 @@
package repository
import (
+ "bytes"
"crypto/sha1"
"fmt"
"strings"
@@ -10,6 +11,7 @@ import (
"github.com/blevesearch/bleve"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
+ "golang.org/x/crypto/openpgp"
"github.com/MichaelMure/git-bug/util/lamport"
)
@@ -180,6 +182,7 @@ var _ RepoData = &mockRepoData{}
type commit struct {
treeHash Hash
parents []Hash
+ sig string
}
type mockRepoData struct {
@@ -198,12 +201,12 @@ func NewMockRepoData() *mockRepoData {
}
}
-// PushRefs push git refs to a remote
-func (r *mockRepoData) PushRefs(remote string, refSpec string) (string, error) {
+func (r *mockRepoData) FetchRefs(remote string, refSpec string) (string, error) {
return "", nil
}
-func (r *mockRepoData) FetchRefs(remote string, refSpec string) (string, error) {
+// PushRefs push git refs to a remote
+func (r *mockRepoData) PushRefs(remote string, refSpec string) (string, error) {
return "", nil
}
@@ -216,7 +219,6 @@ func (r *mockRepoData) StoreData(data []byte) (Hash, error) {
func (r *mockRepoData) ReadData(hash Hash) ([]byte, error) {
data, ok := r.blobs[hash]
-
if !ok {
return nil, fmt.Errorf("unknown hash")
}
@@ -233,25 +235,86 @@ func (r *mockRepoData) StoreTree(entries []TreeEntry) (Hash, error) {
return hash, nil
}
-func (r *mockRepoData) StoreCommit(treeHash Hash) (Hash, error) {
- rawHash := sha1.Sum([]byte(treeHash))
- hash := Hash(fmt.Sprintf("%x", rawHash))
- r.commits[hash] = commit{
- treeHash: treeHash,
+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 hash, nil
+
+ return readTreeEntries(data)
+}
+
+func (r *mockRepoData) StoreCommit(treeHash Hash, parents ...Hash) (Hash, error) {
+ return r.StoreSignedCommit(treeHash, nil, parents...)
}
-func (r *mockRepoData) StoreCommitWithParent(treeHash Hash, parent Hash) (Hash, error) {
- rawHash := sha1.Sum([]byte(treeHash + parent))
+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))
- r.commits[hash] = commit{
+ c := commit{
treeHash: treeHash,
- parents: []Hash{parent},
+ 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 != "" {
+ 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 {
@@ -270,22 +333,6 @@ func (r *mockRepoData) RemoveRef(ref string) error {
return 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) ListRefs(refPrefix string) ([]string, error) {
var keys []string
@@ -298,63 +345,20 @@ func (r *mockRepoData) ListRefs(refPrefix string) ([]string, error) {
return keys, nil
}
-func (r *mockRepoData) ListCommits(ref string) ([]Hash, error) {
- var hashes []Hash
-
- hash := r.refs[ref]
-
- for {
- commit, ok := r.commits[hash]
-
- if !ok {
- break
- }
-
- hashes = append([]Hash{hash}, hashes...)
-
- if len(commit.parents) == 0 {
- break
- }
- hash = commit.parents[0]
- }
-
- return hashes, nil
-}
-
-func (r *mockRepoData) ReadCommit(hash Hash) (Commit, error) {
- c, ok := r.commits[hash]
- if !ok {
- return Commit{}, fmt.Errorf("unknown commit")
- }
-
- return Commit{
- Hash: hash,
- Parents: c.parents,
- TreeHash: c.treeHash,
- }, nil
+func (r *mockRepoData) RefExist(ref string) (bool, error) {
+ _, exist := r.refs[ref]
+ return exist, 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]
+func (r *mockRepoData) CopyRef(source string, dest string) error {
+ hash, exist := r.refs[source]
- if !ok {
- return nil, fmt.Errorf("unknown hash")
- }
+ if !exist {
+ return fmt.Errorf("Unknown ref")
}
- return readTreeEntries(data)
+ r.refs[dest] = hash
+ return nil
}
func (r *mockRepoData) FindCommonAncestor(hash1 Hash, hash2 Hash) (Hash, error) {
@@ -392,13 +396,8 @@ func (r *mockRepoData) FindCommonAncestor(hash1 Hash, hash2 Hash) (Hash, error)
}
}
-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) ListCommits(ref string) ([]Hash, error) {
+ return nonNativeListCommits(r, ref)
}
var _ RepoClock = &mockRepoClock{}
diff --git a/repository/mock_repo_test.go b/repository/mock_repo_test.go
index dec09380..12851a80 100644
--- a/repository/mock_repo_test.go
+++ b/repository/mock_repo_test.go
@@ -1,6 +1,8 @@
package repository
-import "testing"
+import (
+ "testing"
+)
func TestMockRepo(t *testing.T) {
creator := func(bare bool) TestedRepo { return NewMockRepo() }
diff --git a/repository/repo.go b/repository/repo.go
index afd8ff77..d7afa983 100644
--- a/repository/repo.go
+++ b/repository/repo.go
@@ -3,15 +3,17 @@ package repository
import (
"errors"
+ "io"
"github.com/blevesearch/bleve"
"github.com/go-git/go-billy/v5"
+ "golang.org/x/crypto/openpgp"
"github.com/MichaelMure/git-bug/util/lamport"
)
var (
- // ErrNotARepo is the error returned when the git repo root wan't be found
+ // ErrNotARepo is the error returned when the git repo root can't be found
ErrNotARepo = errors.New("not a git repository")
// ErrClockNotExist is the error returned when a clock can't be found
ErrClockNotExist = errors.New("clock doesn't exist")
@@ -89,9 +91,11 @@ type RepoBleve interface {
}
type Commit struct {
- Hash Hash
- Parents []Hash
- TreeHash Hash
+ Hash Hash
+ Parents []Hash // hashes of the parents, if any
+ TreeHash Hash // hash of the git Tree
+ SignedData io.Reader // if signed, reader for the signed data (likely, the serialized commit)
+ Signature io.Reader // if signed, reader for the (non-armored) signature
}
// RepoData give access to the git data storage
@@ -116,21 +120,29 @@ type RepoData interface {
ReadTree(hash Hash) ([]TreeEntry, error)
// StoreCommit will store a Git commit with the given Git tree
- StoreCommit(treeHash Hash) (Hash, error)
+ StoreCommit(treeHash Hash, parents ...Hash) (Hash, error)
- // StoreCommit will store a Git commit with the given Git tree
- 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.
+ StoreSignedCommit(treeHash Hash, signKey *openpgp.Entity, parents ...Hash) (Hash, error)
+ // ReadCommit read a Git commit and returns some of its characteristic
ReadCommit(hash Hash) (Commit, error)
// GetTreeHash return the git tree hash referenced in a commit
GetTreeHash(commit Hash) (Hash, error)
+ // ResolveRef returns the hash of the target commit of the given ref
ResolveRef(ref string) (Hash, error)
// UpdateRef will create or update a Git reference
UpdateRef(ref string, hash Hash) error
+ // // MergeRef merge other into ref and update the reference
+ // // If the update is not fast-forward, the callback treeHashFn will be called for the caller to generate
+ // // the Tree to store in the merge commit.
+ // MergeRef(ref string, otherRef string, treeHashFn func() Hash) error
+
// RemoveRef will remove a Git reference
RemoveRef(ref string) error
@@ -148,7 +160,6 @@ type RepoData interface {
FindCommonAncestor(commit1 Hash, commit2 Hash) (Hash, error)
// ListCommits will return the list of tree hashes of a ref, in chronological order
- // Deprecated
ListCommits(ref string) ([]Hash, error)
}
diff --git a/repository/repo_testing.go b/repository/repo_testing.go
index 1d3a3155..4a5c48bb 100644
--- a/repository/repo_testing.go
+++ b/repository/repo_testing.go
@@ -135,7 +135,8 @@ func RepoDataTest(t *testing.T, repo RepoData) {
require.NoError(t, err)
require.Equal(t, treeHash1, treeHash1Read)
- commit2, err := repo.StoreCommitWithParent(treeHash2, commit1)
+ // commit with a parent
+ commit2, err := repo.StoreCommit(treeHash2, commit1)
require.NoError(t, err)
require.True(t, commit2.IsValid())
@@ -187,7 +188,7 @@ func RepoDataTest(t *testing.T, repo RepoData) {
// Graph
- commit3, err := repo.StoreCommitWithParent(treeHash1, commit1)
+ commit3, err := repo.StoreCommit(treeHash1, commit1)
require.NoError(t, err)
ancestorHash, err := repo.FindCommonAncestor(commit2, commit3)
@@ -237,3 +238,22 @@ func randomData() []byte {
}
return b
}
+
+func makeCommit(t *testing.T, repo RepoData, parents ...Hash) Hash {
+ blobHash, err := repo.StoreData(randomData())
+ require.NoError(t, err)
+
+ treeHash, err := repo.StoreTree([]TreeEntry{
+ {
+ ObjectType: Blob,
+ Hash: blobHash,
+ Name: "foo",
+ },
+ })
+ require.NoError(t, err)
+
+ commitHash, err := repo.StoreCommit(treeHash, parents...)
+ require.NoError(t, err)
+
+ return commitHash
+}