github.com/btwiuse/jiri@v0.0.0-20191125065820-53353bcfef54/gitutil/git.go (about)

     1  // Copyright 2015 The Vanadium Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package gitutil
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/hex"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"runtime"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"github.com/btwiuse/jiri"
    20  	"github.com/btwiuse/jiri/envvar"
    21  )
    22  
    23  type GitError struct {
    24  	Root        string
    25  	Args        []string
    26  	Output      string
    27  	ErrorOutput string
    28  	err         error
    29  }
    30  
    31  func Error(output, errorOutput string, err error, root string, args ...string) GitError {
    32  	return GitError{
    33  		Root:        root,
    34  		Args:        args,
    35  		Output:      output,
    36  		ErrorOutput: errorOutput,
    37  		err:         err,
    38  	}
    39  }
    40  
    41  func (ge GitError) Error() string {
    42  	result := fmt.Sprintf("(%s)", ge.Root)
    43  	result = "'git "
    44  	result += strings.Join(ge.Args, " ")
    45  	result += "' failed:\n"
    46  	result += "stdout:\n"
    47  	result += ge.Output + "\n"
    48  	result += "stderr:\n"
    49  	result += ge.ErrorOutput
    50  	result += "\ncommand fail error: " + ge.err.Error()
    51  	return result
    52  }
    53  
    54  type Git struct {
    55  	jirix     *jiri.X
    56  	opts      map[string]string
    57  	rootDir   string
    58  	userName  string
    59  	userEmail string
    60  }
    61  
    62  type gitOpt interface {
    63  	gitOpt()
    64  }
    65  type AuthorDateOpt string
    66  type CommitterDateOpt string
    67  type RootDirOpt string
    68  type UserNameOpt string
    69  type UserEmailOpt string
    70  
    71  func (AuthorDateOpt) gitOpt()    {}
    72  func (CommitterDateOpt) gitOpt() {}
    73  func (RootDirOpt) gitOpt()       {}
    74  func (UserNameOpt) gitOpt()      {}
    75  func (UserEmailOpt) gitOpt()     {}
    76  
    77  type Reference struct {
    78  	Name     string
    79  	Revision string
    80  	IsHead   bool
    81  }
    82  
    83  type Branch struct {
    84  	*Reference
    85  	Tracking *Reference
    86  }
    87  
    88  type Revision string
    89  type BranchName string
    90  
    91  const (
    92  	RemoteType = "remote"
    93  	LocalType  = "local"
    94  )
    95  
    96  // New is the Git factory.
    97  func New(jirix *jiri.X, opts ...gitOpt) *Git {
    98  	rootDir := ""
    99  	userName := ""
   100  	userEmail := ""
   101  	env := map[string]string{}
   102  	for _, opt := range opts {
   103  		switch typedOpt := opt.(type) {
   104  		case AuthorDateOpt:
   105  			env["GIT_AUTHOR_DATE"] = string(typedOpt)
   106  		case CommitterDateOpt:
   107  			env["GIT_COMMITTER_DATE"] = string(typedOpt)
   108  		case RootDirOpt:
   109  			rootDir = string(typedOpt)
   110  		case UserNameOpt:
   111  			userName = string(typedOpt)
   112  		case UserEmailOpt:
   113  			userEmail = string(typedOpt)
   114  		}
   115  	}
   116  	return &Git{
   117  		jirix:     jirix,
   118  		opts:      env,
   119  		rootDir:   rootDir,
   120  		userName:  userName,
   121  		userEmail: userEmail,
   122  	}
   123  }
   124  
   125  // Add adds a file to staging.
   126  func (g *Git) Add(file string) error {
   127  	return g.run("add", file)
   128  }
   129  
   130  // Add adds a file to staging.
   131  func (g *Git) AddUpdatedFiles() error {
   132  	return g.run("add", "-u")
   133  }
   134  
   135  // AddRemote adds a new remote with the given name and path.
   136  func (g *Git) AddRemote(name, path string) error {
   137  	return g.run("remote", "add", name, path)
   138  }
   139  
   140  // AddOrReplaceRemote adds a new remote with given name and path. If the name
   141  // already exists, it replaces the named remote with new path.
   142  func (g *Git) AddOrReplaceRemote(name, path string) error {
   143  	configStr := fmt.Sprintf("remote.%s.url", name)
   144  	if err := g.Config(configStr, path); err != nil {
   145  		return err
   146  	}
   147  	configStr = fmt.Sprintf("remote.%s.fetch", name)
   148  	if err := g.Config(configStr, "+refs/heads/*:refs/remotes/origin/*"); err != nil {
   149  		return err
   150  	}
   151  	return nil
   152  }
   153  
   154  // GetRemoteBranchesContaining returns a slice of the remote branches
   155  // which contains the given commit
   156  func (g *Git) GetRemoteBranchesContaining(commit string) ([]string, error) {
   157  	branches, _, err := g.GetBranches("-r", "--contains", commit)
   158  	return branches, err
   159  }
   160  
   161  // BranchesDiffer tests whether two branches have any changes between them.
   162  func (g *Git) BranchesDiffer(branch1, branch2 string) (bool, error) {
   163  	out, err := g.runOutput("--no-pager", "diff", "--name-only", branch1+".."+branch2)
   164  	if err != nil {
   165  		return false, err
   166  	}
   167  	// If output is empty, then there is no difference.
   168  	if len(out) == 0 {
   169  		return false, nil
   170  	}
   171  	// Otherwise there is a difference.
   172  	return true, nil
   173  }
   174  
   175  // GetAllBranchesInfo returns information about all branches.
   176  func (g *Git) GetAllBranchesInfo() ([]Branch, error) {
   177  	branchesInfo, err := g.runOutput("for-each-ref", "--format", "%(refname:short):%(upstream:short):%(objectname):%(HEAD):%(upstream)", "refs/heads")
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  	var upstreamRefs []string
   182  	var branches []Branch
   183  	for _, branchInfo := range branchesInfo {
   184  		s := strings.SplitN(branchInfo, ":", 5)
   185  		branch := Branch{
   186  			&Reference{
   187  				Name:     s[0],
   188  				Revision: s[2],
   189  				IsHead:   s[3] == "*",
   190  			},
   191  			nil,
   192  		}
   193  		if s[1] != "" {
   194  			upstreamRefs = append(upstreamRefs, s[4])
   195  		}
   196  		branches = append(branches, branch)
   197  	}
   198  
   199  	args := append([]string{"show-ref"}, upstreamRefs...)
   200  	if refsInfo, err := g.runOutput(args...); err == nil {
   201  		refs := map[string]string{}
   202  		for _, info := range refsInfo {
   203  			strs := strings.SplitN(info, " ", 2)
   204  			refs[strs[1]] = strs[0]
   205  		}
   206  		for i, branchInfo := range branchesInfo {
   207  			s := strings.SplitN(branchInfo, ":", 5)
   208  			if s[1] != "" {
   209  				branches[i].Tracking = &Reference{
   210  					Name:     s[1],
   211  					Revision: refs[s[4]],
   212  				}
   213  			}
   214  		}
   215  	}
   216  
   217  	return branches, nil
   218  }
   219  
   220  // IsRevAvailable runs cat-file on a commit hash is available locally.
   221  func (g *Git) IsRevAvailable(rev string) bool {
   222  	// TODO: (haowei@)(11517) We are having issues with corrupted
   223  	// cache data on mac builders. Return a non-nil error
   224  	// to force the mac builders fetch from remote to avoid
   225  	// jiri checkout failures.
   226  	if runtime.GOOS == "darwin" {
   227  		return false
   228  	}
   229  	// test if rev is a legit sha1 hash string
   230  	if _, err := hex.DecodeString(rev); len(rev) != 40 || err != nil {
   231  		return false
   232  	}
   233  
   234  	if err := g.run("cat-file", "-e", rev); err != nil {
   235  		return false
   236  	}
   237  	return true
   238  }
   239  
   240  // CheckoutBranch checks out the given branch.
   241  func (g *Git) CheckoutBranch(branch string, opts ...CheckoutOpt) error {
   242  	args := []string{"checkout"}
   243  	var force ForceOpt = false
   244  	var detach DetachOpt = false
   245  	for _, opt := range opts {
   246  		switch typedOpt := opt.(type) {
   247  		case ForceOpt:
   248  			force = typedOpt
   249  		case DetachOpt:
   250  			detach = typedOpt
   251  		}
   252  	}
   253  	if force {
   254  		args = append(args, "-f")
   255  	}
   256  	if detach {
   257  		args = append(args, "--detach")
   258  	}
   259  	args = append(args, branch)
   260  	return g.run(args...)
   261  }
   262  
   263  // Clone clones the given repository to the given local path.  If reference is
   264  // not empty it uses the given path as a reference/shared repo.
   265  func (g *Git) Clone(repo, path string, opts ...CloneOpt) error {
   266  	args := []string{"clone"}
   267  	for _, opt := range opts {
   268  		switch typedOpt := opt.(type) {
   269  		case BareOpt:
   270  			if typedOpt {
   271  				args = append(args, "--bare")
   272  			}
   273  		case ReferenceOpt:
   274  			reference := string(typedOpt)
   275  			if reference != "" {
   276  				args = append(args, []string{"--reference-if-able", reference}...)
   277  			}
   278  		case SharedOpt:
   279  			if typedOpt {
   280  				args = append(args, []string{"--shared", "--local"}...)
   281  			}
   282  		case NoCheckoutOpt:
   283  			if typedOpt {
   284  				args = append(args, "--no-checkout")
   285  			}
   286  		case DepthOpt:
   287  			if typedOpt > 0 {
   288  				args = append(args, []string{"--depth", strconv.Itoa(int(typedOpt))}...)
   289  			}
   290  		case OmitBlobsOpt:
   291  			if typedOpt {
   292  				args = append(args, "--filter=blob:none")
   293  			}
   294  		}
   295  	}
   296  	args = append(args, repo)
   297  	args = append(args, path)
   298  	return g.run(args...)
   299  }
   300  
   301  // CloneMirror clones the given repository using mirror flag.
   302  func (g *Git) CloneMirror(repo, path string, depth int) error {
   303  	args := []string{"clone", "--mirror"}
   304  	if depth > 0 {
   305  		args = append(args, []string{"--depth", strconv.Itoa(depth)}...)
   306  	}
   307  	args = append(args, []string{repo, path}...)
   308  	return g.run(args...)
   309  }
   310  
   311  // CloneRecursive clones the given repository recursively to the given local path.
   312  func (g *Git) CloneRecursive(repo, path string) error {
   313  	return g.run("clone", "--recursive", repo, path)
   314  }
   315  
   316  // Commit commits all files in staging with an empty message.
   317  func (g *Git) Commit() error {
   318  	return g.run("commit", "--allow-empty", "--allow-empty-message", "--no-edit")
   319  }
   320  
   321  // CommitAmend amends the previous commit with the currently staged
   322  // changes. Empty commits are allowed.
   323  func (g *Git) CommitAmend() error {
   324  	return g.run("commit", "--amend", "--allow-empty", "--no-edit")
   325  }
   326  
   327  // CommitAmendWithMessage amends the previous commit with the
   328  // currently staged changes, and the given message. Empty commits are
   329  // allowed.
   330  func (g *Git) CommitAmendWithMessage(message string) error {
   331  	return g.run("commit", "--amend", "--allow-empty", "-m", message)
   332  }
   333  
   334  // CommitAndEdit commits all files in staging and allows the user to
   335  // edit the commit message.
   336  func (g *Git) CommitAndEdit() error {
   337  	args := []string{"commit", "--allow-empty"}
   338  	return g.runInteractive(args...)
   339  }
   340  
   341  // CommitFile commits the given file with the given commit message.
   342  func (g *Git) CommitFile(fileName, message string) error {
   343  	if err := g.Add(fileName); err != nil {
   344  		return err
   345  	}
   346  	return g.CommitWithMessage(message)
   347  }
   348  
   349  // CommitMessages returns the concatenation of all commit messages on
   350  // <branch> that are not also on <baseBranch>.
   351  func (g *Git) CommitMessages(branch, baseBranch string) (string, error) {
   352  	out, err := g.runOutput("log", "--no-merges", baseBranch+".."+branch)
   353  	if err != nil {
   354  		return "", err
   355  	}
   356  	return strings.Join(out, "\n"), nil
   357  }
   358  
   359  // CommitNoVerify commits all files in staging with the given
   360  // message and skips all git-hooks.
   361  func (g *Git) CommitNoVerify(message string) error {
   362  	return g.run("commit", "--allow-empty", "--allow-empty-message", "--no-verify", "-m", message)
   363  }
   364  
   365  // CommitWithMessage commits all files in staging with the given
   366  // message.
   367  func (g *Git) CommitWithMessage(message string) error {
   368  	return g.run("commit", "--allow-empty", "--allow-empty-message", "-m", message)
   369  }
   370  
   371  // CommitWithMessage commits all files in staging and allows the user
   372  // to edit the commit message. The given message will be used as the
   373  // default.
   374  func (g *Git) CommitWithMessageAndEdit(message string) error {
   375  	args := []string{"commit", "--allow-empty", "-e", "-m", message}
   376  	return g.runInteractive(args...)
   377  }
   378  
   379  // Committers returns a list of committers for the current repository
   380  // along with the number of their commits.
   381  func (g *Git) Committers() ([]string, error) {
   382  	out, err := g.runOutput("shortlog", "-s", "-n", "-e")
   383  	if err != nil {
   384  		return nil, err
   385  	}
   386  	return out, nil
   387  }
   388  
   389  // Provides list of commits reachable from rev but not from base
   390  // rev can be a branch/tag or revision name.
   391  func (g *Git) ExtraCommits(rev, base string) ([]string, error) {
   392  	return g.runOutput("rev-list", base+".."+rev)
   393  }
   394  
   395  // CountCommits returns the number of commits on <branch> that are not
   396  // on <base>.
   397  func (g *Git) CountCommits(branch, base string) (int, error) {
   398  	args := []string{"rev-list", "--count", branch}
   399  	if base != "" {
   400  		args = append(args, "^"+base)
   401  	}
   402  	args = append(args, "--")
   403  	out, err := g.runOutput(args...)
   404  	if err != nil {
   405  		return 0, err
   406  	}
   407  	if got, want := len(out), 1; got != want {
   408  		return 0, fmt.Errorf("unexpected length of %v: got %v, want %v", out, got, want)
   409  	}
   410  	count, err := strconv.Atoi(out[0])
   411  	if err != nil {
   412  		return 0, fmt.Errorf("Atoi(%v) failed: %v", out[0], err)
   413  	}
   414  	return count, nil
   415  }
   416  
   417  // Get one line log
   418  func (g *Git) OneLineLog(rev string) (string, error) {
   419  	out, err := g.runOutput("log", "--pretty=oneline", "-n", "1", "--abbrev-commit", rev)
   420  	if err != nil {
   421  		return "", err
   422  	}
   423  	if got, want := len(out), 1; got != want {
   424  		g.jirix.Logger.Warningf("wanted one line log, got %d line log: %q", got, out)
   425  	}
   426  	return out[0], nil
   427  }
   428  
   429  // CreateBranch creates a new branch with the given name.
   430  func (g *Git) CreateBranch(branch string) error {
   431  	return g.run("branch", branch)
   432  }
   433  
   434  // CreateBranchFromRef creates a new branch from an existing reference.
   435  func (g *Git) CreateBranchFromRef(branch, ref string) error {
   436  	return g.run("branch", branch, ref)
   437  }
   438  
   439  // CreateAndCheckoutBranch creates a new branch with the given name
   440  // and checks it out.
   441  func (g *Git) CreateAndCheckoutBranch(branch string) error {
   442  	return g.run("checkout", "-b", branch)
   443  }
   444  
   445  // SetUpstream sets the upstream branch to the given one.
   446  func (g *Git) SetUpstream(branch, upstream string) error {
   447  	return g.run("branch", "-u", upstream, branch)
   448  }
   449  
   450  // LsRemote lists referneces in a remote repository.
   451  func (g *Git) LsRemote(args ...string) (string, error) {
   452  	a := []string{"ls-remote"}
   453  	a = append(a, args...)
   454  	out, err := g.runOutput(a...)
   455  	if err != nil {
   456  		return "", err
   457  	}
   458  	if got, want := len(out), 1; got != want {
   459  		return "", fmt.Errorf("git ls-remote %s: unexpected length of %s: got %d, want %d", strings.Join(args, " "), out, got, want)
   460  	}
   461  	return out[0], nil
   462  }
   463  
   464  // CreateBranchWithUpstream creates a new branch and sets the upstream
   465  // repository to the given upstream.
   466  func (g *Git) CreateBranchWithUpstream(branch, upstream string) error {
   467  	return g.run("branch", branch, upstream)
   468  }
   469  
   470  // ShortHash returns the short hash for a given reference.
   471  func (g *Git) ShortHash(ref string) (string, error) {
   472  	out, err := g.runOutput("rev-parse", "--short", ref)
   473  	if err != nil {
   474  		return "", err
   475  	}
   476  	if got, want := len(out), 1; got != want {
   477  		return "", fmt.Errorf("unexpected length of %v: got %v, want %v", out, got, want)
   478  	}
   479  	return out[0], nil
   480  }
   481  
   482  // UserInfoForCommit returns user name and email for a given reference.
   483  func (g *Git) UserInfoForCommit(ref string) (string, string, error) {
   484  	out, err := g.runOutput("log", "-n", "1", "--format=format:%cn:%ce", ref)
   485  	if err != nil {
   486  		return "", "", err
   487  	}
   488  	info := strings.SplitN(out[0], ":", 2)
   489  	return info[0], info[1], nil
   490  }
   491  
   492  // CurrentBranchName returns the name of the current branch.
   493  func (g *Git) CurrentBranchName() (string, error) {
   494  	out, err := g.runOutput("rev-parse", "--abbrev-ref", "HEAD")
   495  	if err != nil {
   496  		return "", err
   497  	}
   498  	if got, want := len(out), 1; got != want {
   499  		return "", fmt.Errorf("unexpected length of %v: got %v, want %v", out, got, want)
   500  	}
   501  	return out[0], nil
   502  }
   503  
   504  func (g *Git) GetSymbolicRef() (string, error) {
   505  	out, err := g.runOutput("symbolic-ref", "-q", "HEAD")
   506  	if err != nil {
   507  		return "", err
   508  	}
   509  	if got, want := len(out), 1; got != want {
   510  		return "", fmt.Errorf("unexpected length of %v: got %v, want %v", out, got, want)
   511  	}
   512  	return out[0], nil
   513  }
   514  
   515  // RemoteBranchName returns the name of the tracking branch stripping remote name from it.
   516  // It will search recursively if current branch tracks a local branch.
   517  func (g *Git) RemoteBranchName() (string, error) {
   518  	branch, err := g.CurrentBranchName()
   519  	if err != nil || branch == "" {
   520  		return "", err
   521  	}
   522  
   523  	trackingBranch, err := g.TrackingBranchName()
   524  	if err != nil || trackingBranch == "" {
   525  		return "", err
   526  	}
   527  
   528  	for {
   529  		out, err := g.runOutput("config", "branch."+branch+".remote")
   530  		if err != nil || len(out) == 0 {
   531  			return "", err
   532  		}
   533  		if got, want := len(out), 1; got != want {
   534  			return "", fmt.Errorf("unexpected length of %v: got %v, want %v", out, got, want)
   535  		}
   536  		// check if current branch tracks local branch
   537  		if out[0] != "." {
   538  			return strings.Replace(trackingBranch, out[0]+"/", "", 1), nil
   539  		} else {
   540  			branch = trackingBranch
   541  			if trackingBranch, err = g.TrackingBranchFromSymbolicRef("refs/heads/" + trackingBranch); err != nil || trackingBranch == "" {
   542  				return "", err
   543  			}
   544  		}
   545  	}
   546  }
   547  
   548  // TrackingBranchName returns the name of the tracking branch.
   549  func (g *Git) TrackingBranchName() (string, error) {
   550  	currentRef, err := g.GetSymbolicRef()
   551  	if err != nil {
   552  		return "", err
   553  	}
   554  	return g.TrackingBranchFromSymbolicRef(currentRef)
   555  }
   556  
   557  // TrackingBranchFromSymbolicRef returns the name of the tracking branch for provided ref
   558  func (g *Git) TrackingBranchFromSymbolicRef(ref string) (string, error) {
   559  	out, err := g.runOutput("for-each-ref", "--format", "%(upstream:short)", ref)
   560  	if err != nil || len(out) == 0 {
   561  		return "", err
   562  	}
   563  	if got, want := len(out), 1; got != want {
   564  		return "", fmt.Errorf("unexpected length of %v: got %v, want %v", out, got, want)
   565  	}
   566  	return out[0], nil
   567  }
   568  
   569  func (g *Git) IsOnBranch() bool {
   570  	_, err := g.runOutput("symbolic-ref", "-q", "HEAD")
   571  	return err == nil
   572  }
   573  
   574  // CurrentRevision returns the current revision.
   575  func (g *Git) CurrentRevision() (string, error) {
   576  	return g.CurrentRevisionForRef("HEAD")
   577  }
   578  
   579  // CurrentRevisionForRef gets current rev for ref/branch/tags
   580  func (g *Git) CurrentRevisionForRef(ref string) (string, error) {
   581  	out, err := g.runOutput("rev-list", "-n", "1", ref)
   582  	if err != nil {
   583  		return "", err
   584  	}
   585  	if got, want := len(out), 1; got != want {
   586  		return "", fmt.Errorf("unexpected length of %v: got %v, want %v", out, got, want)
   587  	}
   588  	return out[0], nil
   589  }
   590  
   591  // CurrentRevisionOfBranch returns the current revision of the given branch.
   592  func (g *Git) CurrentRevisionOfBranch(branch string) (string, error) {
   593  	// Using rev-list instead of rev-parse as latter doesn't work well with tag
   594  	out, err := g.runOutput("rev-list", "-n", "1", branch)
   595  	if err != nil {
   596  		return "", err
   597  	}
   598  	if got, want := len(out), 1; got != want {
   599  		return "", fmt.Errorf("unexpected length of %v: got %v, want %v", out, got, want)
   600  	}
   601  	return out[0], nil
   602  }
   603  
   604  func (g *Git) CherryPick(rev string) error {
   605  	err := g.run("cherry-pick", rev)
   606  	return err
   607  }
   608  
   609  // DeleteBranch deletes the given branch.
   610  func (g *Git) DeleteBranch(branch string, opts ...DeleteBranchOpt) error {
   611  	args := []string{"branch"}
   612  	force := false
   613  	for _, opt := range opts {
   614  		switch typedOpt := opt.(type) {
   615  		case ForceOpt:
   616  			force = bool(typedOpt)
   617  		}
   618  	}
   619  	if force {
   620  		args = append(args, "-D")
   621  	} else {
   622  		args = append(args, "-d")
   623  	}
   624  	args = append(args, branch)
   625  	return g.run(args...)
   626  }
   627  
   628  // DirExistsOnBranch returns true if a directory with the given name
   629  // exists on the branch.  If branch is empty it defaults to "master".
   630  func (g *Git) DirExistsOnBranch(dir, branch string) bool {
   631  	if dir == "." {
   632  		dir = ""
   633  	}
   634  	if branch == "" {
   635  		branch = "master"
   636  	}
   637  	args := []string{"ls-tree", "-d", branch + ":" + dir}
   638  	return g.run(args...) == nil
   639  }
   640  
   641  // CreateLightweightTag creates a lightweight tag with a given name.
   642  func (g *Git) CreateLightweightTag(name string) error {
   643  	return g.run("tag", name)
   644  }
   645  
   646  // Fetch fetches refs and tags from the given remote.
   647  func (g *Git) Fetch(remote string, opts ...FetchOpt) error {
   648  	return g.FetchRefspec(remote, "", opts...)
   649  }
   650  
   651  // FetchRefspec fetches refs and tags from the given remote for a particular refspec.
   652  func (g *Git) FetchRefspec(remote, refspec string, opts ...FetchOpt) error {
   653  	tags := false
   654  	all := false
   655  	prune := false
   656  	updateShallow := false
   657  	depth := 0
   658  	fetchTag := ""
   659  	for _, opt := range opts {
   660  		switch typedOpt := opt.(type) {
   661  		case TagsOpt:
   662  			tags = bool(typedOpt)
   663  		case AllOpt:
   664  			all = bool(typedOpt)
   665  		case PruneOpt:
   666  			prune = bool(typedOpt)
   667  		case DepthOpt:
   668  			depth = int(typedOpt)
   669  		case UpdateShallowOpt:
   670  			updateShallow = bool(typedOpt)
   671  		case FetchTagOpt:
   672  			fetchTag = string(typedOpt)
   673  		}
   674  	}
   675  	args := []string{}
   676  	args = append(args, "fetch")
   677  	if prune {
   678  		args = append(args, "-p")
   679  	}
   680  	if tags {
   681  		args = append(args, "--tags")
   682  	}
   683  	if depth > 0 {
   684  		args = append(args, "--depth", strconv.Itoa(depth))
   685  	}
   686  	if updateShallow {
   687  		args = append(args, "--update-shallow")
   688  	}
   689  	if all {
   690  		args = append(args, "--all")
   691  	}
   692  	if remote != "" {
   693  		args = append(args, remote)
   694  	}
   695  	if fetchTag != "" {
   696  		args = append(args, "tag", fetchTag)
   697  	}
   698  	if refspec != "" {
   699  		args = append(args, refspec)
   700  	}
   701  
   702  	return g.run(args...)
   703  }
   704  
   705  // FilesWithUncommittedChanges returns the list of files that have
   706  // uncommitted changes.
   707  func (g *Git) FilesWithUncommittedChanges() ([]string, error) {
   708  	out, err := g.runOutput("diff", "--name-only", "--no-ext-diff")
   709  	if err != nil {
   710  		return nil, err
   711  	}
   712  	out2, err := g.runOutput("diff", "--cached", "--name-only", "--no-ext-diff")
   713  	if err != nil {
   714  		return nil, err
   715  	}
   716  	return append(out, out2...), nil
   717  }
   718  
   719  // MergedBranches returns the list of all branches that were already merged.
   720  func (g *Git) MergedBranches(ref string) ([]string, error) {
   721  	branches, _, err := g.GetBranches("--merged", ref)
   722  	return branches, err
   723  }
   724  
   725  // GetBranches returns a slice of the local branches of the current
   726  // repository, followed by the name of the current branch. The
   727  // behavior can be customized by providing optional arguments
   728  // (e.g. --merged).
   729  func (g *Git) GetBranches(args ...string) ([]string, string, error) {
   730  	args = append([]string{"branch"}, args...)
   731  	out, err := g.runOutput(args...)
   732  	if err != nil {
   733  		return nil, "", err
   734  	}
   735  	branches, current := []string{}, ""
   736  	for _, branch := range out {
   737  		if strings.HasPrefix(branch, "*") {
   738  			branch = strings.TrimSpace(strings.TrimPrefix(branch, "*"))
   739  			if g.IsOnBranch() {
   740  				current = branch
   741  			} else {
   742  				// Do not append detached head
   743  				continue
   744  			}
   745  		}
   746  		branches = append(branches, strings.TrimSpace(branch))
   747  	}
   748  	return branches, current, nil
   749  }
   750  
   751  // BranchExists tests whether a branch with the given name exists in
   752  // the local repository.
   753  func (g *Git) BranchExists(branch string) (bool, error) {
   754  	var stdout, stderr bytes.Buffer
   755  	args := []string{"rev-parse", "--verify", "--quiet", branch}
   756  	err := g.runGit(&stdout, &stderr, args...)
   757  	if err != nil && stderr.String() != "" {
   758  		return false, Error(stdout.String(), stderr.String(), err, g.rootDir, args...)
   759  	}
   760  	return stdout.String() != "", nil
   761  }
   762  
   763  // ListRemoteBranchesContainingRef returns a slice of the remote branches
   764  // which contains the given commit
   765  func (g *Git) ListRemoteBranchesContainingRef(commit string) (map[string]bool, error) {
   766  	branches, _, err := g.GetBranches("-r", "--contains", commit)
   767  	if err != nil {
   768  		return nil, err
   769  	}
   770  	m := make(map[string]bool)
   771  	for _, branch := range branches {
   772  		m[branch] = true
   773  	}
   774  	return m, nil
   775  }
   776  
   777  // ListBranchesContainingRef returns a slice of the local branches
   778  // which contains the given commit
   779  func (g *Git) ListBranchesContainingRef(commit string) (map[string]bool, error) {
   780  	branches, _, err := g.GetBranches("--contains", commit)
   781  	if err != nil {
   782  		return nil, err
   783  	}
   784  	m := make(map[string]bool)
   785  	for _, branch := range branches {
   786  		m[branch] = true
   787  	}
   788  	return m, nil
   789  }
   790  
   791  // Grep searches for matching text and returns a list of lines from
   792  // `git grep`.
   793  func (g *Git) Grep(query string, pathSpecs []string, flags ...string) ([]string, error) {
   794  	args := append([]string{"grep"}, flags...)
   795  	if query != "" {
   796  		args = append(args, query)
   797  	}
   798  	if len(pathSpecs) != 0 {
   799  		args = append(args, "--")
   800  		args = append(args, pathSpecs...)
   801  	}
   802  	// TODO(ianloic): handle patterns that start with "-"
   803  	// TODO(ianloic): handle different pattern types (-i, -P, -E, etc)
   804  	// TODO(ianloic): handle different response types (--full-name, -v, --name-only, etc)
   805  	return g.runOutput(args...)
   806  }
   807  
   808  // HasUncommittedChanges checks whether the current branch contains
   809  // any uncommitted changes.
   810  func (g *Git) HasUncommittedChanges() (bool, error) {
   811  	out, err := g.FilesWithUncommittedChanges()
   812  	if err != nil {
   813  		return false, err
   814  	}
   815  	return len(out) != 0, nil
   816  }
   817  
   818  // HasUntrackedFiles checks whether the current branch contains any
   819  // untracked files.
   820  func (g *Git) HasUntrackedFiles() (bool, error) {
   821  	out, err := g.UntrackedFiles()
   822  	if err != nil {
   823  		return false, err
   824  	}
   825  	return len(out) != 0, nil
   826  }
   827  
   828  // Init initializes a new git repository.
   829  func (g *Git) Init(path string, opts ...CloneOpt) error {
   830  	args := []string{"init"}
   831  	for _, opt := range opts {
   832  		switch typedOpt := opt.(type) {
   833  		case BareOpt:
   834  			if typedOpt {
   835  				args = append(args, "--bare")
   836  			}
   837  		}
   838  	}
   839  	args = append(args, path)
   840  	return g.run(args...)
   841  }
   842  
   843  // IsFileCommitted tests whether the given file has been committed to
   844  // the repository.
   845  func (g *Git) IsFileCommitted(file string) bool {
   846  	// Check if file is still in staging enviroment.
   847  	if out, _ := g.runOutput("status", "--porcelain", file); len(out) > 0 {
   848  		return false
   849  	}
   850  	// Check if file is unknown to git.
   851  	return g.run("ls-files", file, "--error-unmatch") == nil
   852  }
   853  
   854  func (g *Git) ShortStatus() (string, error) {
   855  	out, err := g.runOutput("status", "-s")
   856  	if err != nil {
   857  		return "", err
   858  	}
   859  	return strings.Join(out, "\n"), nil
   860  }
   861  
   862  func (g *Git) CommitMsg(ref string) (string, error) {
   863  	out, err := g.runOutput("log", "-n", "1", "--format=format:%B", ref)
   864  	if err != nil {
   865  		return "", err
   866  	}
   867  	return strings.Join(out, "\n"), nil
   868  }
   869  
   870  // LatestCommitMessage returns the latest commit message on the
   871  // current branch.
   872  func (g *Git) LatestCommitMessage() (string, error) {
   873  	out, err := g.runOutput("log", "-n", "1", "--format=format:%B")
   874  	if err != nil {
   875  		return "", err
   876  	}
   877  	return strings.Join(out, "\n"), nil
   878  }
   879  
   880  // Log returns a list of commits on <branch> that are not on <base>,
   881  // using the specified format.
   882  func (g *Git) Log(branch, base, format string) ([][]string, error) {
   883  	n, err := g.CountCommits(branch, base)
   884  	if err != nil {
   885  		return nil, err
   886  	}
   887  	result := [][]string{}
   888  	for i := 0; i < n; i++ {
   889  		skipArg := fmt.Sprintf("--skip=%d", i)
   890  		formatArg := fmt.Sprintf("--format=%s", format)
   891  		branchArg := fmt.Sprintf("%v..%v", base, branch)
   892  		out, err := g.runOutput("log", "-1", skipArg, formatArg, branchArg)
   893  		if err != nil {
   894  			return nil, err
   895  		}
   896  		result = append(result, out)
   897  	}
   898  	return result, nil
   899  }
   900  
   901  // Merge merges all commits from <branch> to the current branch. If
   902  // <squash> is set, then all merged commits are squashed into a single
   903  // commit.
   904  func (g *Git) Merge(branch string, opts ...MergeOpt) error {
   905  	args := []string{"merge"}
   906  	squash := false
   907  	strategy := ""
   908  	resetOnFailure := true
   909  	for _, opt := range opts {
   910  		switch typedOpt := opt.(type) {
   911  		case SquashOpt:
   912  			squash = bool(typedOpt)
   913  		case StrategyOpt:
   914  			strategy = string(typedOpt)
   915  		case ResetOnFailureOpt:
   916  			resetOnFailure = bool(typedOpt)
   917  		case FfOnlyOpt:
   918  			args = append(args, "--ff-only")
   919  		}
   920  	}
   921  	if squash {
   922  		args = append(args, "--squash")
   923  	} else {
   924  		args = append(args, "--no-squash")
   925  	}
   926  	if strategy != "" {
   927  		args = append(args, fmt.Sprintf("--strategy=%v", strategy))
   928  	}
   929  	args = append(args, branch)
   930  	if out, err := g.runOutput(args...); err != nil {
   931  		if resetOnFailure {
   932  			if err2 := g.run("reset", "--merge"); err2 != nil {
   933  				return fmt.Errorf("%v\nCould not git reset while recovering from error: %v", err, err2)
   934  			}
   935  		}
   936  		return fmt.Errorf("%v\n%v", err, strings.Join(out, "\n"))
   937  	}
   938  	return nil
   939  }
   940  
   941  // ModifiedFiles returns a slice of filenames that have changed
   942  // between <baseBranch> and <currentBranch>.
   943  func (g *Git) ModifiedFiles(baseBranch, currentBranch string) ([]string, error) {
   944  	out, err := g.runOutput("diff", "--name-only", baseBranch+".."+currentBranch)
   945  	if err != nil {
   946  		return nil, err
   947  	}
   948  	return out, nil
   949  }
   950  
   951  // Pull pulls the given branch from the given remote.
   952  func (g *Git) Pull(remote, branch string) error {
   953  	if out, err := g.runOutput("pull", remote, branch); err != nil {
   954  		g.run("reset", "--merge")
   955  		return fmt.Errorf("%v\n%v", err, strings.Join(out, "\n"))
   956  	}
   957  	major, minor, err := g.Version()
   958  	if err != nil {
   959  		return err
   960  	}
   961  	// Starting with git 1.8, "git pull <remote> <branch>" does not
   962  	// create the branch "<remote>/<branch>" locally. To avoid the need
   963  	// to account for this, run "git pull", which fails but creates the
   964  	// missing branch, for git 1.7 and older.
   965  	if major < 2 && minor < 8 {
   966  		// This command is expected to fail (with desirable side effects).
   967  		// Use exec.Command instead of runner to prevent this failure from
   968  		// showing up in the console and confusing people.
   969  		command := exec.Command("git", "pull")
   970  		command.Run()
   971  	}
   972  	return nil
   973  }
   974  
   975  // Push pushes the given branch to the given remote.
   976  func (g *Git) Push(remote, branch string, opts ...PushOpt) error {
   977  	args := []string{"push"}
   978  	force := false
   979  	verify := true
   980  	// TODO(youngseokyoon): consider making followTags option default to true, after verifying that
   981  	// it works well for the madb repository.
   982  	followTags := false
   983  	for _, opt := range opts {
   984  		switch typedOpt := opt.(type) {
   985  		case ForceOpt:
   986  			force = bool(typedOpt)
   987  		case VerifyOpt:
   988  			verify = bool(typedOpt)
   989  		case FollowTagsOpt:
   990  			followTags = bool(typedOpt)
   991  		}
   992  	}
   993  	if force {
   994  		args = append(args, "--force")
   995  	}
   996  	if verify {
   997  		args = append(args, "--verify")
   998  	} else {
   999  		args = append(args, "--no-verify")
  1000  	}
  1001  	if followTags {
  1002  		args = append(args, "--follow-tags")
  1003  	}
  1004  	args = append(args, remote, branch)
  1005  	return g.run(args...)
  1006  }
  1007  
  1008  // Rebase rebases to a particular upstream branch.
  1009  func (g *Git) Rebase(upstream string, opts ...RebaseOpt) error {
  1010  	args := []string{"rebase"}
  1011  	rebaseMerges := false
  1012  	for _, opt := range opts {
  1013  		switch typedOpt := opt.(type) {
  1014  		case RebaseMerges:
  1015  			rebaseMerges = bool(typedOpt)
  1016  		}
  1017  	}
  1018  
  1019  	if rebaseMerges {
  1020  		args = append(args, "--rebase-merges")
  1021  	}
  1022  	args = append(args, upstream)
  1023  	return g.run(args...)
  1024  }
  1025  
  1026  // CherryPickAbort aborts an in-progress cherry-pick operation.
  1027  func (g *Git) CherryPickAbort() error {
  1028  	// First check if cherry-pick is in progress
  1029  	path := ".git/CHERRY_PICK_HEAD"
  1030  	if g.rootDir != "" {
  1031  		path = filepath.Join(g.rootDir, path)
  1032  	}
  1033  	if _, err := os.Stat(path); err != nil {
  1034  		if os.IsNotExist(err) {
  1035  			return nil // Not in progress return
  1036  		}
  1037  		return err
  1038  	}
  1039  	return g.run("cherry-pick", "--abort")
  1040  }
  1041  
  1042  // RebaseAbort aborts an in-progress rebase operation.
  1043  func (g *Git) RebaseAbort() error {
  1044  	// First check if rebase is in progress
  1045  	path := ".git/rebase-apply"
  1046  	if g.rootDir != "" {
  1047  		path = filepath.Join(g.rootDir, path)
  1048  	}
  1049  	if _, err := os.Stat(path); err != nil {
  1050  		if os.IsNotExist(err) {
  1051  			return nil // Not in progress return
  1052  		}
  1053  		return err
  1054  	}
  1055  	return g.run("rebase", "--abort")
  1056  }
  1057  
  1058  // Remove removes the given files.
  1059  func (g *Git) Remove(fileNames ...string) error {
  1060  	args := []string{"rm"}
  1061  	args = append(args, fileNames...)
  1062  	return g.run(args...)
  1063  }
  1064  
  1065  func (g *Git) Config(configArgs ...string) error {
  1066  	args := []string{"config"}
  1067  	args = append(args, configArgs...)
  1068  	return g.run(args...)
  1069  }
  1070  
  1071  func (g *Git) ConfigGetKey(key string) (string, error) {
  1072  	out, err := g.runOutput("config", "--get", key)
  1073  	if err != nil {
  1074  		return "", err
  1075  	}
  1076  	if got, want := len(out), 1; got != want {
  1077  		g.jirix.Logger.Warningf("wanted one line log, got %d line log: %q", got, out)
  1078  	}
  1079  	return out[0], nil
  1080  }
  1081  
  1082  // RemoteUrl gets the url of the remote with the given name.
  1083  func (g *Git) RemoteUrl(name string) (string, error) {
  1084  	configKey := fmt.Sprintf("remote.%s.url", name)
  1085  	out, err := g.runOutput("config", "--get", configKey)
  1086  	if err != nil {
  1087  		return "", err
  1088  	}
  1089  	if got, want := len(out), 1; got != want {
  1090  		return "", fmt.Errorf("RemoteUrl: unexpected length of remotes %v: got %v, want %v", out, got, want)
  1091  	}
  1092  	return out[0], nil
  1093  }
  1094  
  1095  // RemoveUntrackedFiles removes untracked files and directories.
  1096  func (g *Git) RemoveUntrackedFiles() error {
  1097  	return g.run("clean", "-d", "-f")
  1098  }
  1099  
  1100  // Reset resets the current branch to the target, discarding any
  1101  // uncommitted changes.
  1102  func (g *Git) Reset(target string, opts ...ResetOpt) error {
  1103  	args := []string{"reset"}
  1104  	mode := "hard"
  1105  	for _, opt := range opts {
  1106  		switch typedOpt := opt.(type) {
  1107  		case ModeOpt:
  1108  			mode = string(typedOpt)
  1109  		}
  1110  	}
  1111  	args = append(args, fmt.Sprintf("--%v", mode), target, "--")
  1112  	return g.run(args...)
  1113  }
  1114  
  1115  // SetRemoteUrl sets the url of the remote with given name to the given url.
  1116  func (g *Git) SetRemoteUrl(name, url string) error {
  1117  	return g.run("remote", "set-url", name, url)
  1118  }
  1119  
  1120  // DeleteRemote deletes the named remote
  1121  func (g *Git) DeleteRemote(name string) error {
  1122  	return g.run("remote", "rm", name)
  1123  }
  1124  
  1125  // Stash attempts to stash any unsaved changes. It returns true if
  1126  // anything was actually stashed, otherwise false. An error is
  1127  // returned if the stash command fails.
  1128  func (g *Git) Stash() (bool, error) {
  1129  	oldSize, err := g.StashSize()
  1130  	if err != nil {
  1131  		return false, err
  1132  	}
  1133  	if err := g.run("stash", "save"); err != nil {
  1134  		return false, err
  1135  	}
  1136  	newSize, err := g.StashSize()
  1137  	if err != nil {
  1138  		return false, err
  1139  	}
  1140  	return newSize > oldSize, nil
  1141  }
  1142  
  1143  // StashSize returns the size of the stash stack.
  1144  func (g *Git) StashSize() (int, error) {
  1145  	out, err := g.runOutput("stash", "list")
  1146  	if err != nil {
  1147  		return 0, err
  1148  	}
  1149  	// If output is empty, then stash is empty.
  1150  	if len(out) == 0 {
  1151  		return 0, nil
  1152  	}
  1153  	// Otherwise, stash size is the length of the output.
  1154  	return len(out), nil
  1155  }
  1156  
  1157  // StashPop pops the stash into the current working tree.
  1158  func (g *Git) StashPop() error {
  1159  	return g.run("stash", "pop")
  1160  }
  1161  
  1162  // TopLevel returns the top level path of the current repository.
  1163  func (g *Git) TopLevel() (string, error) {
  1164  	// TODO(sadovsky): If g.rootDir is set, perhaps simply return that?
  1165  	out, err := g.runOutput("rev-parse", "--show-toplevel")
  1166  	if err != nil {
  1167  		return "", err
  1168  	}
  1169  	return strings.Join(out, "\n"), nil
  1170  }
  1171  
  1172  // TrackedFiles returns the list of files that are tracked.
  1173  func (g *Git) TrackedFiles() ([]string, error) {
  1174  	out, err := g.runOutput("ls-files")
  1175  	if err != nil {
  1176  		return nil, err
  1177  	}
  1178  	return out, nil
  1179  }
  1180  
  1181  func (g *Git) Show(ref, file string) (string, error) {
  1182  	arg := ref
  1183  	arg = fmt.Sprintf("%s:%s", arg, file)
  1184  	out, err := g.runOutput("show", arg)
  1185  	if err != nil {
  1186  		return "", err
  1187  	}
  1188  	return strings.Join(out, "\n"), nil
  1189  }
  1190  
  1191  // UntrackedFiles returns the list of files that are not tracked.
  1192  func (g *Git) UntrackedFiles() ([]string, error) {
  1193  	out, err := g.runOutput("ls-files", "--others", "--directory", "--exclude-standard")
  1194  	if err != nil {
  1195  		return nil, err
  1196  	}
  1197  	return out, nil
  1198  }
  1199  
  1200  // Version returns the major and minor git version.
  1201  func (g *Git) Version() (int, int, error) {
  1202  	out, err := g.runOutput("version")
  1203  	if err != nil {
  1204  		return 0, 0, err
  1205  	}
  1206  	if got, want := len(out), 1; got != want {
  1207  		return 0, 0, fmt.Errorf("unexpected length of %v: got %v, want %v", out, got, want)
  1208  	}
  1209  	words := strings.Split(out[0], " ")
  1210  	if got, want := len(words), 3; got < want {
  1211  		return 0, 0, fmt.Errorf("unexpected length of %v: got %v, want at least %v", words, got, want)
  1212  	}
  1213  	version := strings.Split(words[2], ".")
  1214  	if got, want := len(version), 3; got < want {
  1215  		return 0, 0, fmt.Errorf("unexpected length of %v: got %v, want at least %v", version, got, want)
  1216  	}
  1217  	major, err := strconv.Atoi(version[0])
  1218  	if err != nil {
  1219  		return 0, 0, fmt.Errorf("failed parsing %q to integer", major)
  1220  	}
  1221  	minor, err := strconv.Atoi(version[1])
  1222  	if err != nil {
  1223  		return 0, 0, fmt.Errorf("failed parsing %q to integer", minor)
  1224  	}
  1225  	return major, minor, nil
  1226  }
  1227  
  1228  func (g *Git) run(args ...string) error {
  1229  	var stdout, stderr bytes.Buffer
  1230  	if err := g.runGit(&stdout, &stderr, args...); err != nil {
  1231  		return Error(stdout.String(), stderr.String(), err, g.rootDir, args...)
  1232  	}
  1233  	return nil
  1234  }
  1235  
  1236  func trimOutput(o string) []string {
  1237  	output := strings.TrimSpace(o)
  1238  	if len(output) == 0 {
  1239  		return nil
  1240  	}
  1241  	return strings.Split(output, "\n")
  1242  }
  1243  
  1244  func (g *Git) runOutput(args ...string) ([]string, error) {
  1245  	var stdout, stderr bytes.Buffer
  1246  	if err := g.runGit(&stdout, &stderr, args...); err != nil {
  1247  		return nil, Error(stdout.String(), stderr.String(), err, g.rootDir, args...)
  1248  	}
  1249  	return trimOutput(stdout.String()), nil
  1250  }
  1251  
  1252  func (g *Git) runInteractive(args ...string) error {
  1253  	var stderr bytes.Buffer
  1254  	// In order for the editing to work correctly with
  1255  	// terminal-based editors, notably "vim", use os.Stdout.
  1256  	if err := g.runGit(os.Stdout, &stderr, args...); err != nil {
  1257  		return Error("", stderr.String(), err, g.rootDir, args...)
  1258  	}
  1259  	return nil
  1260  }
  1261  
  1262  func (g *Git) runGit(stdout, stderr io.Writer, args ...string) error {
  1263  	if g.userName != "" {
  1264  		args = append([]string{"-c", fmt.Sprintf("user.name=%s", g.userName)}, args...)
  1265  	}
  1266  	if g.userEmail != "" {
  1267  		args = append([]string{"-c", fmt.Sprintf("user.email=%s", g.userEmail)}, args...)
  1268  	}
  1269  	var outbuf bytes.Buffer
  1270  	var errbuf bytes.Buffer
  1271  	command := exec.Command("git", args...)
  1272  	command.Dir = g.rootDir
  1273  	command.Stdin = os.Stdin
  1274  	command.Stdout = io.MultiWriter(stdout, &outbuf)
  1275  	command.Stderr = io.MultiWriter(stderr, &errbuf)
  1276  	env := g.jirix.Env()
  1277  	env = envvar.MergeMaps(g.opts, env)
  1278  	command.Env = envvar.MapToSlice(env)
  1279  	dir := g.rootDir
  1280  	if dir == "" {
  1281  		if cwd, err := os.Getwd(); err == nil {
  1282  			dir = cwd
  1283  		} else {
  1284  			// ignore error
  1285  		}
  1286  	}
  1287  	g.jirix.Logger.Tracef("Run: git %s (%s)", strings.Join(args, " "), dir)
  1288  	err := command.Run()
  1289  	exitCode := 0
  1290  	if err != nil {
  1291  		if exitError, ok := err.(*exec.ExitError); ok {
  1292  			exitCode = exitError.ExitCode()
  1293  		}
  1294  	}
  1295  	g.jirix.Logger.Tracef("Finished: git %s (%s), \nstdout: %s\nstderr: %s\nexit code: %v\n", strings.Join(args, " "), dir, outbuf.String(), errbuf.String(), exitCode)
  1296  	return err
  1297  }
  1298  
  1299  // Committer encapsulates the process of create a commit.
  1300  type Committer struct {
  1301  	commit            func() error
  1302  	commitWithMessage func(message string) error
  1303  }
  1304  
  1305  // Commit creates a commit.
  1306  func (c *Committer) Commit(message string) error {
  1307  	if len(message) == 0 {
  1308  		// No commit message supplied, let git supply one.
  1309  		return c.commit()
  1310  	}
  1311  	return c.commitWithMessage(message)
  1312  }
  1313  
  1314  // NewCommitter is the Committer factory. The boolean <edit> flag
  1315  // determines whether the commit commands should prompt users to edit
  1316  // the commit message. This flag enables automated testing.
  1317  func (g *Git) NewCommitter(edit bool) *Committer {
  1318  	if edit {
  1319  		return &Committer{
  1320  			commit:            g.CommitAndEdit,
  1321  			commitWithMessage: g.CommitWithMessageAndEdit,
  1322  		}
  1323  	} else {
  1324  		return &Committer{
  1325  			commit:            g.Commit,
  1326  			commitWithMessage: g.CommitWithMessage,
  1327  		}
  1328  	}
  1329  }