Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 package main | |
| 2 | |
| 3 import "bufio" | |
| 4 import "flag" | |
| 5 import "fmt" | |
| 6 import "io/ioutil" | |
| 7 import "os" | |
| 8 import "strings" | |
| 9 | |
| 10 import "github.com/daviddengcn/go-colortext" | |
| 11 | |
| 12 import "infra/libs/git" | |
| 13 import "infra/libs/gitiles" | |
| 14 | |
| 15 var commitish = flag.String("commit", "", "The commit to process (or commit posi tion number).") | |
| 16 var action = flag.String("action", "", "The action to take. Must specify `cherry -pick|revert`.") | |
|
M-A Ruel
2014/10/18 00:47:06
Why not two boolean flags instead?
iannucci
2014/10/20 21:11:58
They're mutually exclusive.
| |
| 17 var ref = flag.String("ref", "", "The target ref to change.") | |
| 18 var workdir string | |
| 19 | |
| 20 func init() { | |
|
M-A Ruel
2014/10/18 00:47:06
No need for init in package main, just put it at t
iannucci
2014/10/20 21:11:58
Done.
| |
| 21 cwd, err := os.Getwd() | |
| 22 if err != nil { | |
| 23 panic(err) | |
| 24 } | |
| 25 flag.StringVar(&workdir, "workdir", cwd, "The work directory to use.") | |
| 26 } | |
| 27 | |
| 28 func getCodereviewSettings(g *gitiles.Gitiles, ref string) (ret map[string]strin g) { | |
| 29 crSettings := <-g.Text("+", ref, "codereview.settings") | |
| 30 if crSettings.Err == nil { | |
| 31 lines := strings.Split(string(crSettings.Data), "\n") | |
| 32 ret = make(map[string]string, len(lines)) | |
| 33 for _, line := range lines { | |
| 34 if len(line) == 0 || line[0] == '#' { | |
| 35 continue | |
| 36 } | |
| 37 keyVal := strings.Split(line, ":") | |
| 38 if len(keyVal) == 2 { | |
| 39 ret[keyVal[0]] = strings.TrimSpace(keyVal[1]) | |
| 40 } | |
| 41 } | |
| 42 } | |
| 43 return ret | |
| 44 } | |
| 45 | |
| 46 func getLandingCommit(g *gitiles.Gitiles, ref string, settings map[string]string ) (*git.Commit, string) { | |
| 47 prefix, ok := settings["PENDING_REF_PREFIX"] | |
| 48 if ok { | |
| 49 ref = strings.Replace(ref, "refs/", prefix, 1) | |
| 50 } | |
| 51 result := <-g.GetObjectFromPath(ref) | |
| 52 failIf(result.Err) | |
| 53 if result.Object.Type() != "commit" { | |
| 54 panic(fmt.Errorf("ref points at non-commit? %#v", result.Object) ) | |
| 55 } | |
| 56 return result.Object.(*git.Commit), ref | |
| 57 } | |
| 58 | |
| 59 func confirmAction(URL, action, ref string, commit *git.Commit, diff []string) { | |
| 60 verbPart := "to" | |
| 61 if action == "revert" { | |
| 62 verbPart = "from" | |
| 63 } | |
| 64 | |
| 65 fmt.Printf("Planning to %s commit %s %s %s in %s:\n\n", | |
| 66 action, commit.ID(), verbPart, ref, URL) | |
| 67 | |
| 68 ct.ChangeColor(ct.Yellow, false, ct.None, false) | |
| 69 fmt.Println("commit", commit.ID()) | |
| 70 ct.ResetColor() | |
| 71 fmt.Printf("Author: %s <%s>\n", commit.Author().Name, commit.Author().Em ail) | |
| 72 fmt.Printf("Date: %s\n", commit.Author().Time) | |
| 73 fmt.Println() | |
| 74 for _, l := range commit.MessageLines() { | |
| 75 fmt.Printf(" %s\n", l) | |
| 76 } | |
| 77 fmt.Println() | |
| 78 for _, pair := range commit.FooterPairs() { | |
| 79 ct.ChangeColor(ct.White, true, ct.None, false) | |
| 80 fmt.Printf(" %s: ", pair.Key) | |
| 81 ct.ResetColor() | |
| 82 fmt.Println(pair.Value) | |
| 83 } | |
| 84 fmt.Println() | |
| 85 for _, line := range diff { | |
| 86 if len(line) > 0 { | |
| 87 switch { | |
| 88 case strings.HasPrefix(line, "+++") || strings.HasPrefix (line, "---"): | |
| 89 ct.ChangeColor(ct.White, true, ct.None, false) | |
| 90 case line[0] == '@': | |
| 91 ct.ChangeColor(ct.Cyan, false, ct.None, false) | |
| 92 bits := strings.SplitN(line, "@@", 3) | |
| 93 fmt.Print("@@", bits[1], "@@") | |
| 94 ct.ResetColor() | |
| 95 fmt.Println(bits[2]) | |
| 96 continue | |
| 97 case line[0] == '+': | |
| 98 ct.ChangeColor(ct.Green, false, ct.None, false) | |
| 99 case line[0] == '-': | |
| 100 ct.ChangeColor(ct.Red, false, ct.None, false) | |
| 101 case line[0] == ' ': | |
| 102 ct.ResetColor() | |
| 103 default: | |
| 104 ct.ChangeColor(ct.White, true, ct.None, false) | |
| 105 } | |
| 106 } | |
| 107 fmt.Println(line) | |
| 108 } | |
| 109 ct.ResetColor() | |
| 110 | |
| 111 fmt.Println() | |
| 112 fmt.Printf("Continue? [y/N] ") | |
| 113 answerRaw, err := bufio.NewReader(os.Stdin).ReadBytes('\n') | |
| 114 failIf(err) | |
| 115 | |
| 116 answer := strings.TrimSpace(string(answerRaw)) | |
| 117 | |
| 118 if len(answer) != 0 && strings.HasPrefix("yes", strings.ToLower(answer)) { | |
| 119 return | |
| 120 } | |
| 121 | |
| 122 fmt.Println("Action aborted") | |
| 123 os.Exit(1) | |
| 124 } | |
| 125 | |
| 126 func getCommitData(g *gitiles.Gitiles, gitSha string) (*git.Commit, git.TreeDiff ) { | |
| 127 chCommit := g.GetObjectFromPath(gitSha) | |
| 128 chCommitDiff := g.GetCommitDiff(gitSha) | |
| 129 | |
| 130 commitRslt := <-chCommit | |
| 131 failIf(commitRslt.Err) | |
| 132 | |
| 133 commit, ok := commitRslt.Object.(*git.Commit) | |
| 134 if !ok { | |
| 135 failIf(fmt.Errorf("%s is not a commit?", gitSha)) | |
| 136 } | |
| 137 | |
| 138 commitDiffRslt := chCommitDiff | |
| 139 failIf(commitRslt.Err) | |
| 140 | |
| 141 return commit, commitDiffRslt.Diff | |
| 142 } | |
| 143 | |
| 144 func main() { | |
| 145 flag.Usage = func() { | |
| 146 fmt.Println("Usage: git drover -commit deadbeef -action cherry-p ick -ref 2125") | |
| 147 flag.PrintDefaults() | |
| 148 } | |
| 149 flag.Parse() | |
| 150 if *commitish == "" || *action == "" || *ref == "" { | |
| 151 flag.PrintDefaults() | |
| 152 fmt.Println("must specify commitish, action and ref") | |
| 153 os.Exit(1) | |
|
M-A Ruel
2014/10/18 00:47:06
I generally prefer to have something that looks li
| |
| 154 } | |
| 155 if *action != "cherry-pick" && *action != "revert" { | |
| 156 flag.PrintDefaults() | |
| 157 fmt.Printf( | |
| 158 "must action must be 'cherry-pick' or 'revert'. Got '%s' .\n", *action) | |
| 159 os.Exit(1) | |
| 160 } | |
| 161 | |
| 162 repo := &git.Repo{Path: workdir} | |
| 163 if repo.RunOk("rev-parse", "--git-dir") { | |
| 164 if !repo.RunOk("config", "drover.repo") { | |
| 165 fmt.Println("must be run from an empty directory or a dr over git repo") | |
| 166 os.Exit(1) | |
| 167 } | |
| 168 } else { | |
| 169 files, err := ioutil.ReadDir(workdir) | |
| 170 failIf(err) | |
| 171 if len(files) == 0 { | |
| 172 fmt.Println("Initializing current directory as a drover git repo") | |
| 173 if !repo.RunOk("init") { | |
| 174 flag.PrintDefaults() | |
| 175 panic("Couldn't initialize git repo!") | |
| 176 } | |
| 177 if !repo.RunOk("config", "drover.repo", "true") { | |
| 178 flag.PrintDefaults() | |
| 179 panic("Couldn't configure git repo to be owned b y drover!") | |
| 180 } | |
| 181 } else { | |
| 182 flag.PrintDefaults() | |
| 183 fmt.Println("must be run from an empty directory or a dr over git repo") | |
| 184 os.Exit(1) | |
| 185 } | |
| 186 } | |
| 187 | |
| 188 gitSha, gitilesURL, err := disambiguateCommit(*commitish) | |
| 189 failIf(err) | |
| 190 | |
| 191 g := gitiles.NewGitiles(gitilesURL, 8) | |
| 192 | |
| 193 *ref, err = disambiguateRef(g, gitSha, *ref) | |
| 194 failIf(err) | |
| 195 | |
| 196 commit, treeDiff := getCommitData(g, gitSha) | |
| 197 textDiff := g.GetTextCommitDiff(commit.ID()) | |
| 198 failIf(textDiff.Err) | |
| 199 | |
| 200 settings := getCodereviewSettings(g, *ref) | |
| 201 landCommit, real_ref := getLandingCommit(g, *ref, settings) | |
| 202 | |
| 203 confirmAction(g.URL(), *action, *ref, commit, textDiff.Diff) | |
| 204 | |
| 205 acquireObjects(repo, g, commit, landCommit, treeDiff) | |
| 206 | |
| 207 var left, right git.ObjectID | |
| 208 if *action == "cherry-pick" { | |
| 209 left, right = commit.Parents()[0], commit.ID() | |
| 210 } else if *action == "revert" { | |
| 211 right, left = commit.Parents()[0], commit.ID() | |
| 212 } | |
| 213 toPush := createCommit(*action, repo, left, right, landCommit.ID()) | |
| 214 id := toPush.ID().String() | |
| 215 | |
| 216 confirmAction(g.URL(), "push", real_ref, toPush, repo.GetTextDiff(id+"~" , id)) | |
| 217 } | |
| OLD | NEW |