diff options
| author | Andrew Gerrand <adg@golang.org> | 2014-12-18 11:25:48 +1100 |
|---|---|---|
| committer | Andrew Gerrand <adg@golang.org> | 2014-12-18 00:35:19 +0000 |
| commit | f473ce13dd1bba7ce531e7800fdf018f60aa2454 (patch) | |
| tree | fdc20fb8b555be78ca5a4f251e04de5e77d338ea /git-codereview/review.go | |
| parent | 6a0c83f0c935e49b841a3a880579cb07918bcb57 (diff) | |
| download | go-x-review-f473ce13dd1bba7ce531e7800fdf018f60aa2454.tar.xz | |
git-codereview: rename from 'git-review' to 'git-codereview'
Mostly trivial search and replace, except for hooks.go which
includes a special case to remove the old git-review hooks.
Change-Id: Ic0792bb3e26607e5e0ead88958e46c3ac08288cd
Reviewed-on: https://go-review.googlesource.com/1741
Reviewed-by: Russ Cox <rsc@golang.org>
Diffstat (limited to 'git-codereview/review.go')
| -rw-r--r-- | git-codereview/review.go | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/git-codereview/review.go b/git-codereview/review.go new file mode 100644 index 0000000..0b0ee8e --- /dev/null +++ b/git-codereview/review.go @@ -0,0 +1,287 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// TODO(adg): translate email addresses without @ by looking up somewhere + +// Command git-codereview provides a simple command-line user interface for +// working with git repositories and the Gerrit code review system. +// See "git-codereview help" for details. +package main // import "golang.org/x/review/git-codereview" + +import ( + "bytes" + "flag" + "fmt" + "io" + "os" + "os/exec" + "strconv" + "strings" +) + +var ( + flags *flag.FlagSet + verbose = new(count) // installed as -v below + noRun = new(bool) +) + +func initFlags() { + flags = flag.NewFlagSet("", flag.ExitOnError) + flags.Usage = func() { + fmt.Fprintf(os.Stderr, usage, os.Args[0], os.Args[0]) + } + flags.Var(verbose, "v", "report git commands") + flags.BoolVar(noRun, "n", false, "print but do not run commands") +} + +const globalFlags = "[-n] [-v]" + +const usage = `Usage: %s <command> ` + globalFlags + ` +Type "%s help" for more information. +` + +const help = `Usage: %s <command> ` + globalFlags + ` + +The git-codereview command is a wrapper for the git command that provides a +simple interface to the "single-commit feature branch" development model. + +See the docs for details: https://godoc.org/golang.org/x/review/git-codereview + +The -v flag prints all Git commands that make changes. +The -n flag prints all commands that would be run, but does not run them. + +Available commands: + + change [name] + Create a change commit, or amend an existing change commit, + with the staged changes. If a branch name is provided, check + out that branch (creating it if it does not exist). + Does not amend the existing commit when switching branches. + If -q is specified, skip the editing of an extant pending + change's commit message. + If -a is specified, automatically add any unstaged changes in + tracked files during commit. + + gofmt [-l] + Run gofmt on all tracked files in the staging area and the + working tree. + If -l is specified, list files that need formatting. + Otherwise, reformat files in place. + + help + Show this help text. + + hooks + Install Git commit hooks for Gerrit and gofmt. + Every other operation except help also does this, + if they are not already installed. + + mail [-f] [-r reviewer,...] [-cc mail,...] + Upload change commit to the code review server and send mail + requesting a code review. + If -f is specified, upload even if there are staged changes. + The -r and -cc flags identify the email addresses of people to + do the code review and to be CC'ed about the code review. + Multiple addresses are given as a comma-separated list. + + mail -diff + Show the changes but do not send mail or upload. + + pending [-l] + Show the status of all pending changes and staged, unstaged, + and untracked files in the local repository. + If -l is specified, only use locally available information. + + submit + Push the pending change to the Gerrit server and tell Gerrit to + submit it to the master branch. + + sync + Fetch changes from the remote repository and merge them into + the current branch, rebasing the change commit on top of them. + + +` + +func main() { + initFlags() + + if len(os.Args) < 2 { + flags.Usage() + if dieTrap != nil { + dieTrap() + } + os.Exit(2) + } + command, args := os.Args[1], os.Args[2:] + + if command == "help" { + fmt.Fprintf(os.Stdout, help, os.Args[0]) + return + } + + installHook() + + switch command { + case "change": + change(args) + case "gofmt": + gofmt(args) + case "hook-invoke": + hookInvoke(args) + case "hooks": + // done - installHook already ran + case "mail", "m": + mail(args) + case "pending": + pending(args) + case "submit": + submit(args) + case "sync": + doSync(args) + default: + flags.Usage() + } +} + +func expectZeroArgs(args []string, command string) { + flags.Parse(args) + if len(flags.Args()) > 0 { + fmt.Fprintf(os.Stderr, "Usage: %s %s %s\n", os.Args[0], command, globalFlags) + os.Exit(2) + } +} + +func run(command string, args ...string) { + if err := runErr(command, args...); err != nil { + if *verbose == 0 { + // If we're not in verbose mode, print the command + // before dying to give context to the failure. + fmt.Fprintln(os.Stderr, commandString(command, args)) + } + dief("%v", err) + } +} + +func runErr(command string, args ...string) error { + return runDirErr("", command, args...) +} + +var runLogTrap []string + +func runDirErr(dir, command string, args ...string) error { + if *verbose > 0 || *noRun { + fmt.Fprintln(os.Stderr, commandString(command, args)) + } + if *noRun { + return nil + } + if runLogTrap != nil { + runLogTrap = append(runLogTrap, strings.TrimSpace(command+" "+strings.Join(args, " "))) + } + cmd := exec.Command(command, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + if stdoutTrap != nil { + cmd.Stdout = stdoutTrap + } + cmd.Stderr = os.Stderr + if stderrTrap != nil { + cmd.Stderr = stderrTrap + } + return cmd.Run() +} + +// getOutput runs the specified command and returns its combined standard +// output and standard error outputs. +// NOTE: It should only be used to run commands that return information, +// **not** commands that make any actual changes. +func getOutput(command string, args ...string) string { + // NOTE: We only show these non-state-modifying commands with -v -v. + // Otherwise things like 'git sync -v' show all our internal "find out about + // the git repo" commands, which is confusing if you are just trying to find + // out what git sync means. + if *verbose > 1 { + fmt.Fprintln(os.Stderr, commandString(command, args)) + } + b, err := exec.Command(command, args...).CombinedOutput() + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n%s\n", commandString(command, args), b) + dief("%v", err) + } + return string(bytes.TrimSpace(b)) +} + +// getLines is like getOutput but it returns only non-empty output lines, +// with leading and trailing spaces removed. +// NOTE: It should only be used to run commands that return information, +// **not** commands that make any actual changes. +func getLines(command string, args ...string) []string { + var s []string + for _, l := range strings.Split(getOutput(command, args...), "\n") { + if len(strings.TrimSpace(l)) > 0 { + s = append(s, l) + } + } + return s +} + +func commandString(command string, args []string) string { + return strings.Join(append([]string{command}, args...), " ") +} + +var dieTrap func() + +func dief(format string, args ...interface{}) { + printf(format, args...) + if dieTrap != nil { + dieTrap() + } + os.Exit(1) +} + +func verbosef(format string, args ...interface{}) { + if *verbose > 0 { + printf(format, args...) + } +} + +var stdoutTrap, stderrTrap *bytes.Buffer + +func printf(format string, args ...interface{}) { + w := io.Writer(os.Stderr) + if stderrTrap != nil { + w = stderrTrap + } + fmt.Fprintf(w, "%s: %s\n", os.Args[0], fmt.Sprintf(format, args...)) +} + +// count is a flag.Value that is like a flag.Bool and a flag.Int. +// If used as -name, it increments the count, but -name=x sets the count. +// Used for verbose flag -v. +type count int + +func (c *count) String() string { + return fmt.Sprint(int(*c)) +} + +func (c *count) Set(s string) error { + switch s { + case "true": + *c++ + case "false": + *c = 0 + default: + n, err := strconv.Atoi(s) + if err != nil { + return fmt.Errorf("invalid count %q", s) + } + *c = count(n) + } + return nil +} + +func (c *count) IsBoolFlag() bool { + return true +} |
