git.go

  1// Package repository contains helper methods for working with the Git repo.
  2package repository
  3
  4import (
  5	"bytes"
  6	"crypto/sha1"
  7	"fmt"
  8	"github.com/MichaelMure/git-bug/util"
  9	"io"
 10	"os"
 11	"os/exec"
 12	"strings"
 13)
 14
 15// GitRepo represents an instance of a (local) git repository.
 16type GitRepo struct {
 17	Path string
 18}
 19
 20// Run the given git command with the given I/O reader/writers, returning an error if it fails.
 21func (repo *GitRepo) runGitCommandWithIO(stdin io.Reader, stdout, stderr io.Writer, args ...string) error {
 22	cmd := exec.Command("git", args...)
 23	cmd.Dir = repo.Path
 24	cmd.Stdin = stdin
 25	cmd.Stdout = stdout
 26	cmd.Stderr = stderr
 27	return cmd.Run()
 28}
 29
 30// Run the given git command and return its stdout, or an error if the command fails.
 31func (repo *GitRepo) runGitCommandRaw(args ...string) (string, string, error) {
 32	var stdout bytes.Buffer
 33	var stderr bytes.Buffer
 34	err := repo.runGitCommandWithIO(nil, &stdout, &stderr, args...)
 35	return strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), err
 36}
 37
 38// Run the given git command and return its stdout, or an error if the command fails.
 39func (repo *GitRepo) runGitCommand(args ...string) (string, error) {
 40	stdout, stderr, err := repo.runGitCommandRaw(args...)
 41	if err != nil {
 42		if stderr == "" {
 43			stderr = "Error running git command: " + strings.Join(args, " ")
 44		}
 45		err = fmt.Errorf(stderr)
 46	}
 47	return stdout, err
 48}
 49
 50// Run the given git command using the same stdin, stdout, and stderr as the review tool.
 51func (repo *GitRepo) runGitCommandInline(args ...string) error {
 52	return repo.runGitCommandWithIO(os.Stdin, os.Stdout, os.Stderr, args...)
 53}
 54
 55// NewGitRepo determines if the given working directory is inside of a git repository,
 56// and returns the corresponding GitRepo instance if it is.
 57func NewGitRepo(path string) (*GitRepo, error) {
 58	repo := &GitRepo{Path: path}
 59	_, _, err := repo.runGitCommandRaw("rev-parse")
 60	if err == nil {
 61		return repo, nil
 62	}
 63	if _, ok := err.(*exec.ExitError); ok {
 64		return nil, err
 65	}
 66	return nil, err
 67}
 68
 69// GetPath returns the path to the repo.
 70func (repo *GitRepo) GetPath() string {
 71	return repo.Path
 72}
 73
 74// GetRepoStateHash returns a hash which embodies the entire current state of a repository.
 75func (repo *GitRepo) GetRepoStateHash() (string, error) {
 76	stateSummary, err := repo.runGitCommand("show-ref")
 77	return fmt.Sprintf("%x", sha1.Sum([]byte(stateSummary))), err
 78}
 79
 80// GetUserName returns the name the the user has used to configure git
 81func (repo *GitRepo) GetUserName() (string, error) {
 82	return repo.runGitCommand("config", "user.name")
 83}
 84
 85// GetUserEmail returns the email address that the user has used to configure git.
 86func (repo *GitRepo) GetUserEmail() (string, error) {
 87	return repo.runGitCommand("config", "user.email")
 88}
 89
 90// GetCoreEditor returns the name of the editor that the user has used to configure git.
 91func (repo *GitRepo) GetCoreEditor() (string, error) {
 92	return repo.runGitCommand("var", "GIT_EDITOR")
 93}
 94
 95// PullRefs pull git refs from a remote
 96func (repo *GitRepo) PullRefs(remote string, refPattern string) error {
 97	fetchRefSpec := fmt.Sprintf("+%s:%s", refPattern, refPattern)
 98	err := repo.runGitCommandInline("fetch", remote, fetchRefSpec)
 99
100	// TODO: merge new data
101
102	return err
103}
104
105// PushRefs push git refs to a remote
106func (repo *GitRepo) PushRefs(remote string, refPattern string) error {
107	// The push is liable to fail if the user forgot to do a pull first, so
108	// we treat errors as user errors rather than fatal errors.
109	err := repo.runGitCommandInline("push", remote, refPattern)
110	if err != nil {
111		return fmt.Errorf("failed to push to the remote '%s': %v", remote, err)
112	}
113	return nil
114}
115
116// StoreData will store arbitrary data and return the corresponding hash
117func (repo *GitRepo) StoreData(data []byte) (util.Hash, error) {
118	var stdin = bytes.NewReader(data)
119	var stdout bytes.Buffer
120	var stderr bytes.Buffer
121
122	err := repo.runGitCommandWithIO(stdin, &stdout, &stderr, "hash-object", "--stdin", "-w")
123
124	return util.Hash(stdout.String()), err
125}
126
127/*
128//
129//// GetSubmitStrategy returns the way in which a review is submitted
130//func (repo *GitRepo) GetSubmitStrategy() (string, error) {
131//	submitStrategy, _ := repo.runGitCommand("config", "appraise.submit")
132//	return submitStrategy, nil
133//}
134//
135//// HasUncommittedChanges returns true if there are local, uncommitted changes.
136//func (repo *GitRepo) HasUncommittedChanges() (bool, error) {
137//	out, err := repo.runGitCommand("status", "--porcelain")
138//	if err != nil {
139//		return false, err
140//	}
141//	if len(out) > 0 {
142//		return true, nil
143//	}
144//	return false, nil
145//}
146//
147//// VerifyCommit verifies that the supplied hash points to a known commit.
148//func (repo *GitRepo) VerifyCommit(hash string) error {
149//	out, err := repo.runGitCommand("cat-file", "-t", hash)
150//	if err != nil {
151//		return err
152//	}
153//	objectType := strings.TrimSpace(string(out))
154//	if objectType != "commit" {
155//		return fmt.Errorf("Hash %q points to a non-commit object of type %q", hash, objectType)
156//	}
157//	return nil
158//}
159//
160//// VerifyGitRef verifies that the supplied ref points to a known commit.
161//func (repo *GitRepo) VerifyGitRef(ref string) error {
162//	_, err := repo.runGitCommand("show-ref", "--verify", ref)
163//	return err
164//}
165//
166//// GetHeadRef returns the ref that is the current HEAD.
167//func (repo *GitRepo) GetHeadRef() (string, error) {
168//	return repo.runGitCommand("symbolic-ref", "HEAD")
169//}
170//
171//// GetCommitHash returns the hash of the commit pointed to by the given ref.
172//func (repo *GitRepo) GetCommitHash(ref string) (string, error) {
173//	return repo.runGitCommand("show", "-s", "--format=%H", ref)
174//}
175//
176//// ResolveRefCommit returns the commit pointed to by the given ref, which may be a remote ref.
177////
178//// This differs from GetCommitHash which only works on exact matches, in that it will try to
179//// intelligently handle the scenario of a ref not existing locally, but being known to exist
180//// in a remote repo.
181////
182//// This method should be used when a command may be performed by either the reviewer or the
183//// reviewee, while GetCommitHash should be used when the encompassing command should only be
184//// performed by the reviewee.
185//func (repo *GitRepo) ResolveRefCommit(ref string) (string, error) {
186//	if err := repo.VerifyGitRef(ref); err == nil {
187//		return repo.GetCommitHash(ref)
188//	}
189//	if strings.HasPrefix(ref, "refs/heads/") {
190//		// The ref is a branch. Check if it exists in exactly one remote
191//		pattern := strings.Replace(ref, "refs/heads", "**", 1)
192//		matchingOutput, err := repo.runGitCommand("for-each-ref", "--format=%(refname)", pattern)
193//		if err != nil {
194//			return "", err
195//		}
196//		matchingRefs := strings.Split(matchingOutput, "\n")
197//		if len(matchingRefs) == 1 && matchingRefs[0] != "" {
198//			// There is exactly one match
199//			return repo.GetCommitHash(matchingRefs[0])
200//		}
201//		return "", fmt.Errorf("Unable to find a git ref matching the pattern %q", pattern)
202//	}
203//	return "", fmt.Errorf("Unknown git ref %q", ref)
204//}
205//
206//// GetCommitMessage returns the message stored in the commit pointed to by the given ref.
207//func (repo *GitRepo) GetCommitMessage(ref string) (string, error) {
208//	return repo.runGitCommand("show", "-s", "--format=%B", ref)
209//}
210//
211//// GetCommitTime returns the commit time of the commit pointed to by the given ref.
212//func (repo *GitRepo) GetCommitTime(ref string) (string, error) {
213//	return repo.runGitCommand("show", "-s", "--format=%ct", ref)
214//}
215//
216//// GetLastParent returns the last parent of the given commit (as ordered by git).
217//func (repo *GitRepo) GetLastParent(ref string) (string, error) {
218//	return repo.runGitCommand("rev-list", "--skip", "1", "-n", "1", ref)
219//}
220//
221//// GetCommitDetails returns the details of a commit's metadata.
222//func (repo GitRepo) GetCommitDetails(ref string) (*CommitDetails, error) {
223//	var err error
224//	show := func(formatString string) (result string) {
225//		if err != nil {
226//			return ""
227//		}
228//		result, err = repo.runGitCommand("show", "-s", ref, fmt.Sprintf("--format=tformat:%s", formatString))
229//		return result
230//	}
231//
232//	jsonFormatString := "{\"tree\":\"%T\", \"time\": \"%at\"}"
233//	detailsJSON := show(jsonFormatString)
234//	if err != nil {
235//		return nil, err
236//	}
237//	var details CommitDetails
238//	err = json.Unmarshal([]byte(detailsJSON), &details)
239//	if err != nil {
240//		return nil, err
241//	}
242//	details.Author = show("%an")
243//	details.AuthorEmail = show("%ae")
244//	details.Summary = show("%s")
245//	parentsString := show("%P")
246//	details.Parents = strings.Split(parentsString, " ")
247//	if err != nil {
248//		return nil, err
249//	}
250//	return &details, nil
251//}
252//
253//// MergeBase determines if the first commit that is an ancestor of the two arguments.
254//func (repo *GitRepo) MergeBase(a, b string) (string, error) {
255//	return repo.runGitCommand("merge-base", a, b)
256//}
257//
258//// IsAncestor determines if the first argument points to a commit that is an ancestor of the second.
259//func (repo *GitRepo) IsAncestor(ancestor, descendant string) (bool, error) {
260//	_, _, err := repo.runGitCommandRaw("merge-base", "--is-ancestor", ancestor, descendant)
261//	if err == nil {
262//		return true, nil
263//	}
264//	if _, ok := err.(*exec.ExitError); ok {
265//		return false, nil
266//	}
267//	return false, fmt.Errorf("Error while trying to determine commit ancestry: %v", err)
268//}
269//
270//// Diff computes the diff between two given commits.
271//func (repo *GitRepo) Diff(left, right string, diffArgs ...string) (string, error) {
272//	args := []string{"diff"}
273//	args = append(args, diffArgs...)
274//	args = append(args, fmt.Sprintf("%s..%s", left, right))
275//	return repo.runGitCommand(args...)
276//}
277//
278//// Show returns the contents of the given file at the given commit.
279//func (repo *GitRepo) Show(commit, path string) (string, error) {
280//	return repo.runGitCommand("show", fmt.Sprintf("%s:%s", commit, path))
281//}
282//
283//// SwitchToRef changes the currently-checked-out ref.
284//func (repo *GitRepo) SwitchToRef(ref string) error {
285//	// If the ref starts with "refs/heads/", then we have to trim that prefix,
286//	// or else we will wind up in a detached HEAD state.
287//	if strings.HasPrefix(ref, branchRefPrefix) {
288//		ref = ref[len(branchRefPrefix):]
289//	}
290//	_, err := repo.runGitCommand("checkout", ref)
291//	return err
292//}
293//
294//// mergeArchives merges two archive refs.
295//func (repo *GitRepo) mergeArchives(archive, remoteArchive string) error {
296//	remoteHash, err := repo.GetCommitHash(remoteArchive)
297//	if err != nil {
298//		return err
299//	}
300//	if remoteHash == "" {
301//		// The remote archive does not exist, so we have nothing to do
302//		return nil
303//	}
304//
305//	archiveHash, err := repo.GetCommitHash(archive)
306//	if err != nil {
307//		return err
308//	}
309//	if archiveHash == "" {
310//		// The local archive does not exist, so we merely need to set it
311//		_, err := repo.runGitCommand("update-ref", archive, remoteHash)
312//		return err
313//	}
314//
315//	isAncestor, err := repo.IsAncestor(archiveHash, remoteHash)
316//	if err != nil {
317//		return err
318//	}
319//	if isAncestor {
320//		// The archive can simply be fast-forwarded
321//		_, err := repo.runGitCommand("update-ref", archive, remoteHash, archiveHash)
322//		return err
323//	}
324//
325//	// Create a merge commit of the two archives
326//	refDetails, err := repo.GetCommitDetails(remoteArchive)
327//	if err != nil {
328//		return err
329//	}
330//	newArchiveHash, err := repo.runGitCommand("commit-tree", "-p", remoteHash, "-p", archiveHash, "-m", "Merge local and remote archives", refDetails.Tree)
331//	if err != nil {
332//		return err
333//	}
334//	newArchiveHash = strings.TrimSpace(newArchiveHash)
335//	_, err = repo.runGitCommand("update-ref", archive, newArchiveHash, archiveHash)
336//	return err
337//}
338//
339//// ArchiveRef adds the current commit pointed to by the 'ref' argument
340//// under the ref specified in the 'archive' argument.
341////
342//// Both the 'ref' and 'archive' arguments are expected to be the fully
343//// qualified names of git refs (e.g. 'refs/heads/my-change' or
344//// 'refs/devtools/archives/reviews').
345////
346//// If the ref pointed to by the 'archive' argument does not exist
347//// yet, then it will be created.
348//func (repo *GitRepo) ArchiveRef(ref, archive string) error {
349//	refHash, err := repo.GetCommitHash(ref)
350//	if err != nil {
351//		return err
352//	}
353//	refDetails, err := repo.GetCommitDetails(ref)
354//	if err != nil {
355//		return err
356//	}
357//
358//	commitTreeArgs := []string{"commit-tree"}
359//	archiveHash, err := repo.GetCommitHash(archive)
360//	if err != nil {
361//		archiveHash = ""
362//	} else {
363//		commitTreeArgs = append(commitTreeArgs, "-p", archiveHash)
364//	}
365//	commitTreeArgs = append(commitTreeArgs, "-p", refHash, "-m", fmt.Sprintf("Archive %s", refHash), refDetails.Tree)
366//	newArchiveHash, err := repo.runGitCommand(commitTreeArgs...)
367//	if err != nil {
368//		return err
369//	}
370//	newArchiveHash = strings.TrimSpace(newArchiveHash)
371//	updateRefArgs := []string{"update-ref", archive, newArchiveHash}
372//	if archiveHash != "" {
373//		updateRefArgs = append(updateRefArgs, archiveHash)
374//	}
375//	_, err = repo.runGitCommand(updateRefArgs...)
376//	return err
377//}
378//
379//// MergeRef merges the given ref into the current one.
380////
381//// The ref argument is the ref to merge, and fastForward indicates that the
382//// current ref should only move forward, as opposed to creating a bubble merge.
383//// The messages argument(s) provide text that should be included in the default
384//// merge commit message (separated by blank lines).
385//func (repo *GitRepo) MergeRef(ref string, fastForward bool, messages ...string) error {
386//	args := []string{"merge"}
387//	if fastForward {
388//		args = append(args, "--ff", "--ff-only")
389//	} else {
390//		args = append(args, "--no-ff")
391//	}
392//	if len(messages) > 0 {
393//		commitMessage := strings.Join(messages, "\n\n")
394//		args = append(args, "-e", "-m", commitMessage)
395//	}
396//	args = append(args, ref)
397//	return repo.runGitCommandInline(args...)
398//}
399//
400//// RebaseRef rebases the current ref onto the given one.
401//func (repo *GitRepo) RebaseRef(ref string) error {
402//	return repo.runGitCommandInline("rebase", "-i", ref)
403//}
404//
405//// ListCommits returns the list of commits reachable from the given ref.
406////
407//// The generated list is in chronological order (with the oldest commit first).
408////
409//// If the specified ref does not exist, then this method returns an empty result.
410//func (repo *GitRepo) ListCommits(ref string) []string {
411//	var stdout bytes.Buffer
412//	var stderr bytes.Buffer
413//	if err := repo.runGitCommandWithIO(nil, &stdout, &stderr, "rev-list", "--reverse", ref); err != nil {
414//		return nil
415//	}
416//
417//	byteLines := bytes.Split(stdout.Bytes(), []byte("\n"))
418//	var commits []string
419//	for _, byteLine := range byteLines {
420//		commits = append(commits, string(byteLine))
421//	}
422//	return commits
423//}
424//
425//// ListCommitsBetween returns the list of commits between the two given revisions.
426////
427//// The "from" parameter is the starting point (exclusive), and the "to"
428//// parameter is the ending point (inclusive).
429////
430//// The "from" commit does not need to be an ancestor of the "to" commit. If it
431//// is not, then the merge base of the two is used as the starting point.
432//// Admittedly, this makes calling these the "between" commits is a bit of a
433//// misnomer, but it also makes the method easier to use when you want to
434//// generate the list of changes in a feature branch, as it eliminates the need
435//// to explicitly calculate the merge base. This also makes the semantics of the
436//// method compatible with git's built-in "rev-list" command.
437////
438//// The generated list is in chronological order (with the oldest commit first).
439//func (repo *GitRepo) ListCommitsBetween(from, to string) ([]string, error) {
440//	out, err := repo.runGitCommand("rev-list", "--reverse", from+".."+to)
441//	if err != nil {
442//		return nil, err
443//	}
444//	if out == "" {
445//		return nil, nil
446//	}
447//	return strings.Split(out, "\n"), nil
448//}
449//
450//// GetNotes uses the "git" command-line tool to read the notes from the given ref for a given revision.
451//func (repo *GitRepo) GetNotes(notesRef, revision string) []Note {
452//	var notes []Note
453//	rawNotes, err := repo.runGitCommand("notes", "--ref", notesRef, "show", revision)
454//	if err != nil {
455//		// We just assume that this means there are no notes
456//		return nil
457//	}
458//	for _, line := range strings.Split(rawNotes, "\n") {
459//		notes = append(notes, Note([]byte(line)))
460//	}
461//	return notes
462//}
463//
464//func stringsReader(s []*string) io.Reader {
465//	var subReaders []io.Reader
466//	for _, strPtr := range s {
467//		subReader := strings.NewReader(*strPtr)
468//		subReaders = append(subReaders, subReader, strings.NewReader("\n"))
469//	}
470//	return io.MultiReader(subReaders...)
471//}
472//
473//// splitBatchCheckOutput parses the output of a 'git cat-file --batch-check=...' command.
474////
475//// The output is expected to be formatted as a series of entries, with each
476//// entry consisting of:
477//// 1. The SHA1 hash of the git object being output, followed by a space.
478//// 2. The git "type" of the object (commit, blob, tree, missing, etc), followed by a newline.
479////
480//// To generate this format, make sure that the 'git cat-file' command includes
481//// the argument '--batch-check=%(objectname) %(objecttype)'.
482////
483//// The return value is a map from object hash to a boolean indicating if that object is a commit.
484//func splitBatchCheckOutput(out *bytes.Buffer) (map[string]bool, error) {
485//	isCommit := make(map[string]bool)
486//	reader := bufio.NewReader(out)
487//	for {
488//		nameLine, err := reader.ReadString(byte(' '))
489//		if err == io.EOF {
490//			return isCommit, nil
491//		}
492//		if err != nil {
493//			return nil, fmt.Errorf("Failure while reading the next object name: %v", err)
494//		}
495//		nameLine = strings.TrimSuffix(nameLine, " ")
496//		typeLine, err := reader.ReadString(byte('\n'))
497//		if err != nil && err != io.EOF {
498//			return nil, fmt.Errorf("Failure while reading the next object type: %q - %v", nameLine, err)
499//		}
500//		typeLine = strings.TrimSuffix(typeLine, "\n")
501//		if typeLine == "commit" {
502//			isCommit[nameLine] = true
503//		}
504//	}
505//}
506//
507//// splitBatchCatFileOutput parses the output of a 'git cat-file --batch=...' command.
508////
509//// The output is expected to be formatted as a series of entries, with each
510//// entry consisting of:
511//// 1. The SHA1 hash of the git object being output, followed by a newline.
512//// 2. The size of the object's contents in bytes, followed by a newline.
513//// 3. The objects contents.
514////
515//// To generate this format, make sure that the 'git cat-file' command includes
516//// the argument '--batch=%(objectname)\n%(objectsize)'.
517//func splitBatchCatFileOutput(out *bytes.Buffer) (map[string][]byte, error) {
518//	contentsMap := make(map[string][]byte)
519//	reader := bufio.NewReader(out)
520//	for {
521//		nameLine, err := reader.ReadString(byte('\n'))
522//		if strings.HasSuffix(nameLine, "\n") {
523//			nameLine = strings.TrimSuffix(nameLine, "\n")
524//		}
525//		if err == io.EOF {
526//			return contentsMap, nil
527//		}
528//		if err != nil {
529//			return nil, fmt.Errorf("Failure while reading the next object name: %v", err)
530//		}
531//		sizeLine, err := reader.ReadString(byte('\n'))
532//		if strings.HasSuffix(sizeLine, "\n") {
533//			sizeLine = strings.TrimSuffix(sizeLine, "\n")
534//		}
535//		if err != nil {
536//			return nil, fmt.Errorf("Failure while reading the next object size: %q - %v", nameLine, err)
537//		}
538//		size, err := strconv.Atoi(sizeLine)
539//		if err != nil {
540//			return nil, fmt.Errorf("Failure while parsing the next object size: %q - %v", nameLine, err)
541//		}
542//		contentBytes := make([]byte, size, size)
543//		readDest := contentBytes
544//		len := 0
545//		err = nil
546//		for err == nil && len < size {
547//			nextLen := 0
548//			nextLen, err = reader.Read(readDest)
549//			len += nextLen
550//			readDest = contentBytes[len:]
551//		}
552//		contentsMap[nameLine] = contentBytes
553//		if err == io.EOF {
554//			return contentsMap, nil
555//		}
556//		if err != nil {
557//			return nil, err
558//		}
559//		for bs, err := reader.Peek(1); err == nil && bs[0] == byte('\n'); bs, err = reader.Peek(1) {
560//			reader.ReadByte()
561//		}
562//	}
563//}
564//
565//// notesMapping represents the association between a git object and the notes for that object.
566//type notesMapping struct {
567//	ObjectHash *string
568//	NotesHash  *string
569//}
570//
571//// notesOverview represents a high-level overview of all the notes under a single notes ref.
572//type notesOverview struct {
573//	NotesMappings      []*notesMapping
574//	ObjectHashesReader io.Reader
575//	NotesHashesReader  io.Reader
576//}
577//
578//// notesOverview returns an overview of the git notes stored under the given ref.
579//func (repo *GitRepo) notesOverview(notesRef string) (*notesOverview, error) {
580//	var stdout bytes.Buffer
581//	var stderr bytes.Buffer
582//	if err := repo.runGitCommandWithIO(nil, &stdout, &stderr, "notes", "--ref", notesRef, "list"); err != nil {
583//		return nil, err
584//	}
585//
586//	var notesMappings []*notesMapping
587//	var objHashes []*string
588//	var notesHashes []*string
589//	outScanner := bufio.NewScanner(&stdout)
590//	for outScanner.Scan() {
591//		line := outScanner.Text()
592//		lineParts := strings.Split(line, " ")
593//		if len(lineParts) != 2 {
594//			return nil, fmt.Errorf("Malformed output line from 'git-notes list': %q", line)
595//		}
596//		objHash := &lineParts[1]
597//		notesHash := &lineParts[0]
598//		notesMappings = append(notesMappings, &notesMapping{
599//			ObjectHash: objHash,
600//			NotesHash:  notesHash,
601//		})
602//		objHashes = append(objHashes, objHash)
603//		notesHashes = append(notesHashes, notesHash)
604//	}
605//	err := outScanner.Err()
606//	if err != nil && err != io.EOF {
607//		return nil, fmt.Errorf("Failure parsing the output of 'git-notes list': %v", err)
608//	}
609//	return &notesOverview{
610//		NotesMappings:      notesMappings,
611//		ObjectHashesReader: stringsReader(objHashes),
612//		NotesHashesReader:  stringsReader(notesHashes),
613//	}, nil
614//}
615//
616//// getIsCommitMap returns a mapping of all the annotated objects that are commits.
617//func (overview *notesOverview) getIsCommitMap(repo *GitRepo) (map[string]bool, error) {
618//	var stdout bytes.Buffer
619//	var stderr bytes.Buffer
620//	if err := repo.runGitCommandWithIO(overview.ObjectHashesReader, &stdout, &stderr, "cat-file", "--batch-check=%(objectname) %(objecttype)"); err != nil {
621//		return nil, fmt.Errorf("Failure performing a batch file check: %v", err)
622//	}
623//	isCommit, err := splitBatchCheckOutput(&stdout)
624//	if err != nil {
625//		return nil, fmt.Errorf("Failure parsing the output of a batch file check: %v", err)
626//	}
627//	return isCommit, nil
628//}
629//
630//// getNoteContentsMap returns a mapping from all the notes hashes to their contents.
631//func (overview *notesOverview) getNoteContentsMap(repo *GitRepo) (map[string][]byte, error) {
632//	var stdout bytes.Buffer
633//	var stderr bytes.Buffer
634//	if err := repo.runGitCommandWithIO(overview.NotesHashesReader, &stdout, &stderr, "cat-file", "--batch=%(objectname)\n%(objectsize)"); err != nil {
635//		return nil, fmt.Errorf("Failure performing a batch file read: %v", err)
636//	}
637//	noteContentsMap, err := splitBatchCatFileOutput(&stdout)
638//	if err != nil {
639//		return nil, fmt.Errorf("Failure parsing the output of a batch file read: %v", err)
640//	}
641//	return noteContentsMap, nil
642//}
643//
644//// GetAllNotes reads the contents of the notes under the given ref for every commit.
645////
646//// The returned value is a mapping from commit hash to the list of notes for that commit.
647////
648//// This is the batch version of the corresponding GetNotes(...) method.
649//func (repo *GitRepo) GetAllNotes(notesRef string) (map[string][]Note, error) {
650//	// This code is unfortunately quite complicated, but it needs to be so.
651//	//
652//	// Conceptually, this is equivalent to:
653//	//   result := make(map[string][]Note)
654//	//   for _, commit := range repo.ListNotedRevisions(notesRef) {
655//	//     result[commit] = repo.GetNotes(notesRef, commit)
656//	//   }
657//	//   return result, nil
658//	//
659//	// However, that logic would require separate executions of the 'git'
660//	// command for every annotated commit. For a repo with 10s of thousands
661//	// of reviews, that would mean calling Cmd.Run(...) 10s of thousands of
662//	// times. That, in turn, would take so long that the tool would be unusable.
663//	//
664//	// This method avoids that by taking advantage of the 'git cat-file --batch="..."'
665//	// command. That allows us to use a single invocation of Cmd.Run(...) to
666//	// inspect multiple git objects at once.
667//	//
668//	// As such, regardless of the number of reviews in a repo, we can get all
669//	// of the notes using a total of three invocations of Cmd.Run(...):
670//	//  1. One to list all the annotated objects (and their notes hash)
671//	//  2. A second one to filter out all of the annotated objects that are not commits.
672//	//  3. A final one to get the contents of all of the notes blobs.
673//	overview, err := repo.notesOverview(notesRef)
674//	if err != nil {
675//		return nil, err
676//	}
677//	isCommit, err := overview.getIsCommitMap(repo)
678//	if err != nil {
679//		return nil, fmt.Errorf("Failure building the set of commit objects: %v", err)
680//	}
681//	noteContentsMap, err := overview.getNoteContentsMap(repo)
682//	if err != nil {
683//		return nil, fmt.Errorf("Failure building the mapping from notes hash to contents: %v", err)
684//	}
685//	commitNotesMap := make(map[string][]Note)
686//	for _, notesMapping := range overview.NotesMappings {
687//		if !isCommit[*notesMapping.ObjectHash] {
688//			continue
689//		}
690//		noteBytes := noteContentsMap[*notesMapping.NotesHash]
691//		byteSlices := bytes.Split(noteBytes, []byte("\n"))
692//		var notes []Note
693//		for _, slice := range byteSlices {
694//			notes = append(notes, Note(slice))
695//		}
696//		commitNotesMap[*notesMapping.ObjectHash] = notes
697//	}
698//
699//	return commitNotesMap, nil
700//}
701//
702//// AppendNote appends a note to a revision under the given ref.
703//func (repo *GitRepo) AppendNote(notesRef, revision string, note Note) error {
704//	_, err := repo.runGitCommand("notes", "--ref", notesRef, "append", "-m", string(note), revision)
705//	return err
706//}
707//
708//// ListNotedRevisions returns the collection of revisions that are annotated by notes in the given ref.
709//func (repo *GitRepo) ListNotedRevisions(notesRef string) []string {
710//	var revisions []string
711//	notesListOut, err := repo.runGitCommand("notes", "--ref", notesRef, "list")
712//	if err != nil {
713//		return nil
714//	}
715//	notesList := strings.Split(notesListOut, "\n")
716//	for _, notePair := range notesList {
717//		noteParts := strings.SplitN(notePair, " ", 2)
718//		if len(noteParts) == 2 {
719//			objHash := noteParts[1]
720//			objType, err := repo.runGitCommand("cat-file", "-t", objHash)
721//			// If a note points to an object that we do not know about (yet), then err will not
722//			// be nil. We can safely just ignore those notes.
723//			if err == nil && objType == "commit" {
724//				revisions = append(revisions, objHash)
725//			}
726//		}
727//	}
728//	return revisions
729//}
730//
731//// PushNotes pushes git notes to a remote repo.
732//func (repo *GitRepo) PushNotes(remote, notesRefPattern string) error {
733//	refspec := fmt.Sprintf("%s:%s", notesRefPattern, notesRefPattern)
734//
735//	// The push is liable to fail if the user forgot to do a pull first, so
736//	// we treat errors as user errors rather than fatal errors.
737//	err := repo.runGitCommandInline("push", remote, refspec)
738//	if err != nil {
739//		return fmt.Errorf("Failed to push to the remote '%s': %v", remote, err)
740//	}
741//	return nil
742//}
743//
744//// PushNotesAndArchive pushes the given notes and archive refs to a remote repo.
745//func (repo *GitRepo) PushNotesAndArchive(remote, notesRefPattern, archiveRefPattern string) error {
746//	notesRefspec := fmt.Sprintf("%s:%s", notesRefPattern, notesRefPattern)
747//	archiveRefspec := fmt.Sprintf("%s:%s", archiveRefPattern, archiveRefPattern)
748//	err := repo.runGitCommandInline("push", remote, notesRefspec, archiveRefspec)
749//	if err != nil {
750//		return fmt.Errorf("Failed to push the local archive to the remote '%s': %v", remote, err)
751//	}
752//	return nil
753//}
754//
755//func getRemoteNotesRef(remote, localNotesRef string) string {
756//	relativeNotesRef := strings.TrimPrefix(localNotesRef, "refs/notes/")
757//	return "refs/notes/" + remote + "/" + relativeNotesRef
758//}
759//
760//func (repo *GitRepo) mergeRemoteNotes(remote, notesRefPattern string) error {
761//	remoteRefs, err := repo.runGitCommand("ls-remote", remote, notesRefPattern)
762//	if err != nil {
763//		return err
764//	}
765//	for _, line := range strings.Split(remoteRefs, "\n") {
766//		lineParts := strings.Split(line, "\t")
767//		if len(lineParts) == 2 {
768//			ref := lineParts[1]
769//			remoteRef := getRemoteNotesRef(remote, ref)
770//			_, err := repo.runGitCommand("notes", "--ref", ref, "merge", remoteRef, "-s", "cat_sort_uniq")
771//			if err != nil {
772//				return err
773//			}
774//		}
775//	}
776//	return nil
777//}
778//
779//// PullNotes fetches the contents of the given notes ref from a remote repo,
780//// and then merges them with the corresponding local notes using the
781//// "cat_sort_uniq" strategy.
782//func (repo *GitRepo) PullNotes(remote, notesRefPattern string) error {
783//	remoteNotesRefPattern := getRemoteNotesRef(remote, notesRefPattern)
784//	fetchRefSpec := fmt.Sprintf("+%s:%s", notesRefPattern, remoteNotesRefPattern)
785//	err := repo.runGitCommandInline("fetch", remote, fetchRefSpec)
786//	if err != nil {
787//		return err
788//	}
789//
790//	return repo.mergeRemoteNotes(remote, notesRefPattern)
791//}
792//
793//func getRemoteArchiveRef(remote, archiveRefPattern string) string {
794//	relativeArchiveRef := strings.TrimPrefix(archiveRefPattern, "refs/devtools/archives/")
795//	return "refs/devtools/remoteArchives/" + remote + "/" + relativeArchiveRef
796//}
797//
798//func (repo *GitRepo) mergeRemoteArchives(remote, archiveRefPattern string) error {
799//	remoteRefs, err := repo.runGitCommand("ls-remote", remote, archiveRefPattern)
800//	if err != nil {
801//		return err
802//	}
803//	for _, line := range strings.Split(remoteRefs, "\n") {
804//		lineParts := strings.Split(line, "\t")
805//		if len(lineParts) == 2 {
806//			ref := lineParts[1]
807//			remoteRef := getRemoteArchiveRef(remote, ref)
808//			if err := repo.mergeArchives(ref, remoteRef); err != nil {
809//				return err
810//			}
811//		}
812//	}
813//	return nil
814//}
815//
816//// PullNotesAndArchive fetches the contents of the notes and archives refs from
817//// a remote repo, and merges them with the corresponding local refs.
818////
819//// For notes refs, we assume that every note can be automatically merged using
820//// the 'cat_sort_uniq' strategy (the git-appraise schemas fit that requirement),
821//// so we automatically merge the remote notes into the local notes.
822////
823//// For "archive" refs, they are expected to be used solely for maintaining
824//// reachability of commits that are part of the history of any reviews,
825//// so we do not maintain any consistency with their tree objects. Instead,
826//// we merely ensure that their history graph includes every commit that we
827//// intend to keep.
828//func (repo *GitRepo) PullNotesAndArchive(remote, notesRefPattern, archiveRefPattern string) error {
829//	remoteArchiveRef := getRemoteArchiveRef(remote, archiveRefPattern)
830//	archiveFetchRefSpec := fmt.Sprintf("+%s:%s", archiveRefPattern, remoteArchiveRef)
831//
832//	remoteNotesRefPattern := getRemoteNotesRef(remote, notesRefPattern)
833//	notesFetchRefSpec := fmt.Sprintf("+%s:%s", notesRefPattern, remoteNotesRefPattern)
834//
835//	err := repo.runGitCommandInline("fetch", remote, notesFetchRefSpec, archiveFetchRefSpec)
836//	if err != nil {
837//		return err
838//	}
839//
840//	if err := repo.mergeRemoteNotes(remote, notesRefPattern); err != nil {
841//		return err
842//	}
843//	if err := repo.mergeRemoteArchives(remote, archiveRefPattern); err != nil {
844//		return err
845//	}
846//	return nil
847//}
848*/