rewrite
Change-Id: I7984d6f29f5f0dc15fe63e7373bfe827a2c24990
diff --git a/review.go b/review.go
index d2045e5..ee73482 100644
--- a/review.go
+++ b/review.go
@@ -1,50 +1,170 @@
-/*
-Copyright 2014 The Camlistore Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
+// 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.
package main
//go:generate go run bake.go commit-msg.githook
import (
- "bufio"
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
+ "strings"
)
-// TODO(adg): detect repo path from context
-const repo = "https://go.googlesource.com/go/"
-
var hookFile = filepath.FromSlash(".git/hooks/commit-msg")
+const usage = `Usage: %s [command]
+
+The review command is a wrapper for the git command that provides a simple
+interface to the "single-commit feature branch" development model.
+
+Available comands:
+
+ create <name>
+
+ Create a local feature branch with the provided name
+ and commit the staged changes to it.
+
+ commit
+
+ Amend feature branch HEAD with the staged changes.
+
+ diff
+
+ View differences between remote branch HEAD and
+ the feature branch HEAD.
+ (The differences introduced by this change.)
+
+ upload
+
+ Upload HEAD commit to the code review server.
+
+ sync
+
+ Fetch changes from the remote repository and merge them to the
+ current branch, rebasing the HEAD commit (if any) on top of
+ them.
+
+ pending
+
+ Show local feature branches and their head commits.
+
+`
+
func main() {
flag.Usage = func() {
- fmt.Fprintf(os.Stderr, "Usage: go-review\n")
+ fmt.Fprintf(os.Stderr, usage, os.Args[0])
os.Exit(2)
}
flag.Parse()
- if len(flag.Args()) > 0 {
+
+ goToRepoRoot()
+ installHook()
+
+ switch flag.Arg(0) {
+ case "create":
+ name := flag.Arg(1)
+ if name == "" {
+ flag.Usage()
+ }
+ create(name)
+ case "commit":
+ commit()
+ case "diff":
+ diff()
+ case "upload":
+ upload()
+ case "sync":
+ sync()
+ case "pending":
+ pending()
+ default:
flag.Usage()
}
- goToRepoRoot()
- checkHook()
- gitPush()
+}
+
+func create(name string) {
+ if !hasStagedChanges() {
+ dief(`No staged changes. Did you forget to "git add" your files?\n`)
+ }
+ if !isOnMaster() {
+ dief("You must run create from the master branch.\n")
+ }
+ fmt.Printf("Creating and checking out branch: %v\n", name)
+ run("git", "checkout", "-b", name)
+ fmt.Printf("Committing staged changes to branch.\n")
+ run("git", "commit")
+}
+
+func commit() {
+ if !hasStagedChanges() {
+ dief(`No staged changes. Did you forget to "git add" your files?\n`)
+ }
+ if isOnMaster() {
+ dief("Can't commit to master branch.\n")
+ }
+ fmt.Printf("Amending head commit with staged changes.\n")
+ run("git", "commit", "--amend", "-C", "HEAD")
+}
+
+func diff() {
+ run("git", "diff", "HEAD^", "HEAD")
+}
+
+func upload() {
+ if isOnMaster() {
+ dief("Can't upload from master branch.\n")
+ }
+ fmt.Printf("Pushing commit to Gerrit code review server.\n")
+ run("git", "push", "origin", "HEAD:refs/for/master")
+}
+
+func sync() {
+ fmt.Printf("Fetching changes from remote repo.\n")
+ run("git", "fetch")
+ if isOnMaster() {
+ run("git", "pull", "--ff-only")
+ return
+ }
+ fmt.Printf("Rebasing head commit atop origin/master.\n")
+ run("git", "rebase", "origin/master")
+}
+
+func pending() {
+ dief("not implemented\n")
+}
+
+func hasStagedChanges() bool {
+ status, err := exec.Command("git", "status", "-s").CombinedOutput()
+ if err != nil {
+ dief("%s\nchecking for staged changes: %v\n", status, err)
+ }
+ for _, s := range strings.Split(string(status), "\n") {
+ if strings.HasPrefix(s, "A ") ||
+ strings.HasPrefix(s, "M ") ||
+ strings.HasPrefix(s, "D ") {
+ return true
+ }
+ }
+ return false
+}
+
+func isOnMaster() bool {
+ branch, err := exec.Command("git", "branch").CombinedOutput()
+ if err != nil {
+ dief("%s\nchecking current branch: %v\n", branch, err)
+ }
+ for _, s := range strings.Split(string(branch), "\n") {
+ if strings.HasPrefix(s, "* ") {
+ return s == "* master"
+ }
+ }
+ return false
}
func goToRepoRoot() {
@@ -70,7 +190,7 @@
}
}
-func checkHook() {
+func installHook() {
_, err := os.Stat(hookFile)
if err == nil {
return
@@ -83,31 +203,18 @@
if err := ioutil.WriteFile(hookFile, hookContent, 0700); err != nil {
dief("writing hook file: %v\n", err)
}
- fmt.Printf("Amending last commit to add Change-Id.\nPlease re-save description without making changes.\n\n")
- fmt.Printf("Press Enter to continue.\n")
- if _, _, err := bufio.NewReader(os.Stdin).ReadLine(); err != nil {
- dief("waiting for user input: %v\n", err)
- }
-
- cmd := exec.Command("git", []string{"commit", "--amend"}...)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- if err := cmd.Run(); err != nil {
- dief("amending commit: %v\n", err)
- }
-}
-
-func gitPush() {
- cmd := exec.Command("git",
- []string{"push", repo, "HEAD:refs/for/master"}...)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- if err := cmd.Run(); err != nil {
- dief("Could not git push: %v\n", err)
- }
}
func dief(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format, args...)
os.Exit(1)
}
+
+func run(command string, args ...string) {
+ cmd := exec.Command(command, args...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ dief("%v\n", err)
+ }
+}