Chromium Code Reviews| Index: go/src/infra/tools/drover/main.go |
| diff --git a/go/src/infra/tools/drover/main.go b/go/src/infra/tools/drover/main.go |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..8e18f9647423a7d824b237ee8f728fed9a1fdd17 |
| --- /dev/null |
| +++ b/go/src/infra/tools/drover/main.go |
| @@ -0,0 +1,220 @@ |
| +// Copyright 2014 The Chromium 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 |
| + |
| +import ( |
| + "bufio" |
| + "flag" |
| + "fmt" |
| + "io/ioutil" |
| + "os" |
| + "strings" |
| + |
| + "github.com/daviddengcn/go-colortext" |
|
Vadim Sh.
2014/10/21 15:27:00
did you manage to add this to Goop file?
|
| + |
| + "infra/libs/git" |
| + "infra/libs/gitiles" |
| +) |
| + |
| +var commitish = flag.String("commit", "", "The commit to process (or commit position number).") |
| +var action = flag.String("action", "", "The action to take. Must specify `cherry-pick|revert`.") |
| +var ref = flag.String("ref", "", "The target ref to change.") |
| +var workdir string |
| + |
| +func getCodereviewSettings(g *gitiles.Gitiles, ref string) (ret map[string]string) { |
| + crSettings, err := g.Text("+", ref, "codereview.settings") |
| + if err == nil { |
| + lines := strings.Split(string(crSettings), "\n") |
| + ret = make(map[string]string, len(lines)) |
| + for _, line := range lines { |
| + if len(line) == 0 || line[0] == '#' { |
| + continue |
| + } |
| + keyVal := strings.Split(line, ":") |
| + if len(keyVal) == 2 { |
| + ret[keyVal[0]] = strings.TrimSpace(keyVal[1]) |
| + } |
| + } |
| + } |
| + return ret |
| +} |
| + |
| +func getLandingCommit(g *gitiles.Gitiles, ref string, settings map[string]string) (*git.Commit, string) { |
| + prefix, ok := settings["PENDING_REF_PREFIX"] |
| + if ok { |
| + ref = strings.Replace(ref, "refs/", prefix, 1) |
| + } |
| + result, err := g.GetObjectFromPath(ref) |
| + failIf(err) |
| + if result.Type() != git.CommitType { |
| + panic(fmt.Errorf("ref points at non-commit? %#v", result)) |
| + } |
| + return result.(*git.Commit), ref |
| +} |
| + |
| +func confirmAction(URL, action, ref string, commit *git.Commit, diff string) { |
| + verbPart := "to" |
| + if action == "revert" { |
| + verbPart = "from" |
| + } |
| + |
| + fmt.Printf("Planning to %s commit %s %s %s in %s:\n\n", |
| + action, commit.ID(), verbPart, ref, URL) |
| + |
| + ct.ChangeColor(ct.Yellow, false, ct.None, false) |
| + fmt.Println("commit", commit.ID()) |
| + ct.ResetColor() |
| + fmt.Printf("Author: %s <%s>\n", commit.Author().Name, commit.Author().Email) |
| + fmt.Printf("Date: %s\n", commit.Author().Time) |
| + fmt.Println() |
| + for _, l := range commit.MessageLines() { |
| + fmt.Printf(" %s\n", l) |
| + } |
| + fmt.Println() |
| + for _, pair := range commit.FooterPairs() { |
| + ct.ChangeColor(ct.White, true, ct.None, false) |
| + fmt.Printf(" %s: ", pair.Key) |
| + ct.ResetColor() |
| + fmt.Println(pair.Value) |
| + } |
| + fmt.Println() |
| + for _, line := range strings.Split(diff, "\n") { |
| + if len(line) > 0 { |
| + switch { |
| + case strings.HasPrefix(line, "+++") || strings.HasPrefix(line, "---"): |
| + ct.ChangeColor(ct.White, true, ct.None, false) |
| + case line[0] == '@': |
| + ct.ChangeColor(ct.Cyan, false, ct.None, false) |
| + bits := strings.SplitN(line, "@@", 3) |
| + fmt.Print("@@", bits[1], "@@") |
| + ct.ResetColor() |
| + fmt.Println(bits[2]) |
| + continue |
| + case line[0] == '+': |
| + ct.ChangeColor(ct.Green, false, ct.None, false) |
| + case line[0] == '-': |
| + ct.ChangeColor(ct.Red, false, ct.None, false) |
| + case line[0] == ' ': |
| + ct.ResetColor() |
| + default: |
| + ct.ChangeColor(ct.White, true, ct.None, false) |
| + } |
| + } |
| + fmt.Println(line) |
| + } |
| + ct.ResetColor() |
| + |
| + fmt.Println() |
| + fmt.Printf("Continue? [y/N] ") |
| + answerRaw, err := bufio.NewReader(os.Stdin).ReadBytes('\n') |
| + failIf(err) |
| + |
| + answer := strings.TrimSpace(string(answerRaw)) |
| + |
| + if len(answer) != 0 && strings.HasPrefix("yes", strings.ToLower(answer)) { |
| + return |
| + } |
| + |
| + fmt.Println("Action aborted") |
| + os.Exit(1) |
| +} |
| + |
| +func getCommitData(g *gitiles.Gitiles, gitSha string) (*git.Commit, git.TreeDiff) { |
| + commitRslt, err := g.GetObjectFromPath(gitSha) |
| + failIf(err) |
| + |
| + commit, ok := commitRslt.(*git.Commit) |
| + if !ok { |
| + failIf(fmt.Errorf("%s is not a commit?", gitSha)) |
| + } |
| + |
| + commitDiffRslt, err := g.GetCommitDiff(gitSha) |
| + failIf(err) |
| + |
| + return commit, commitDiffRslt |
| +} |
| + |
| +func main() { |
| + cwd, err := os.Getwd() |
| + if err != nil { |
| + panic(err) |
| + } |
| + flag.StringVar(&workdir, "workdir", cwd, "The work directory to use.") |
| + |
| + flag.Usage = func() { |
| + fmt.Println("Usage: git drover -commit deadbeef -action cherry-pick -ref 2125") |
| + flag.PrintDefaults() |
| + } |
| + flag.Parse() |
| + if *commitish == "" || *action == "" || *ref == "" { |
| + flag.PrintDefaults() |
| + fmt.Println("must specify commitish, action and ref") |
| + os.Exit(1) |
| + } |
| + if *action != "cherry-pick" && *action != "revert" { |
| + flag.PrintDefaults() |
| + fmt.Printf( |
| + "must action must be 'cherry-pick' or 'revert'. Got '%s'.\n", *action) |
| + os.Exit(1) |
| + } |
| + |
| + repo := &git.Repo{Path: workdir} |
| + if repo.RunOk("rev-parse", "--git-dir") { |
| + if !repo.RunOk("config", "drover.repo") { |
| + fmt.Println("must be run from an empty directory or a drover git repo") |
| + os.Exit(1) |
| + } |
| + } else { |
| + files, err := ioutil.ReadDir(workdir) |
| + failIf(err) |
| + if len(files) == 0 { |
| + fmt.Println("Initializing current directory as a drover git repo") |
| + if !repo.RunOk("init") { |
| + flag.PrintDefaults() |
| + panic("Couldn't initialize git repo!") |
| + } |
| + if !repo.RunOk("config", "drover.repo", "true") { |
| + flag.PrintDefaults() |
| + panic("Couldn't configure git repo to be owned by drover!") |
| + } |
| + } else { |
| + flag.PrintDefaults() |
| + fmt.Println("must be run from an empty directory or a drover git repo") |
| + os.Exit(1) |
| + } |
| + } |
| + |
| + gitSha, gitilesURL, err := disambiguateCommit(*commitish) |
| + failIf(err) |
| + |
| + g := gitiles.NewGitiles(gitilesURL, 8) |
| + |
| + *ref, err = disambiguateRef(g, gitSha, *ref) |
| + failIf(err) |
| + |
| + commit, treeDiff := getCommitData(g, gitSha) |
| + textDiff, err := g.GetTextCommitDiff(commit.ID()) |
| + failIf(err) |
| + |
| + settings := getCodereviewSettings(g, *ref) |
| + landCommit, real_ref := getLandingCommit(g, *ref, settings) |
| + |
| + confirmAction(g.URL(), *action, *ref, commit, textDiff) |
| + |
| + acquireObjects(repo, g, commit, landCommit, treeDiff) |
| + |
| + var left, right git.ObjectID |
| + if *action == "cherry-pick" { |
| + left, right = commit.Parents()[0], commit.ID() |
| + } else if *action == "revert" { |
| + right, left = commit.Parents()[0], commit.ID() |
| + } |
| + toPush := createCommit(*action, repo, left, right, landCommit.ID()) |
| + id := toPush.ID().String() |
| + |
| + diff, err := repo.GetTextDiff(id+"~", id) |
| + failIf(err) |
| + |
| + confirmAction(g.URL(), "push", real_ref, toPush, diff) |
| +} |