aboutsummaryrefslogtreecommitdiffstats
path: root/repository
diff options
context:
space:
mode:
Diffstat (limited to 'repository')
-rw-r--r--repository/git.go801
-rw-r--r--repository/mock_repo.go53
-rw-r--r--repository/repo.go14
3 files changed, 117 insertions, 751 deletions
diff --git a/repository/git.go b/repository/git.go
index 0b7fad34..8c20d0b8 100644
--- a/repository/git.go
+++ b/repository/git.go
@@ -7,11 +7,14 @@ import (
"fmt"
"github.com/MichaelMure/git-bug/util"
"io"
- "os"
"os/exec"
"strings"
)
+// This is used to have a different staging area than the regular git index
+// when creating data in git
+const gitEnvConfig = "GIT_INDEX_FILE=BUG_STAGING_INDEX"
+
// GitRepo represents an instance of a (local) git repository.
type GitRepo struct {
Path string
@@ -24,20 +27,23 @@ func (repo *GitRepo) runGitCommandWithIO(stdin io.Reader, stdout, stderr io.Writ
cmd.Stdin = stdin
cmd.Stdout = stdout
cmd.Stderr = stderr
+
+ //cmd.Env = append(cmd.Env, gitEnvConfig)
+
return cmd.Run()
}
// Run the given git command and return its stdout, or an error if the command fails.
-func (repo *GitRepo) runGitCommandRaw(args ...string) (string, string, error) {
+func (repo *GitRepo) runGitCommandRaw(stdin io.Reader, args ...string) (string, string, error) {
var stdout bytes.Buffer
var stderr bytes.Buffer
- err := repo.runGitCommandWithIO(nil, &stdout, &stderr, args...)
+ err := repo.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 (repo *GitRepo) runGitCommand(args ...string) (string, error) {
- stdout, stderr, err := repo.runGitCommandRaw(args...)
+func (repo *GitRepo) runGitCommandWithStdin(stdin io.Reader, args ...string) (string, error) {
+ stdout, stderr, err := repo.runGitCommandRaw(stdin, args...)
if err != nil {
if stderr == "" {
stderr = "Error running git command: " + strings.Join(args, " ")
@@ -47,16 +53,16 @@ func (repo *GitRepo) runGitCommand(args ...string) (string, error) {
return stdout, err
}
-// Run the given git command using the same stdin, stdout, and stderr as the review tool.
-func (repo *GitRepo) runGitCommandInline(args ...string) error {
- return repo.runGitCommandWithIO(os.Stdin, os.Stdout, os.Stderr, args...)
+// Run the given git command and return its stdout, or an error if the command fails.
+func (repo *GitRepo) runGitCommand(args ...string) (string, error) {
+ return repo.runGitCommandWithStdin(nil, args...)
}
// NewGitRepo determines if the given working directory is inside of a git repository,
// and returns the corresponding GitRepo instance if it is.
func NewGitRepo(path string) (*GitRepo, error) {
repo := &GitRepo{Path: path}
- _, _, err := repo.runGitCommandRaw("rev-parse")
+ _, err := repo.runGitCommand("rev-parse")
if err == nil {
return repo, nil
}
@@ -95,7 +101,7 @@ func (repo *GitRepo) GetCoreEditor() (string, error) {
// PullRefs pull git refs from a remote
func (repo *GitRepo) PullRefs(remote string, refPattern string) error {
fetchRefSpec := fmt.Sprintf("+%s:%s", refPattern, refPattern)
- err := repo.runGitCommandInline("fetch", remote, fetchRefSpec)
+ _, err := repo.runGitCommand("fetch", remote, fetchRefSpec)
// TODO: merge new data
@@ -106,7 +112,7 @@ func (repo *GitRepo) PullRefs(remote string, refPattern string) error {
func (repo *GitRepo) PushRefs(remote string, refPattern string) error {
// The push is liable to fail if the user forgot to do a pull first, so
// we treat errors as user errors rather than fatal errors.
- err := repo.runGitCommandInline("push", remote, refPattern)
+ _, err := repo.runGitCommand("push", remote, refPattern)
if err != nil {
return fmt.Errorf("failed to push to the remote '%s': %v", remote, err)
}
@@ -116,733 +122,54 @@ func (repo *GitRepo) PushRefs(remote string, refPattern string) error {
// StoreData will store arbitrary data and return the corresponding hash
func (repo *GitRepo) StoreData(data []byte) (util.Hash, error) {
var stdin = bytes.NewReader(data)
- var stdout bytes.Buffer
- var stderr bytes.Buffer
- err := repo.runGitCommandWithIO(stdin, &stdout, &stderr, "hash-object", "--stdin", "-w")
+ stdout, err := repo.runGitCommandWithStdin(stdin, "hash-object", "--stdin", "-w")
+
+ return util.Hash(stdout), err
+}
+
+// StoreTree will store a mapping key-->Hash as a Git tree
+func (repo *GitRepo) StoreTree(mapping map[string]util.Hash) (util.Hash, error) {
+ var buffer bytes.Buffer
+
+ for key, hash := range mapping {
+ buffer.WriteString(fmt.Sprintf("100644 blob %s\t%s\n", hash, key))
+ }
+
+ stdout, err := repo.runGitCommandWithStdin(&buffer, "mktree")
+ if err != nil {
+ return "", err
+ }
- return util.Hash(stdout.String()), err
+ return util.Hash(stdout), nil
}
-/*
-//
-//// GetSubmitStrategy returns the way in which a review is submitted
-//func (repo *GitRepo) GetSubmitStrategy() (string, error) {
-// submitStrategy, _ := repo.runGitCommand("config", "appraise.submit")
-// return submitStrategy, nil
-//}
-//
-//// HasUncommittedChanges returns true if there are local, uncommitted changes.
-//func (repo *GitRepo) HasUncommittedChanges() (bool, error) {
-// out, err := repo.runGitCommand("status", "--porcelain")
-// if err != nil {
-// return false, err
-// }
-// if len(out) > 0 {
-// return true, nil
-// }
-// return false, nil
-//}
-//
-//// VerifyCommit verifies that the supplied hash points to a known commit.
-//func (repo *GitRepo) VerifyCommit(hash string) error {
-// out, err := repo.runGitCommand("cat-file", "-t", hash)
-// if err != nil {
-// return err
-// }
-// objectType := strings.TrimSpace(string(out))
-// if objectType != "commit" {
-// return fmt.Errorf("Hash %q points to a non-commit object of type %q", hash, objectType)
-// }
-// return nil
-//}
-//
-//// VerifyGitRef verifies that the supplied ref points to a known commit.
-//func (repo *GitRepo) VerifyGitRef(ref string) error {
-// _, err := repo.runGitCommand("show-ref", "--verify", ref)
-// return err
-//}
-//
-//// GetHeadRef returns the ref that is the current HEAD.
-//func (repo *GitRepo) GetHeadRef() (string, error) {
-// return repo.runGitCommand("symbolic-ref", "HEAD")
-//}
-//
-//// GetCommitHash returns the hash of the commit pointed to by the given ref.
-//func (repo *GitRepo) GetCommitHash(ref string) (string, error) {
-// return repo.runGitCommand("show", "-s", "--format=%H", ref)
-//}
-//
-//// ResolveRefCommit returns the commit pointed to by the given ref, which may be a remote ref.
-////
-//// This differs from GetCommitHash which only works on exact matches, in that it will try to
-//// intelligently handle the scenario of a ref not existing locally, but being known to exist
-//// in a remote repo.
-////
-//// This method should be used when a command may be performed by either the reviewer or the
-//// reviewee, while GetCommitHash should be used when the encompassing command should only be
-//// performed by the reviewee.
-//func (repo *GitRepo) ResolveRefCommit(ref string) (string, error) {
-// if err := repo.VerifyGitRef(ref); err == nil {
-// return repo.GetCommitHash(ref)
-// }
-// if strings.HasPrefix(ref, "refs/heads/") {
-// // The ref is a branch. Check if it exists in exactly one remote
-// pattern := strings.Replace(ref, "refs/heads", "**", 1)
-// matchingOutput, err := repo.runGitCommand("for-each-ref", "--format=%(refname)", pattern)
-// if err != nil {
-// return "", err
-// }
-// matchingRefs := strings.Split(matchingOutput, "\n")
-// if len(matchingRefs) == 1 && matchingRefs[0] != "" {
-// // There is exactly one match
-// return repo.GetCommitHash(matchingRefs[0])
-// }
-// return "", fmt.Errorf("Unable to find a git ref matching the pattern %q", pattern)
-// }
-// return "", fmt.Errorf("Unknown git ref %q", ref)
-//}
-//
-//// GetCommitMessage returns the message stored in the commit pointed to by the given ref.
-//func (repo *GitRepo) GetCommitMessage(ref string) (string, error) {
-// return repo.runGitCommand("show", "-s", "--format=%B", ref)
-//}
-//
-//// GetCommitTime returns the commit time of the commit pointed to by the given ref.
-//func (repo *GitRepo) GetCommitTime(ref string) (string, error) {
-// return repo.runGitCommand("show", "-s", "--format=%ct", ref)
-//}
-//
-//// GetLastParent returns the last parent of the given commit (as ordered by git).
-//func (repo *GitRepo) GetLastParent(ref string) (string, error) {
-// return repo.runGitCommand("rev-list", "--skip", "1", "-n", "1", ref)
-//}
-//
-//// GetCommitDetails returns the details of a commit's metadata.
-//func (repo GitRepo) GetCommitDetails(ref string) (*CommitDetails, error) {
-// var err error
-// show := func(formatString string) (result string) {
-// if err != nil {
-// return ""
-// }
-// result, err = repo.runGitCommand("show", "-s", ref, fmt.Sprintf("--format=tformat:%s", formatString))
-// return result
-// }
-//
-// jsonFormatString := "{\"tree\":\"%T\", \"time\": \"%at\"}"
-// detailsJSON := show(jsonFormatString)
-// if err != nil {
-// return nil, err
-// }
-// var details CommitDetails
-// err = json.Unmarshal([]byte(detailsJSON), &details)
-// if err != nil {
-// return nil, err
-// }
-// details.Author = show("%an")
-// details.AuthorEmail = show("%ae")
-// details.Summary = show("%s")
-// parentsString := show("%P")
-// details.Parents = strings.Split(parentsString, " ")
-// if err != nil {
-// return nil, err
-// }
-// return &details, nil
-//}
-//
-//// MergeBase determines if the first commit that is an ancestor of the two arguments.
-//func (repo *GitRepo) MergeBase(a, b string) (string, error) {
-// return repo.runGitCommand("merge-base", a, b)
-//}
-//
-//// IsAncestor determines if the first argument points to a commit that is an ancestor of the second.
-//func (repo *GitRepo) IsAncestor(ancestor, descendant string) (bool, error) {
-// _, _, err := repo.runGitCommandRaw("merge-base", "--is-ancestor", ancestor, descendant)
-// if err == nil {
-// return true, nil
-// }
-// if _, ok := err.(*exec.ExitError); ok {
-// return false, nil
-// }
-// return false, fmt.Errorf("Error while trying to determine commit ancestry: %v", err)
-//}
-//
-//// Diff computes the diff between two given commits.
-//func (repo *GitRepo) Diff(left, right string, diffArgs ...string) (string, error) {
-// args := []string{"diff"}
-// args = append(args, diffArgs...)
-// args = append(args, fmt.Sprintf("%s..%s", left, right))
-// return repo.runGitCommand(args...)
-//}
-//
-//// Show returns the contents of the given file at the given commit.
-//func (repo *GitRepo) Show(commit, path string) (string, error) {
-// return repo.runGitCommand("show", fmt.Sprintf("%s:%s", commit, path))
-//}
-//
-//// SwitchToRef changes the currently-checked-out ref.
-//func (repo *GitRepo) SwitchToRef(ref string) error {
-// // If the ref starts with "refs/heads/", then we have to trim that prefix,
-// // or else we will wind up in a detached HEAD state.
-// if strings.HasPrefix(ref, branchRefPrefix) {
-// ref = ref[len(branchRefPrefix):]
-// }
-// _, err := repo.runGitCommand("checkout", ref)
-// return err
-//}
-//
-//// mergeArchives merges two archive refs.
-//func (repo *GitRepo) mergeArchives(archive, remoteArchive string) error {
-// remoteHash, err := repo.GetCommitHash(remoteArchive)
-// if err != nil {
-// return err
-// }
-// if remoteHash == "" {
-// // The remote archive does not exist, so we have nothing to do
-// return nil
-// }
-//
-// archiveHash, err := repo.GetCommitHash(archive)
-// if err != nil {
-// return err
-// }
-// if archiveHash == "" {
-// // The local archive does not exist, so we merely need to set it
-// _, err := repo.runGitCommand("update-ref", archive, remoteHash)
-// return err
-// }
-//
-// isAncestor, err := repo.IsAncestor(archiveHash, remoteHash)
-// if err != nil {
-// return err
-// }
-// if isAncestor {
-// // The archive can simply be fast-forwarded
-// _, err := repo.runGitCommand("update-ref", archive, remoteHash, archiveHash)
-// return err
-// }
-//
-// // Create a merge commit of the two archives
-// refDetails, err := repo.GetCommitDetails(remoteArchive)
-// if err != nil {
-// return err
-// }
-// newArchiveHash, err := repo.runGitCommand("commit-tree", "-p", remoteHash, "-p", archiveHash, "-m", "Merge local and remote archives", refDetails.Tree)
-// if err != nil {
-// return err
-// }
-// newArchiveHash = strings.TrimSpace(newArchiveHash)
-// _, err = repo.runGitCommand("update-ref", archive, newArchiveHash, archiveHash)
-// return err
-//}
-//
-//// ArchiveRef adds the current commit pointed to by the 'ref' argument
-//// under the ref specified in the 'archive' argument.
-////
-//// Both the 'ref' and 'archive' arguments are expected to be the fully
-//// qualified names of git refs (e.g. 'refs/heads/my-change' or
-//// 'refs/devtools/archives/reviews').
-////
-//// If the ref pointed to by the 'archive' argument does not exist
-//// yet, then it will be created.
-//func (repo *GitRepo) ArchiveRef(ref, archive string) error {
-// refHash, err := repo.GetCommitHash(ref)
-// if err != nil {
-// return err
-// }
-// refDetails, err := repo.GetCommitDetails(ref)
-// if err != nil {
-// return err
-// }
-//
-// commitTreeArgs := []string{"commit-tree"}
-// archiveHash, err := repo.GetCommitHash(archive)
-// if err != nil {
-// archiveHash = ""
-// } else {
-// commitTreeArgs = append(commitTreeArgs, "-p", archiveHash)
-// }
-// commitTreeArgs = append(commitTreeArgs, "-p", refHash, "-m", fmt.Sprintf("Archive %s", refHash), refDetails.Tree)
-// newArchiveHash, err := repo.runGitCommand(commitTreeArgs...)
-// if err != nil {
-// return err
-// }
-// newArchiveHash = strings.TrimSpace(newArchiveHash)
-// updateRefArgs := []string{"update-ref", archive, newArchiveHash}
-// if archiveHash != "" {
-// updateRefArgs = append(updateRefArgs, archiveHash)
-// }
-// _, err = repo.runGitCommand(updateRefArgs...)
-// return err
-//}
-//
-//// MergeRef merges the given ref into the current one.
-////
-//// The ref argument is the ref to merge, and fastForward indicates that the
-//// current ref should only move forward, as opposed to creating a bubble merge.
-//// The messages argument(s) provide text that should be included in the default
-//// merge commit message (separated by blank lines).
-//func (repo *GitRepo) MergeRef(ref string, fastForward bool, messages ...string) error {
-// args := []string{"merge"}
-// if fastForward {
-// args = append(args, "--ff", "--ff-only")
-// } else {
-// args = append(args, "--no-ff")
-// }
-// if len(messages) > 0 {
-// commitMessage := strings.Join(messages, "\n\n")
-// args = append(args, "-e", "-m", commitMessage)
-// }
-// args = append(args, ref)
-// return repo.runGitCommandInline(args...)
-//}
-//
-//// RebaseRef rebases the current ref onto the given one.
-//func (repo *GitRepo) RebaseRef(ref string) error {
-// return repo.runGitCommandInline("rebase", "-i", ref)
-//}
-//
-//// ListCommits returns the list of commits reachable from the given ref.
-////
-//// The generated list is in chronological order (with the oldest commit first).
-////
-//// If the specified ref does not exist, then this method returns an empty result.
-//func (repo *GitRepo) ListCommits(ref string) []string {
-// var stdout bytes.Buffer
-// var stderr bytes.Buffer
-// if err := repo.runGitCommandWithIO(nil, &stdout, &stderr, "rev-list", "--reverse", ref); err != nil {
-// return nil
-// }
-//
-// byteLines := bytes.Split(stdout.Bytes(), []byte("\n"))
-// var commits []string
-// for _, byteLine := range byteLines {
-// commits = append(commits, string(byteLine))
-// }
-// return commits
-//}
-//
-//// ListCommitsBetween returns the list of commits between the two given revisions.
-////
-//// The "from" parameter is the starting point (exclusive), and the "to"
-//// parameter is the ending point (inclusive).
-////
-//// The "from" commit does not need to be an ancestor of the "to" commit. If it
-//// is not, then the merge base of the two is used as the starting point.
-//// Admittedly, this makes calling these the "between" commits is a bit of a
-//// misnomer, but it also makes the method easier to use when you want to
-//// generate the list of changes in a feature branch, as it eliminates the need
-//// to explicitly calculate the merge base. This also makes the semantics of the
-//// method compatible with git's built-in "rev-list" command.
-////
-//// The generated list is in chronological order (with the oldest commit first).
-//func (repo *GitRepo) ListCommitsBetween(from, to string) ([]string, error) {
-// out, err := repo.runGitCommand("rev-list", "--reverse", from+".."+to)
-// if err != nil {
-// return nil, err
-// }
-// if out == "" {
-// return nil, nil
-// }
-// return strings.Split(out, "\n"), nil
-//}
-//
-//// GetNotes uses the "git" command-line tool to read the notes from the given ref for a given revision.
-//func (repo *GitRepo) GetNotes(notesRef, revision string) []Note {
-// var notes []Note
-// rawNotes, err := repo.runGitCommand("notes", "--ref", notesRef, "show", revision)
-// if err != nil {
-// // We just assume that this means there are no notes
-// return nil
-// }
-// for _, line := range strings.Split(rawNotes, "\n") {
-// notes = append(notes, Note([]byte(line)))
-// }
-// return notes
-//}
-//
-//func stringsReader(s []*string) io.Reader {
-// var subReaders []io.Reader
-// for _, strPtr := range s {
-// subReader := strings.NewReader(*strPtr)
-// subReaders = append(subReaders, subReader, strings.NewReader("\n"))
-// }
-// return io.MultiReader(subReaders...)
-//}
-//
-//// splitBatchCheckOutput parses the output of a 'git cat-file --batch-check=...' command.
-////
-//// The output is expected to be formatted as a series of entries, with each
-//// entry consisting of:
-//// 1. The SHA1 hash of the git object being output, followed by a space.
-//// 2. The git "type" of the object (commit, blob, tree, missing, etc), followed by a newline.
-////
-//// To generate this format, make sure that the 'git cat-file' command includes
-//// the argument '--batch-check=%(objectname) %(objecttype)'.
-////
-//// The return value is a map from object hash to a boolean indicating if that object is a commit.
-//func splitBatchCheckOutput(out *bytes.Buffer) (map[string]bool, error) {
-// isCommit := make(map[string]bool)
-// reader := bufio.NewReader(out)
-// for {
-// nameLine, err := reader.ReadString(byte(' '))
-// if err == io.EOF {
-// return isCommit, nil
-// }
-// if err != nil {
-// return nil, fmt.Errorf("Failure while reading the next object name: %v", err)
-// }
-// nameLine = strings.TrimSuffix(nameLine, " ")
-// typeLine, err := reader.ReadString(byte('\n'))
-// if err != nil && err != io.EOF {
-// return nil, fmt.Errorf("Failure while reading the next object type: %q - %v", nameLine, err)
-// }
-// typeLine = strings.TrimSuffix(typeLine, "\n")
-// if typeLine == "commit" {
-// isCommit[nameLine] = true
-// }
-// }
-//}
-//
-//// splitBatchCatFileOutput parses the output of a 'git cat-file --batch=...' command.
-////
-//// The output is expected to be formatted as a series of entries, with each
-//// entry consisting of:
-//// 1. The SHA1 hash of the git object being output, followed by a newline.
-//// 2. The size of the object's contents in bytes, followed by a newline.
-//// 3. The objects contents.
-////
-//// To generate this format, make sure that the 'git cat-file' command includes
-//// the argument '--batch=%(objectname)\n%(objectsize)'.
-//func splitBatchCatFileOutput(out *bytes.Buffer) (map[string][]byte, error) {
-// contentsMap := make(map[string][]byte)
-// reader := bufio.NewReader(out)
-// for {
-// nameLine, err := reader.ReadString(byte('\n'))
-// if strings.HasSuffix(nameLine, "\n") {
-// nameLine = strings.TrimSuffix(nameLine, "\n")
-// }
-// if err == io.EOF {
-// return contentsMap, nil
-// }
-// if err != nil {
-// return nil, fmt.Errorf("Failure while reading the next object name: %v", err)
-// }
-// sizeLine, err := reader.ReadString(byte('\n'))
-// if strings.HasSuffix(sizeLine, "\n") {
-// sizeLine = strings.TrimSuffix(sizeLine, "\n")
-// }
-// if err != nil {
-// return nil, fmt.Errorf("Failure while reading the next object size: %q - %v", nameLine, err)
-// }
-// size, err := strconv.Atoi(sizeLine)
-// if err != nil {
-// return nil, fmt.Errorf("Failure while parsing the next object size: %q - %v", nameLine, err)
-// }
-// contentBytes := make([]byte, size, size)
-// readDest := contentBytes
-// len := 0
-// err = nil
-// for err == nil && len < size {
-// nextLen := 0
-// nextLen, err = reader.Read(readDest)
-// len += nextLen
-// readDest = contentBytes[len:]
-// }
-// contentsMap[nameLine] = contentBytes
-// if err == io.EOF {
-// return contentsMap, nil
-// }
-// if err != nil {
-// return nil, err
-// }
-// for bs, err := reader.Peek(1); err == nil && bs[0] == byte('\n'); bs, err = reader.Peek(1) {
-// reader.ReadByte()
-// }
-// }
-//}
-//
-//// notesMapping represents the association between a git object and the notes for that object.
-//type notesMapping struct {
-// ObjectHash *string
-// NotesHash *string
-//}
-//
-//// notesOverview represents a high-level overview of all the notes under a single notes ref.
-//type notesOverview struct {
-// NotesMappings []*notesMapping
-// ObjectHashesReader io.Reader
-// NotesHashesReader io.Reader
-//}
-//
-//// notesOverview returns an overview of the git notes stored under the given ref.
-//func (repo *GitRepo) notesOverview(notesRef string) (*notesOverview, error) {
-// var stdout bytes.Buffer
-// var stderr bytes.Buffer
-// if err := repo.runGitCommandWithIO(nil, &stdout, &stderr, "notes", "--ref", notesRef, "list"); err != nil {
-// return nil, err
-// }
-//
-// var notesMappings []*notesMapping
-// var objHashes []*string
-// var notesHashes []*string
-// outScanner := bufio.NewScanner(&stdout)
-// for outScanner.Scan() {
-// line := outScanner.Text()
-// lineParts := strings.Split(line, " ")
-// if len(lineParts) != 2 {
-// return nil, fmt.Errorf("Malformed output line from 'git-notes list': %q", line)
-// }
-// objHash := &lineParts[1]
-// notesHash := &lineParts[0]
-// notesMappings = append(notesMappings, &notesMapping{
-// ObjectHash: objHash,
-// NotesHash: notesHash,
-// })
-// objHashes = append(objHashes, objHash)
-// notesHashes = append(notesHashes, notesHash)
-// }
-// err := outScanner.Err()
-// if err != nil && err != io.EOF {
-// return nil, fmt.Errorf("Failure parsing the output of 'git-notes list': %v", err)
-// }
-// return &notesOverview{
-// NotesMappings: notesMappings,
-// ObjectHashesReader: stringsReader(objHashes),
-// NotesHashesReader: stringsReader(notesHashes),
-// }, nil
-//}
-//
-//// getIsCommitMap returns a mapping of all the annotated objects that are commits.
-//func (overview *notesOverview) getIsCommitMap(repo *GitRepo) (map[string]bool, error) {
-// var stdout bytes.Buffer
-// var stderr bytes.Buffer
-// if err := repo.runGitCommandWithIO(overview.ObjectHashesReader, &stdout, &stderr, "cat-file", "--batch-check=%(objectname) %(objecttype)"); err != nil {
-// return nil, fmt.Errorf("Failure performing a batch file check: %v", err)
-// }
-// isCommit, err := splitBatchCheckOutput(&stdout)
-// if err != nil {
-// return nil, fmt.Errorf("Failure parsing the output of a batch file check: %v", err)
-// }
-// return isCommit, nil
-//}
-//
-//// getNoteContentsMap returns a mapping from all the notes hashes to their contents.
-//func (overview *notesOverview) getNoteContentsMap(repo *GitRepo) (map[string][]byte, error) {
-// var stdout bytes.Buffer
-// var stderr bytes.Buffer
-// if err := repo.runGitCommandWithIO(overview.NotesHashesReader, &stdout, &stderr, "cat-file", "--batch=%(objectname)\n%(objectsize)"); err != nil {
-// return nil, fmt.Errorf("Failure performing a batch file read: %v", err)
-// }
-// noteContentsMap, err := splitBatchCatFileOutput(&stdout)
-// if err != nil {
-// return nil, fmt.Errorf("Failure parsing the output of a batch file read: %v", err)
-// }
-// return noteContentsMap, nil
-//}
-//
-//// GetAllNotes reads the contents of the notes under the given ref for every commit.
-////
-//// The returned value is a mapping from commit hash to the list of notes for that commit.
-////
-//// This is the batch version of the corresponding GetNotes(...) method.
-//func (repo *GitRepo) GetAllNotes(notesRef string) (map[string][]Note, error) {
-// // This code is unfortunately quite complicated, but it needs to be so.
-// //
-// // Conceptually, this is equivalent to:
-// // result := make(map[string][]Note)
-// // for _, commit := range repo.ListNotedRevisions(notesRef) {
-// // result[commit] = repo.GetNotes(notesRef, commit)
-// // }
-// // return result, nil
-// //
-// // However, that logic would require separate executions of the 'git'
-// // command for every annotated commit. For a repo with 10s of thousands
-// // of reviews, that would mean calling Cmd.Run(...) 10s of thousands of
-// // times. That, in turn, would take so long that the tool would be unusable.
-// //
-// // This method avoids that by taking advantage of the 'git cat-file --batch="..."'
-// // command. That allows us to use a single invocation of Cmd.Run(...) to
-// // inspect multiple git objects at once.
-// //
-// // As such, regardless of the number of reviews in a repo, we can get all
-// // of the notes using a total of three invocations of Cmd.Run(...):
-// // 1. One to list all the annotated objects (and their notes hash)
-// // 2. A second one to filter out all of the annotated objects that are not commits.
-// // 3. A final one to get the contents of all of the notes blobs.
-// overview, err := repo.notesOverview(notesRef)
-// if err != nil {
-// return nil, err
-// }
-// isCommit, err := overview.getIsCommitMap(repo)
-// if err != nil {
-// return nil, fmt.Errorf("Failure building the set of commit objects: %v", err)
-// }
-// noteContentsMap, err := overview.getNoteContentsMap(repo)
-// if err != nil {
-// return nil, fmt.Errorf("Failure building the mapping from notes hash to contents: %v", err)
-// }
-// commitNotesMap := make(map[string][]Note)
-// for _, notesMapping := range overview.NotesMappings {
-// if !isCommit[*notesMapping.ObjectHash] {
-// continue
-// }
-// noteBytes := noteContentsMap[*notesMapping.NotesHash]
-// byteSlices := bytes.Split(noteBytes, []byte("\n"))
-// var notes []Note
-// for _, slice := range byteSlices {
-// notes = append(notes, Note(slice))
-// }
-// commitNotesMap[*notesMapping.ObjectHash] = notes
-// }
-//
-// return commitNotesMap, nil
-//}
-//
-//// AppendNote appends a note to a revision under the given ref.
-//func (repo *GitRepo) AppendNote(notesRef, revision string, note Note) error {
-// _, err := repo.runGitCommand("notes", "--ref", notesRef, "append", "-m", string(note), revision)
-// return err
-//}
-//
-//// ListNotedRevisions returns the collection of revisions that are annotated by notes in the given ref.
-//func (repo *GitRepo) ListNotedRevisions(notesRef string) []string {
-// var revisions []string
-// notesListOut, err := repo.runGitCommand("notes", "--ref", notesRef, "list")
-// if err != nil {
-// return nil
-// }
-// notesList := strings.Split(notesListOut, "\n")
-// for _, notePair := range notesList {
-// noteParts := strings.SplitN(notePair, " ", 2)
-// if len(noteParts) == 2 {
-// objHash := noteParts[1]
-// objType, err := repo.runGitCommand("cat-file", "-t", objHash)
-// // If a note points to an object that we do not know about (yet), then err will not
-// // be nil. We can safely just ignore those notes.
-// if err == nil && objType == "commit" {
-// revisions = append(revisions, objHash)
-// }
-// }
-// }
-// return revisions
-//}
-//
-//// PushNotes pushes git notes to a remote repo.
-//func (repo *GitRepo) PushNotes(remote, notesRefPattern string) error {
-// refspec := fmt.Sprintf("%s:%s", notesRefPattern, notesRefPattern)
-//
-// // The push is liable to fail if the user forgot to do a pull first, so
-// // we treat errors as user errors rather than fatal errors.
-// err := repo.runGitCommandInline("push", remote, refspec)
-// if err != nil {
-// return fmt.Errorf("Failed to push to the remote '%s': %v", remote, err)
-// }
-// return nil
-//}
-//
-//// PushNotesAndArchive pushes the given notes and archive refs to a remote repo.
-//func (repo *GitRepo) PushNotesAndArchive(remote, notesRefPattern, archiveRefPattern string) error {
-// notesRefspec := fmt.Sprintf("%s:%s", notesRefPattern, notesRefPattern)
-// archiveRefspec := fmt.Sprintf("%s:%s", archiveRefPattern, archiveRefPattern)
-// err := repo.runGitCommandInline("push", remote, notesRefspec, archiveRefspec)
-// if err != nil {
-// return fmt.Errorf("Failed to push the local archive to the remote '%s': %v", remote, err)
-// }
-// return nil
-//}
-//
-//func getRemoteNotesRef(remote, localNotesRef string) string {
-// relativeNotesRef := strings.TrimPrefix(localNotesRef, "refs/notes/")
-// return "refs/notes/" + remote + "/" + relativeNotesRef
-//}
-//
-//func (repo *GitRepo) mergeRemoteNotes(remote, notesRefPattern string) error {
-// remoteRefs, err := repo.runGitCommand("ls-remote", remote, notesRefPattern)
-// if err != nil {
-// return err
-// }
-// for _, line := range strings.Split(remoteRefs, "\n") {
-// lineParts := strings.Split(line, "\t")
-// if len(lineParts) == 2 {
-// ref := lineParts[1]
-// remoteRef := getRemoteNotesRef(remote, ref)
-// _, err := repo.runGitCommand("notes", "--ref", ref, "merge", remoteRef, "-s", "cat_sort_uniq")
-// if err != nil {
-// return err
-// }
-// }
-// }
-// return nil
-//}
-//
-//// PullNotes fetches the contents of the given notes ref from a remote repo,
-//// and then merges them with the corresponding local notes using the
-//// "cat_sort_uniq" strategy.
-//func (repo *GitRepo) PullNotes(remote, notesRefPattern string) error {
-// remoteNotesRefPattern := getRemoteNotesRef(remote, notesRefPattern)
-// fetchRefSpec := fmt.Sprintf("+%s:%s", notesRefPattern, remoteNotesRefPattern)
-// err := repo.runGitCommandInline("fetch", remote, fetchRefSpec)
-// if err != nil {
-// return err
-// }
-//
-// return repo.mergeRemoteNotes(remote, notesRefPattern)
-//}
-//
-//func getRemoteArchiveRef(remote, archiveRefPattern string) string {
-// relativeArchiveRef := strings.TrimPrefix(archiveRefPattern, "refs/devtools/archives/")
-// return "refs/devtools/remoteArchives/" + remote + "/" + relativeArchiveRef
-//}
-//
-//func (repo *GitRepo) mergeRemoteArchives(remote, archiveRefPattern string) error {
-// remoteRefs, err := repo.runGitCommand("ls-remote", remote, archiveRefPattern)
-// if err != nil {
-// return err
-// }
-// for _, line := range strings.Split(remoteRefs, "\n") {
-// lineParts := strings.Split(line, "\t")
-// if len(lineParts) == 2 {
-// ref := lineParts[1]
-// remoteRef := getRemoteArchiveRef(remote, ref)
-// if err := repo.mergeArchives(ref, remoteRef); err != nil {
-// return err
-// }
-// }
-// }
-// return nil
-//}
-//
-//// PullNotesAndArchive fetches the contents of the notes and archives refs from
-//// a remote repo, and merges them with the corresponding local refs.
-////
-//// For notes refs, we assume that every note can be automatically merged using
-//// the 'cat_sort_uniq' strategy (the git-appraise schemas fit that requirement),
-//// so we automatically merge the remote notes into the local notes.
-////
-//// For "archive" refs, they are expected to be used solely for maintaining
-//// reachability of commits that are part of the history of any reviews,
-//// so we do not maintain any consistency with their tree objects. Instead,
-//// we merely ensure that their history graph includes every commit that we
-//// intend to keep.
-//func (repo *GitRepo) PullNotesAndArchive(remote, notesRefPattern, archiveRefPattern string) error {
-// remoteArchiveRef := getRemoteArchiveRef(remote, archiveRefPattern)
-// archiveFetchRefSpec := fmt.Sprintf("+%s:%s", archiveRefPattern, remoteArchiveRef)
-//
-// remoteNotesRefPattern := getRemoteNotesRef(remote, notesRefPattern)
-// notesFetchRefSpec := fmt.Sprintf("+%s:%s", notesRefPattern, remoteNotesRefPattern)
-//
-// err := repo.runGitCommandInline("fetch", remote, notesFetchRefSpec, archiveFetchRefSpec)
-// if err != nil {
-// return err
-// }
-//
-// if err := repo.mergeRemoteNotes(remote, notesRefPattern); err != nil {
-// return err
-// }
-// if err := repo.mergeRemoteArchives(remote, archiveRefPattern); err != nil {
-// return err
-// }
-// return nil
-//}
-*/
+// StoreCommit will store a Git commit with the given Git tree
+func (repo *GitRepo) StoreCommit(treeHash util.Hash) (util.Hash, error) {
+ stdout, err := repo.runGitCommand("commit-tree", string(treeHash))
+
+ if err != nil {
+ return "", err
+ }
+
+ return util.Hash(stdout), nil
+}
+
+// StoreCommitWithParent will store a Git commit with the given Git tree
+func (repo *GitRepo) StoreCommitWithParent(treeHash util.Hash, parent util.Hash) (util.Hash, error) {
+ stdout, err := repo.runGitCommand("commit-tree", string(treeHash),
+ "-p", string(parent))
+
+ if err != nil {
+ return "", err
+ }
+
+ return util.Hash(stdout), nil
+}
+
+// UpdateRef will create or update a Git reference
+func (repo *GitRepo) UpdateRef(ref string, hash util.Hash) error {
+ _, err := repo.runGitCommand("update-ref", ref, string(hash))
+
+ return err
+}
diff --git a/repository/mock_repo.go b/repository/mock_repo.go
index f5d27d12..e747eb24 100644
--- a/repository/mock_repo.go
+++ b/repository/mock_repo.go
@@ -1,33 +1,60 @@
package repository
import (
- "crypto/sha1"
- "encoding/json"
- "fmt"
+ "github.com/MichaelMure/git-bug/util"
)
// mockRepoForTest defines an instance of Repo that can be used for testing.
type mockRepoForTest struct{}
+func NewMockRepoForTest() Repo {
+ return &mockRepoForTest{}
+}
+
// GetPath returns the path to the repo.
-func (r *mockRepoForTest) GetPath() string { return "~/mockRepo/" }
+func (r *mockRepoForTest) GetPath() string {
+ return "~/mockRepo/"
+}
-// GetRepoStateHash returns a hash which embodies the entire current state of a repository.
-func (r *mockRepoForTest) GetRepoStateHash() (string, error) {
- repoJSON, err := json.Marshal(r)
- if err != nil {
- return "", err
- }
- return fmt.Sprintf("%x", sha1.Sum([]byte(repoJSON))), nil
+func (r *mockRepoForTest) GetUserName() (string, error) {
+ return "René Descartes", nil
}
// GetUserEmail returns the email address that the user has used to configure git.
-func (r *mockRepoForTest) GetUserEmail() (string, error) { return "user@example.com", nil }
+func (r *mockRepoForTest) 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 *mockRepoForTest) GetCoreEditor() (string, error) { return "vi", nil }
+func (r *mockRepoForTest) GetCoreEditor() (string, error) {
+ return "vi", nil
+}
// PushRefs push git refs to a remote
func (r *mockRepoForTest) PushRefs(remote string, refPattern string) error {
return nil
}
+
+func (r *mockRepoForTest) PullRefs(remote string, refPattern string) error {
+ return nil
+}
+
+func (r *mockRepoForTest) StoreData([]byte) (util.Hash, error) {
+ return "", nil
+}
+
+func (r *mockRepoForTest) StoreTree(mapping map[string]util.Hash) (util.Hash, error) {
+ return "", nil
+}
+
+func (r *mockRepoForTest) StoreCommit(treeHash util.Hash) (util.Hash, error) {
+ return "", nil
+}
+
+func (r *mockRepoForTest) StoreCommitWithParent(treeHash util.Hash, parent util.Hash) (util.Hash, error) {
+ return "", nil
+}
+
+func (r *mockRepoForTest) UpdateRef(ref string, hash util.Hash) error {
+ return nil
+}
diff --git a/repository/repo.go b/repository/repo.go
index 2611324f..3a30128f 100644
--- a/repository/repo.go
+++ b/repository/repo.go
@@ -24,5 +24,17 @@ type Repo interface {
PushRefs(remote string, refPattern string) error
// StoreData will store arbitrary data and return the corresponding hash
- StoreData([]byte) (util.Hash, error)
+ StoreData(data []byte) (util.Hash, error)
+
+ // StoreTree will store a mapping key-->Hash as a Git tree
+ StoreTree(mapping map[string]util.Hash) (util.Hash, error)
+
+ // StoreCommit will store a Git commit with the given Git tree
+ StoreCommit(treeHash util.Hash) (util.Hash, error)
+
+ // StoreCommit will store a Git commit with the given Git tree
+ StoreCommitWithParent(treeHash util.Hash, parent util.Hash) (util.Hash, error)
+
+ // UpdateRef will create or update a Git reference
+ UpdateRef(ref string, hash util.Hash) error
}