github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/review/git-codereview/review.go (about)

     1  // Copyright 2014 The Go 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  // TODO(rsc): Document multi-change branch behavior.
     6  
     7  // Command git-codereview provides a simple command-line user interface for
     8  // working with git repositories and the Gerrit code review system.
     9  // See "git-codereview help" for details.
    10  package main // import "golang.org/x/review/git-codereview"
    11  
    12  import (
    13  	"bytes"
    14  	"flag"
    15  	"fmt"
    16  	"io"
    17  	"os"
    18  	"os/exec"
    19  	"strconv"
    20  	"strings"
    21  )
    22  
    23  var (
    24  	flags   *flag.FlagSet
    25  	verbose = new(count) // installed as -v below
    26  	noRun   = new(bool)
    27  )
    28  
    29  func initFlags() {
    30  	flags = flag.NewFlagSet("", flag.ExitOnError)
    31  	flags.Usage = func() {
    32  		fmt.Fprintf(stderr(), usage, os.Args[0], os.Args[0])
    33  	}
    34  	flags.Var(verbose, "v", "report commands")
    35  	flags.BoolVar(noRun, "n", false, "print but do not run commands")
    36  }
    37  
    38  const globalFlags = "[-n] [-v]"
    39  
    40  const usage = `Usage: %s <command> ` + globalFlags + `
    41  Type "%s help" for more information.
    42  `
    43  
    44  const help = `Usage: %s <command> ` + globalFlags + `
    45  
    46  The git-codereview command is a wrapper for the git command that provides a
    47  simple interface to the "single-commit feature branch" development model.
    48  
    49  See the docs for details: https://godoc.org/golang.org/x/review/git-codereview
    50  
    51  The -v flag prints all commands that make changes.
    52  The -n flag prints all commands that would be run, but does not run them.
    53  
    54  Available commands:
    55  
    56  	change [name]
    57  		Create a change commit, or amend an existing change commit,
    58  		with the staged changes. If a branch name is provided, check
    59  		out that branch (creating it if it does not exist).
    60  		Does not amend the existing commit when switching branches.
    61  		If -q is specified, skip the editing of an extant pending
    62  		change's commit message.
    63  		If -a is specified, automatically add any unstaged changes in
    64  		tracked files during commit.
    65  
    66  	gofmt [-l]
    67  		Run gofmt on all tracked files in the staging area and the
    68  		working tree.
    69  		If -l is specified, list files that need formatting.
    70  		Otherwise, reformat files in place.
    71  
    72  	help
    73  		Show this help text.
    74  
    75  	hooks
    76  		Install Git commit hooks for Gerrit and gofmt.
    77  		Every other operation except help also does this,
    78  		if they are not already installed.
    79  
    80  	mail [-f] [-r reviewer,...] [-cc mail,...]
    81  		Upload change commit to the code review server and send mail
    82  		requesting a code review.
    83  		If -f is specified, upload even if there are staged changes.
    84  		The -r and -cc flags identify the email addresses of people to
    85  		do the code review and to be CC'ed about the code review.
    86  		Multiple addresses are given as a comma-separated list.
    87  
    88  	mail -diff
    89  		Show the changes but do not send mail or upload.
    90  
    91  	pending [-c] [-l] [-s]
    92  		Show the status of all pending changes and staged, unstaged,
    93  		and untracked files in the local repository.
    94  		If -c is specified, show only changes on the current branch.
    95  		If -l is specified, only use locally available information.
    96  		If -s is specified, show short output.
    97  
    98  	submit [-i | commit-hash...]
    99  		Push the pending change to the Gerrit server and tell Gerrit to
   100  		submit it to the master branch.
   101  
   102  	sync
   103  		Fetch changes from the remote repository and merge them into
   104  		the current branch, rebasing the change commit on top of them.
   105  
   106  
   107  `
   108  
   109  func main() {
   110  	initFlags()
   111  
   112  	if len(os.Args) < 2 {
   113  		flags.Usage()
   114  		if dieTrap != nil {
   115  			dieTrap()
   116  		}
   117  		os.Exit(2)
   118  	}
   119  	command, args := os.Args[1], os.Args[2:]
   120  
   121  	if command == "help" {
   122  		fmt.Fprintf(stdout(), help, os.Args[0])
   123  		return
   124  	}
   125  
   126  	installHook()
   127  
   128  	switch command {
   129  	case "branchpoint":
   130  		cmdBranchpoint(args)
   131  	case "change":
   132  		cmdChange(args)
   133  	case "gofmt":
   134  		cmdGofmt(args)
   135  	case "hook-invoke":
   136  		cmdHookInvoke(args)
   137  	case "hooks":
   138  		// done - installHook already ran
   139  	case "mail", "m":
   140  		cmdMail(args)
   141  	case "pending":
   142  		cmdPending(args)
   143  	case "rebase-work":
   144  		cmdRebaseWork(args)
   145  	case "submit":
   146  		cmdSubmit(args)
   147  	case "sync":
   148  		cmdSync(args)
   149  	case "test-loadAuth": // for testing only
   150  		loadAuth()
   151  	default:
   152  		flags.Usage()
   153  	}
   154  }
   155  
   156  func expectZeroArgs(args []string, command string) {
   157  	flags.Parse(args)
   158  	if len(flags.Args()) > 0 {
   159  		fmt.Fprintf(stderr(), "Usage: %s %s %s\n", os.Args[0], command, globalFlags)
   160  		os.Exit(2)
   161  	}
   162  }
   163  
   164  func run(command string, args ...string) {
   165  	if err := runErr(command, args...); err != nil {
   166  		if *verbose == 0 {
   167  			// If we're not in verbose mode, print the command
   168  			// before dying to give context to the failure.
   169  			fmt.Fprintf(stderr(), "(running: %s)\n", commandString(command, args))
   170  		}
   171  		dief("%v", err)
   172  	}
   173  }
   174  
   175  func runErr(command string, args ...string) error {
   176  	return runDirErr("", command, args...)
   177  }
   178  
   179  var runLogTrap []string
   180  
   181  func runDirErr(dir, command string, args ...string) error {
   182  	if *verbose > 0 || *noRun {
   183  		fmt.Fprintln(stderr(), commandString(command, args))
   184  	}
   185  	if *noRun {
   186  		return nil
   187  	}
   188  	if runLogTrap != nil {
   189  		runLogTrap = append(runLogTrap, strings.TrimSpace(command+" "+strings.Join(args, " ")))
   190  	}
   191  	cmd := exec.Command(command, args...)
   192  	cmd.Stdin = os.Stdin
   193  	cmd.Stdout = stdout()
   194  	cmd.Stderr = stderr()
   195  	return cmd.Run()
   196  }
   197  
   198  // cmdOutput runs the command line, returning its output.
   199  // If the command cannot be run or does not exit successfully,
   200  // cmdOutput dies.
   201  //
   202  // NOTE: cmdOutput must be used only to run commands that read state,
   203  // not for commands that make changes. Commands that make changes
   204  // should be run using runDirErr so that the -v and -n flags apply to them.
   205  func cmdOutput(command string, args ...string) string {
   206  	return cmdOutputDir(".", command, args...)
   207  }
   208  
   209  // cmdOutputDir runs the command line in dir, returning its output.
   210  // If the command cannot be run or does not exit successfully,
   211  // cmdOutput dies.
   212  //
   213  // NOTE: cmdOutput must be used only to run commands that read state,
   214  // not for commands that make changes. Commands that make changes
   215  // should be run using runDirErr so that the -v and -n flags apply to them.
   216  func cmdOutputDir(dir, command string, args ...string) string {
   217  	s, err := cmdOutputDirErr(dir, command, args...)
   218  	if err != nil {
   219  		fmt.Fprintf(stderr(), "%v\n%s\n", commandString(command, args), s)
   220  		dief("%v", err)
   221  	}
   222  	return s
   223  }
   224  
   225  // cmdOutputErr runs the command line in dir, returning its output
   226  // and any error results.
   227  //
   228  // NOTE: cmdOutputErr must be used only to run commands that read state,
   229  // not for commands that make changes. Commands that make changes
   230  // should be run using runDirErr so that the -v and -n flags apply to them.
   231  func cmdOutputErr(command string, args ...string) (string, error) {
   232  	return cmdOutputDirErr(".", command, args...)
   233  }
   234  
   235  // cmdOutputDirErr runs the command line in dir, returning its output
   236  // and any error results.
   237  //
   238  // NOTE: cmdOutputDirErr must be used only to run commands that read state,
   239  // not for commands that make changes. Commands that make changes
   240  // should be run using runDirErr so that the -v and -n flags apply to them.
   241  func cmdOutputDirErr(dir, command string, args ...string) (string, error) {
   242  	// NOTE: We only show these non-state-modifying commands with -v -v.
   243  	// Otherwise things like 'git sync -v' show all our internal "find out about
   244  	// the git repo" commands, which is confusing if you are just trying to find
   245  	// out what git sync means.
   246  	if *verbose > 1 {
   247  		fmt.Fprintln(stderr(), commandString(command, args))
   248  	}
   249  	cmd := exec.Command(command, args...)
   250  	if dir != "." {
   251  		cmd.Dir = dir
   252  	}
   253  	b, err := cmd.CombinedOutput()
   254  	return string(b), err
   255  }
   256  
   257  // trim is shorthand for strings.TrimSpace.
   258  func trim(text string) string {
   259  	return strings.TrimSpace(text)
   260  }
   261  
   262  // trimErr applies strings.TrimSpace to the result of cmdOutput(Dir)Err,
   263  // passing the error along unmodified.
   264  func trimErr(text string, err error) (string, error) {
   265  	return strings.TrimSpace(text), err
   266  }
   267  
   268  // lines returns the lines in text.
   269  func lines(text string) []string {
   270  	out := strings.Split(text, "\n")
   271  	// Split will include a "" after the last line. Remove it.
   272  	if n := len(out) - 1; n >= 0 && out[n] == "" {
   273  		out = out[:n]
   274  	}
   275  	return out
   276  }
   277  
   278  // nonBlankLines returns the non-blank lines in text.
   279  func nonBlankLines(text string) []string {
   280  	var out []string
   281  	for _, s := range lines(text) {
   282  		if strings.TrimSpace(s) != "" {
   283  			out = append(out, s)
   284  		}
   285  	}
   286  	return out
   287  }
   288  
   289  func commandString(command string, args []string) string {
   290  	return strings.Join(append([]string{command}, args...), " ")
   291  }
   292  
   293  var dieTrap func()
   294  
   295  func dief(format string, args ...interface{}) {
   296  	printf(format, args...)
   297  	die()
   298  }
   299  
   300  func die() {
   301  	if dieTrap != nil {
   302  		dieTrap()
   303  	}
   304  	os.Exit(1)
   305  }
   306  
   307  func verbosef(format string, args ...interface{}) {
   308  	if *verbose > 0 {
   309  		printf(format, args...)
   310  	}
   311  }
   312  
   313  var stdoutTrap, stderrTrap *bytes.Buffer
   314  
   315  func stdout() io.Writer {
   316  	if stdoutTrap != nil {
   317  		return stdoutTrap
   318  	}
   319  	return os.Stdout
   320  }
   321  
   322  func stderr() io.Writer {
   323  	if stderrTrap != nil {
   324  		return stderrTrap
   325  	}
   326  	return os.Stderr
   327  }
   328  
   329  func printf(format string, args ...interface{}) {
   330  	fmt.Fprintf(stderr(), "%s: %s\n", os.Args[0], fmt.Sprintf(format, args...))
   331  }
   332  
   333  // count is a flag.Value that is like a flag.Bool and a flag.Int.
   334  // If used as -name, it increments the count, but -name=x sets the count.
   335  // Used for verbose flag -v.
   336  type count int
   337  
   338  func (c *count) String() string {
   339  	return fmt.Sprint(int(*c))
   340  }
   341  
   342  func (c *count) Set(s string) error {
   343  	switch s {
   344  	case "true":
   345  		*c++
   346  	case "false":
   347  		*c = 0
   348  	default:
   349  		n, err := strconv.Atoi(s)
   350  		if err != nil {
   351  			return fmt.Errorf("invalid count %q", s)
   352  		}
   353  		*c = count(n)
   354  	}
   355  	return nil
   356  }
   357  
   358  func (c *count) IsBoolFlag() bool {
   359  	return true
   360  }