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
}
notesOverview returns an overview of the git notes stored under the given ref.
package repository
import (
	"bufio"
	"bytes"
	"crypto/sha1"
	"encoding/json"
	"fmt"
	"io"
	"os"
	exec "golang.org/x/sys/execabs"
	"sort"
	"strconv"
	"strings"
)
const (
	branchRefPrefix         = "refs/heads/"
	notesRefPrefix          = "refs/notes/"
	devtoolsRefPrefix       = "refs/devtools/"
	remoteDevtoolsRefPrefix = "refs/remoteDevtools/"
)
type GitRepo struct {
	Path string
}
func (repo *GitRepo) runGitCommandWithIOAndEnv(stdin io.Reader, stdout, stderr io.Writer, env []string, args ...string) error {
	cmd := exec.Command("git", args...)
	cmd.Dir = repo.Path
	cmd.Stdin = stdin
	cmd.Stdout = stdout
	cmd.Stderr = stderr
	cmd.Env = env
	return cmd.Run()
}
func (repo *GitRepo) runGitCommandWithIO(stdin io.Reader, stdout, stderr io.Writer, args ...string) error {
	return repo.runGitCommandWithIOAndEnv(stdin, stdout, stderr, nil, args...)
}
func (repo *GitRepo) runGitCommandRaw(args ...string) (string, string, error) {
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	err := repo.runGitCommandWithIO(nil, &stdout, &stderr, args...)
	return strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), err
}
func (repo *GitRepo) runGitCommand(args ...string) (string, error) {
	stdout, stderr, err := repo.runGitCommandRaw(args...)
	if err != nil {
		if stderr == "" {
			stderr = "Error running git command: " + strings.Join(args, " ")
		}
		err = fmt.Errorf(stderr)
	}
	return stdout, err
}
func (repo *GitRepo) runGitCommandWithEnv(env []string, args ...string) (string, error) {
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	err := repo.runGitCommandWithIOAndEnv(nil, &stdout, &stderr, env, args...)
	if err != nil {
		stderrStr := strings.TrimSpace(stderr.String())
		if stderrStr == "" {
			stderrStr = "Error running git command: " + strings.Join(args, " ")
		}
		err = fmt.Errorf(stderrStr)
	}
	return strings.TrimSpace(stdout.String()), err
}
func (repo *GitRepo) runGitCommandInline(args ...string) error {
	return repo.runGitCommandWithIO(os.Stdin, os.Stdout, os.Stderr, args...)
}
func NewGitRepo(path string) (*GitRepo, error) {
	repo := &GitRepo{Path: path}
	_, _, err := repo.runGitCommandRaw("rev-parse")
	if err == nil {
		return repo, nil
	}
	if _, ok := err.(*exec.ExitError); ok {
		return nil, err
	}
	return nil, err
}
func (repo *GitRepo) HasRef(ref string) (bool, error) {
	_, _, err := repo.runGitCommandRaw("show-ref", "--verify", "--quiet", ref)
	if err == nil {
		return true, nil
	}
	if _, ok := err.(*exec.ExitError); ok {
		return false, nil
	}
	return false, err
}
func (repo *GitRepo) HasObject(hash string) (bool, error) {
	_, err := repo.runGitCommand("cat-file", "-e", hash)
	if err == nil {
		return true, nil
	}
	if _, ok := err.(*exec.ExitError); ok {
		return false, nil
	}
	return false, err
}
func (repo *GitRepo) GetPath() string {
	return repo.Path
}
func (repo *GitRepo) GetRepoStateHash() (string, error) {
	stateSummary, error := repo.runGitCommand("show-ref")
	return fmt.Sprintf("%x", sha1.Sum([]byte(stateSummary))), error
}
func (repo *GitRepo) GetUserEmail() (string, error) {
	return repo.runGitCommand("config", "user.email")
}
func (repo *GitRepo) GetUserSigningKey() (string, error) {
	return repo.runGitCommand("config", "user.signingKey")
}
func (repo *GitRepo) GetCoreEditor() (string, error) {
	return repo.runGitCommand("var", "GIT_EDITOR")
}
func (repo *GitRepo) GetSubmitStrategy() (string, error) {
	submitStrategy, _ := repo.runGitCommand("config", "appraise.submit")
	return submitStrategy, nil
}
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
}
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
}
func (repo *GitRepo) VerifyGitRef(ref string) error {
	_, err := repo.runGitCommand("show-ref", "--verify", ref)
	return err
}
func (repo *GitRepo) GetHeadRef() (string, error) {
	return repo.runGitCommand("symbolic-ref", "HEAD")
}
func (repo *GitRepo) GetCommitHash(ref string) (string, error) {
	return repo.runGitCommand("show", "-s", "--format=%H", ref)
}
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/") {
		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] != "" {
			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)
}
func (repo *GitRepo) GetCommitMessage(ref string) (string, error) {
	return repo.runGitCommand("show", "-s", "--format=%B", ref)
}
func (repo *GitRepo) GetCommitTime(ref string) (string, error) {
	return repo.runGitCommand("show", "-s", "--format=%ct", ref)
}
func (repo *GitRepo) GetLastParent(ref string) (string, error) {
	return repo.runGitCommand("rev-list", "--skip", "1", "-n", "1", ref)
}
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.Committer = show("%cn")
	details.CommitterEmail = show("%ce")
	details.Summary = show("%s")
	parentsString := show("%P")
	details.Parents = strings.Split(parentsString, " ")
	if err != nil {
		return nil, err
	}
	return &details, nil
}
func (repo *GitRepo) MergeBase(a, b string) (string, error) {
	return repo.runGitCommand("merge-base", a, b)
}
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)
}
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...)
}
func (repo *GitRepo) Show(commit, path string) (string, error) {
	return repo.runGitCommand("show", fmt.Sprintf("%s:%s", commit, path))
}
func (repo *GitRepo) SwitchToRef(ref string) error {
	if strings.HasPrefix(ref, branchRefPrefix) {
		ref = ref[len(branchRefPrefix):]
	}
	_, err := repo.runGitCommand("checkout", ref)
	return err
}
func (repo *GitRepo) mergeArchives(archive, remoteArchive string) error {
	hasRemote, err := repo.HasRef(remoteArchive)
	if err != nil {
		return err
	}
	if !hasRemote {
		return nil
	}
	remoteHash, err := repo.GetCommitHash(remoteArchive)
	if err != nil {
		return err
	}
	hasLocal, err := repo.HasRef(archive)
	if err != nil {
		return err
	}
	if !hasLocal {
		_, err := repo.runGitCommand("update-ref", archive, remoteHash)
		return err
	}
	archiveHash, err := repo.GetCommitHash(archive)
	if err != nil {
		return err
	}
	isAncestor, err := repo.IsAncestor(archiveHash, remoteHash)
	if err != nil {
		return err
	}
	if isAncestor {
		_, err := repo.runGitCommand("update-ref", archive, remoteHash, archiveHash)
		return err
	}
	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
}
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 {
		if isAncestor, err := repo.IsAncestor(refHash, archiveHash); err != nil {
			return err
		} else if isAncestor {
			return nil
		}
		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
}
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...)
}
func (repo *GitRepo) MergeAndSignRef(ref string, fastForward bool,
	messages ...string) error {
	args := []string{"merge"}
	if fastForward {
		args = append(args, "--ff", "--ff-only", "-S")
	} else {
		args = append(args, "--no-ff", "-S")
	}
	if len(messages) > 0 {
		commitMessage := strings.Join(messages, "\n\n")
		args = append(args, "-e", "-m", commitMessage)
	}
	args = append(args, ref)
	return repo.runGitCommandInline(args...)
}
func (repo *GitRepo) RebaseRef(ref string) error {
	return repo.runGitCommandInline("rebase", "-i", ref)
}
func (repo *GitRepo) RebaseAndSignRef(ref string) error {
	return repo.runGitCommandInline("rebase", "-S", "-i", ref)
}
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
}
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
}
func (repo *GitRepo) StoreBlob(contents string) (string, error) {
	stdin := strings.NewReader(contents)
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	args := []string{"hash-object", "-w", "-t", "blob", "--stdin"}
	err := repo.runGitCommandWithIO(stdin, &stdout, &stderr, args...)
	if err != nil {
		message := strings.TrimSpace(stderr.String())
		return "", fmt.Errorf("failure storing a git blob, %v: %q", err, message)
	}
	return strings.TrimSpace(stdout.String()), nil
}
func (repo *GitRepo) StoreTree(contents map[string]TreeChild) (string, error) {
	var lines []string
	for path, obj := range contents {
		objHash, err := obj.Store(repo)
		if err != nil {
			return "", err
		}
		mode := "040000"
		if obj.Type() == "blob" {
			mode = "100644"
		}
		line := fmt.Sprintf("%s %s %s\t%s", mode, obj.Type(), objHash, path)
		lines = append(lines, line)
	}
	stdin := strings.NewReader(strings.Join(lines, "\n"))
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	args := []string{"mktree"}
	err := repo.runGitCommandWithIO(stdin, &stdout, &stderr, args...)
	if err != nil {
		message := strings.TrimSpace(stderr.String())
		return "", fmt.Errorf("failure storing a git tree, %v: %q", err, message)
	}
	return strings.TrimSpace(stdout.String()), nil
}
func (repo *GitRepo) readBlob(objHash string) (*Blob, error) {
	out, err := repo.runGitCommand("cat-file", "-p", objHash)
	if err != nil {
		return nil, fmt.Errorf("failure reading the file contents of %q: %v", objHash, err)
	}
	return &Blob{contents: out, savedHashes: map[Repo]string{repo: objHash}}, nil
}
func (repo *GitRepo) ReadTree(ref string) (*Tree, error) {
	return repo.readTreeWithHash(ref, "")
}
func (repo *GitRepo) readTreeWithHash(ref, hash string) (*Tree, error) {
	out, err := repo.runGitCommand("ls-tree", "--full-tree", ref)
	if err != nil {
		return nil, fmt.Errorf("failure listing the file contents of %q: %v", ref, err)
	}
	contents := make(map[string]TreeChild)
	if len(out) == 0 {
		return NewTree(contents), nil
	}
	for _, line := range strings.Split(out, "\n") {
		lineParts := strings.Split(line, "\t")
		if len(lineParts) != 2 {
			return nil, fmt.Errorf("malformed ls-tree output line: %q", line)
		}
		path := lineParts[1]
		lineParts = strings.Split(lineParts[0], " ")
		if len(lineParts) != 3 {
			return nil, fmt.Errorf("malformed ls-tree output line: %q", line)
		}
		objType := lineParts[1]
		objHash := lineParts[2]
		var child TreeChild
		if objType == "tree" {
			child, err = repo.readTreeWithHash(objHash, objHash)
		} else if objType == "blob" {
			child, err = repo.readBlob(objHash)
		} else {
			return nil, fmt.Errorf("unrecognized tree object type: %q", objType)
		}
		if err != nil {
			return nil, fmt.Errorf("failed to read a tree child object: %v", err)
		}
		contents[path] = child
	}
	t := NewTree(contents)
	t.savedHashes[repo] = hash
	return t, nil
}
func (repo *GitRepo) CreateCommit(details *CommitDetails) (string, error) {
	args := []string{"commit-tree", details.Tree, "-m", details.Summary}
	for _, parent := range details.Parents {
		args = append(args, "-p", parent)
	}
	var env []string
	if details.Author != "" {
		env = append(env, fmt.Sprintf("GIT_AUTHOR_NAME=%s", details.Author))
	}
	if details.AuthorEmail != "" {
		env = append(env, fmt.Sprintf("GIT_AUTHOR_EMAIL=%s", details.AuthorEmail))
	}
	if details.AuthorTime != "" {
		env = append(env, fmt.Sprintf("GIT_AUTHOR_DATE=%s", details.AuthorTime))
	}
	if details.Committer != "" {
		env = append(env, fmt.Sprintf("GIT_COMMITTER_NAME=%s", details.Committer))
	}
	if details.CommitterEmail != "" {
		env = append(env, fmt.Sprintf("GIT_COMMITTER_EMAIL=%s", details.CommitterEmail))
	}
	if details.Time != "" {
		env = append(env, fmt.Sprintf("GIT_COMMITTER_DATE=%s", details.Time))
	}
	return repo.runGitCommandWithEnv(env, args...)
}
func (repo *GitRepo) CreateCommitWithTree(details *CommitDetails, t *Tree) (string, error) {
	treeHash, err := repo.StoreTree(t.Contents())
	if err != nil {
		return "", fmt.Errorf("failure storing a tree: %v", err)
	}
	details.Tree = treeHash
	return repo.CreateCommit(details)
}
func (repo *GitRepo) SetRef(ref, newCommitHash, previousCommitHash string) error {
	args := []string{"update-ref", ref, newCommitHash}
	if previousCommitHash != "" {
		args = append(args, previousCommitHash)
	}
	_, err := repo.runGitCommand(args...)
	return err
}
func (repo *GitRepo) GetNotes(notesRef, revision string) []Note {
	var notes []Note
	rawNotes, err := repo.runGitCommand("notes", "--ref", notesRef, "show", revision)
	if err != nil {
		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...)
}
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
		}
	}
}
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()
		}
	}
}
type notesMapping struct {
	ObjectHash *string
	NotesHash  *string
}
type notesOverview struct {
	NotesMappings      []*notesMapping
	ObjectHashesReader io.Reader
	NotesHashesReader  io.Reader
}