1/* 2Copyright 2014 The Perkeep Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17// This file adds the "review" subcommand to devcam, to send changes for peer review. 18 19package main 20 21import ( 22 "bufio" 23 "flag" 24 "fmt" 25 "log" 26 "os" 27 "os/exec" 28 "path/filepath" 29 "regexp" 30 31 "perkeep.org/pkg/cmdmain" 32) 33 34var ( 35 defaultHook = filepath.FromSlash("misc/commit-msg.githook") 36 hookFile = filepath.FromSlash(".git/hooks/commit-msg") 37 configFile = filepath.FromSlash(".git/config") 38) 39 40type reviewCmd struct { 41 releaseBranch string 42} 43 44func init() { 45 cmdmain.RegisterMode("review", func(flags *flag.FlagSet) cmdmain.CommandRunner { 46 cmd := &reviewCmd{} 47 flags.StringVar(&cmd.releaseBranch, "branch", "", "Alternative release branch to push to. Defaults to master branch.") 48 return cmd 49 }) 50} 51 52func (c *reviewCmd) Usage() { 53 fmt.Fprintf(cmdmain.Stderr, "Usage: devcam review\n") 54} 55 56func (c *reviewCmd) Describe() string { 57 return "Submit your git commits for review." 58} 59 60func (c *reviewCmd) RunCommand(args []string) error { 61 if len(args) > 0 { 62 return cmdmain.UsageError("too many arguments.") 63 } 64 goToCamliRoot() 65 c.checkHook() 66 checkOrigin() 67 c.gitPush() 68 return nil 69} 70 71func goToCamliRoot() { 72 prevDir, err := os.Getwd() 73 if err != nil { 74 log.Fatalf("could not get current directory: %v", err) 75 } 76 for { 77 if _, err := os.Stat(defaultHook); err == nil { 78 return 79 } 80 if err := os.Chdir(".."); err != nil { 81 log.Fatalf("Could not chdir: %v", err) 82 } 83 currentDir, err := os.Getwd() 84 if err != nil { 85 log.Fatalf("Could not get current directory: %v", err) 86 } 87 if currentDir == prevDir { 88 log.Fatal("Perkeep tree root not found. Run from within the Perkeep tree please.") 89 } 90 prevDir = currentDir 91 } 92} 93 94func (c *reviewCmd) checkHook() { 95 _, err := os.Stat(hookFile) 96 if err == nil { 97 return 98 } 99 if !os.IsNotExist(err) { 100 log.Fatal(err) 101 } 102 fmt.Fprintf(cmdmain.Stdout, "Presubmit hook to add Change-Id to commit messages is missing.\nNow automatically creating it at %v\n\n", hookFile) 103 cmd := exec.Command("devcam", "hook") 104 cmd.Stdout = cmdmain.Stdout 105 cmd.Stderr = cmdmain.Stderr 106 if err := cmd.Run(); err != nil { 107 log.Fatal(err) 108 } 109 fmt.Fprintf(cmdmain.Stdout, "Amending last commit to add Change-Id.\nPlease re-save description without making changes.\n\n") 110 fmt.Fprintf(cmdmain.Stdout, "Press Enter to continue.\n") 111 if _, _, err := bufio.NewReader(cmdmain.Stdin).ReadLine(); err != nil { 112 log.Fatal(err) 113 } 114 115 cmd = exec.Command("git", []string{"commit", "--amend"}...) 116 cmd.Stdout = cmdmain.Stdout 117 cmd.Stderr = cmdmain.Stderr 118 if err := cmd.Run(); err != nil { 119 log.Fatal(err) 120 } 121} 122 123const newOrigin = "https://perkeep.googlesource.com/perkeep" 124 125var ( 126 newFetch = regexp.MustCompile(`.*Fetch\s+URL:\s+` + newOrigin + `.*`) 127 newPush = regexp.MustCompile(`.*Push\s+URL:\s+` + newOrigin + `.*`) 128) 129 130func checkOrigin() { 131 out, err := exec.Command("git", "remote", "show", "origin").CombinedOutput() 132 if err != nil { 133 log.Fatalf("%v, %s", err, out) 134 } 135 136 if !newPush.Match(out) { 137 setPushOrigin() 138 } 139 140 if !newFetch.Match(out) { 141 setFetchOrigin() 142 } 143} 144 145func setPushOrigin() { 146 out, err := exec.Command("git", "remote", "set-url", "--push", "origin", newOrigin).CombinedOutput() 147 if err != nil { 148 log.Fatalf("%v, %s", err, out) 149 } 150} 151 152func setFetchOrigin() { 153 out, err := exec.Command("git", "remote", "set-url", "origin", newOrigin).CombinedOutput() 154 if err != nil { 155 log.Fatalf("%v, %s", err, out) 156 } 157} 158 159func (c *reviewCmd) gitPush() { 160 args := []string{"push", "origin"} 161 if c.releaseBranch != "" { 162 args = append(args, "HEAD:refs/for/releases/"+c.releaseBranch) 163 } else { 164 args = append(args, "HEAD:refs/for/master") 165 } 166 cmd := exec.Command("git", args...) 167 cmd.Stdout = cmdmain.Stdout 168 cmd.Stderr = cmdmain.Stderr 169 if err := cmd.Run(); err != nil { 170 log.Fatalf("Could not git push: %v", err) 171 } 172} 173