github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/review/git-review/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(adg): recognize non-master remote branches
     6  // TODO(adg): accept -a flag on 'commit' (like git commit -a)
     7  // TODO(adg): check style of commit message
     8  // TOOD(adg): print gerrit votes on 'pending'
     9  // TODO(adg): add gofmt commit hook
    10  // TODO(adg): print changed files on review sync
    11  // TODO(adg): translate email addresses without @ by looking up somewhere
    12  
    13  // Command git-review provides a simple command-line user interface for
    14  // working with git repositories and the Gerrit code review system.
    15  // See "git-review help" for details.
    16  package main // import "golang.org/x/review/git-review"
    17  
    18  import (
    19  	"bytes"
    20  	"flag"
    21  	"fmt"
    22  	"os"
    23  	"os/exec"
    24  	"strconv"
    25  	"strings"
    26  )
    27  
    28  var (
    29  	flags   *flag.FlagSet
    30  	verbose = new(count) // installed as -v below
    31  	noRun   = new(bool)
    32  )
    33  
    34  func initFlags() {
    35  	flags = flag.NewFlagSet("", flag.ExitOnError)
    36  	flags.Usage = func() {
    37  		fmt.Fprintf(os.Stderr, usage, os.Args[0], os.Args[0])
    38  	}
    39  	flags.Var(verbose, "v", "report git commands")
    40  	flags.BoolVar(noRun, "n", false, "print but do not run commands")
    41  }
    42  
    43  const globalFlags = "[-n] [-v]"
    44  
    45  const usage = `Usage: %s <command> ` + globalFlags + `
    46  Type "%s help" for more information.
    47  `
    48  
    49  const help = `Usage: %s <command> ` + globalFlags + `
    50  
    51  The review command is a wrapper for the git command that provides a simple
    52  interface to the "single-commit feature branch" development model.
    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  
    62  	gofmt
    63  		TBD
    64  
    65  	help
    66  		Show this help text.
    67  
    68  	hooks
    69  		Install Git commit hooks for Gerrit and gofmt.
    70  		Every other operation except help also does this, if they are not
    71  		already installed.
    72  
    73  	mail [-f] [-r reviewer,...] [-cc mail,...]
    74  		Upload change commit to the code review server and send mail
    75  		requesting a code review.
    76  		If -f is specified, upload even if there are staged changes.
    77  
    78  	mail -diff
    79  		Show the changes but do not send mail or upload.
    80  
    81  	pending [-r]
    82  		Show local branches and their head commits.
    83  		If -r is specified, show additional information from Gerrit.
    84  
    85  	sync
    86  		Fetch changes from the remote repository and merge them into
    87  		the current branch, rebasing the change commit on top of them.
    88  
    89  
    90  `
    91  
    92  func main() {
    93  	initFlags()
    94  
    95  	if len(os.Args) < 2 {
    96  		flags.Usage()
    97  		if dieTrap != nil {
    98  			dieTrap()
    99  		}
   100  		os.Exit(2)
   101  	}
   102  	command, args := os.Args[1], os.Args[2:]
   103  
   104  	if command == "help" {
   105  		fmt.Fprintf(os.Stdout, help, os.Args[0])
   106  		return
   107  	}
   108  
   109  	installHook()
   110  
   111  	switch command {
   112  	case "change", "c":
   113  		change(args)
   114  	case "gofmt":
   115  		dief("gofmt not implemented")
   116  	case "hooks":
   117  		// done - installHook already ran
   118  	case "mail", "m":
   119  		mail(args)
   120  	case "pending", "p":
   121  		pending(args)
   122  	case "sync", "s":
   123  		doSync(args)
   124  	default:
   125  		flags.Usage()
   126  	}
   127  }
   128  
   129  func expectZeroArgs(args []string, command string) {
   130  	flags.Parse(args)
   131  	if len(flags.Args()) > 0 {
   132  		fmt.Fprintf(os.Stderr, "Usage: %s %s %s\n", os.Args[0], command, globalFlags)
   133  		os.Exit(2)
   134  	}
   135  }
   136  
   137  func run(command string, args ...string) {
   138  	if err := runErr(command, args...); err != nil {
   139  		if *verbose == 0 {
   140  			// If we're not in verbose mode, print the command
   141  			// before dying to give context to the failure.
   142  			fmt.Fprintln(os.Stderr, commandString(command, args))
   143  		}
   144  		dief("%v", err)
   145  	}
   146  }
   147  
   148  var runLog []string
   149  
   150  func runErr(command string, args ...string) error {
   151  	if *verbose > 0 || *noRun {
   152  		fmt.Fprintln(os.Stderr, commandString(command, args))
   153  	}
   154  	if *noRun {
   155  		return nil
   156  	}
   157  	if runLog != nil {
   158  		runLog = append(runLog, strings.TrimSpace(command+" "+strings.Join(args, " ")))
   159  	}
   160  	cmd := exec.Command(command, args...)
   161  	cmd.Stdin = os.Stdin
   162  	cmd.Stdout = os.Stdout
   163  	cmd.Stderr = os.Stderr
   164  	return cmd.Run()
   165  }
   166  
   167  // getOutput runs the specified command and returns its combined standard
   168  // output and standard error outputs.
   169  // NOTE: It should only be used to run commands that return information,
   170  // **not** commands that make any actual changes.
   171  func getOutput(command string, args ...string) string {
   172  	// NOTE: We only show these non-state-modifying commands with -v -v.
   173  	// Otherwise things like 'git sync -v' show all our internal "find out about
   174  	// the git repo" commands, which is confusing if you are just trying to find
   175  	// out what git sync means.
   176  	if *verbose > 1 {
   177  		fmt.Fprintln(os.Stderr, commandString(command, args))
   178  	}
   179  	b, err := exec.Command(command, args...).CombinedOutput()
   180  	if err != nil {
   181  		fmt.Fprintf(os.Stderr, "%v\n%s\n", commandString(command, args), b)
   182  		dief("%v", err)
   183  	}
   184  	return string(bytes.TrimSpace(b))
   185  }
   186  
   187  // getLines is like getOutput but it returns non-empty output lines.
   188  // NOTE: It should only be used to run commands that return information,
   189  // **not** commands that make any actual changes.
   190  func getLines(command string, args ...string) []string {
   191  	var s []string
   192  	for _, l := range strings.Split(getOutput(command, args...), "\n") {
   193  		s = append(s, strings.TrimSpace(l))
   194  	}
   195  	return s
   196  }
   197  
   198  func commandString(command string, args []string) string {
   199  	return strings.Join(append([]string{command}, args...), " ")
   200  }
   201  
   202  var dieTrap func()
   203  
   204  func dief(format string, args ...interface{}) {
   205  	printf(format, args...)
   206  	if dieTrap != nil {
   207  		dieTrap()
   208  	}
   209  	os.Exit(1)
   210  }
   211  
   212  func verbosef(format string, args ...interface{}) {
   213  	if *verbose > 0 {
   214  		printf(format, args...)
   215  	}
   216  }
   217  
   218  func printf(format string, args ...interface{}) {
   219  	fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], fmt.Sprintf(format, args...))
   220  }
   221  
   222  // count is a flag.Value that is like a flag.Bool and a flag.Int.
   223  // If used as -name, it increments the count, but -name=x sets the count.
   224  // Used for verbose flag -v.
   225  type count int
   226  
   227  func (c *count) String() string {
   228  	return fmt.Sprint(int(*c))
   229  }
   230  
   231  func (c *count) Set(s string) error {
   232  	switch s {
   233  	case "true":
   234  		*c++
   235  	case "false":
   236  		*c = 0
   237  	default:
   238  		n, err := strconv.Atoi(s)
   239  		if err != nil {
   240  			return fmt.Errorf("invalid count %q", s)
   241  		}
   242  		*c = count(n)
   243  	}
   244  	return nil
   245  }
   246  
   247  func (c *count) IsBoolFlag() bool {
   248  	return true
   249  }