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