1// Copyright 2014 The Go Authors.  All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package main
6
7import (
8	"fmt"
9	"strings"
10	"testing"
11)
12
13func TestChange(t *testing.T) {
14	gt := newGitTest(t)
15	defer gt.done()
16
17	t.Logf("main -> main")
18	testMain(t, "change", "main")
19	testRan(t, "git checkout -q main")
20	branchpoint := strings.TrimSpace(trun(t, gt.client, "git", "rev-parse", "HEAD"))
21
22	testCommitMsg = "foo: my commit msg"
23	t.Logf("main -> work")
24	testMain(t, "change", "work")
25	testRan(t, "git checkout -q -b work HEAD",
26		"git branch -q --set-upstream-to origin/main")
27
28	t.Logf("work -> main")
29	testMain(t, "change", "main")
30	testRan(t, "git checkout -q main")
31
32	t.Logf("main -> work with staged changes")
33	write(t, gt.client+"/file", "new content", 0644)
34	trun(t, gt.client, "git", "add", "file")
35	testMain(t, "change", "work")
36	testRan(t, "git checkout -q work",
37		"git commit -q --allow-empty -m foo: my commit msg")
38
39	t.Logf("work -> work2")
40	testMain(t, "change", "work2")
41	testRan(t, "git checkout -q -b work2 "+branchpoint,
42		"git branch -q --set-upstream-to origin/main")
43
44	t.Logf("work2 -> dev.branch")
45	testMain(t, "change", "dev.branch")
46	testRan(t, "git checkout -q -t -b dev.branch origin/dev.branch")
47
48	testMain(t, "pending", "-c")
49	testPrintedStdout(t, "tracking dev.branch")
50	testMain(t, "change", "main")
51	testMain(t, "change", "work2")
52
53	t.Logf("server work × 2")
54	gt.serverWork(t)
55	gt.serverWork(t)
56	testMain(t, "sync")
57
58	t.Logf("-> work with server ahead")
59	testMain(t, "change", "work")
60	testPrintedStderr(t, "warning: 2 commits behind origin/main; run 'git codereview sync' to update")
61}
62
63func TestChangeHEAD(t *testing.T) {
64	gt := newGitTest(t)
65	defer gt.done()
66
67	testMainDied(t, "change", "HeAd")
68	testPrintedStderr(t, "invalid branch name \"HeAd\": ref name HEAD is reserved for git")
69}
70
71func TestMessageRE(t *testing.T) {
72	for _, c := range []struct {
73		in   string
74		want bool
75	}{
76		{"blah", false},
77		{"[release-branch.go1.4] blah", false},
78		{"[release-branch.go1.4] math: fix cosine", true},
79		{"math: fix cosine", true},
80		{"math/rand: make randomer", true},
81		{"math/rand, crypto/rand: fix random sources", true},
82		{"cmd/internal/rsc.io/x86: update from external repo", true},
83	} {
84		got := messageRE.MatchString(c.in)
85		if got != c.want {
86			t.Errorf("MatchString(%q) = %v, want %v", c.in, got, c.want)
87		}
88	}
89}
90
91func TestChangeAmendCommit(t *testing.T) {
92	gt := newGitTest(t)
93	defer gt.done()
94
95	testCommitMsg = "foo: amended commit message"
96	gt.work(t)
97
98	write(t, gt.client+"/file", "new content in work to be amend", 0644)
99	trun(t, gt.client, "git", "add", "file")
100	testMain(t, "change")
101}
102
103func TestChangeFailAmendWithMultiplePending(t *testing.T) {
104	gt := newGitTest(t)
105	defer gt.done()
106
107	testCommitMsg = "foo: amended commit message"
108	gt.work(t)
109	gt.work(t)
110
111	write(t, gt.client+"/file", "new content in work to be amend", 0644)
112	trun(t, gt.client, "git", "add", "file")
113	testMainDied(t, "change")
114	testPrintedStderr(t, "multiple changes pending")
115}
116
117func TestChangeCL(t *testing.T) {
118	gt := newGitTest(t)
119	defer gt.done()
120
121	srv := newGerritServer(t)
122	defer srv.done()
123
124	// Ensure that 'change' with a CL accepts we have gerrit. Test address is injected by newGerritServer.
125	write(t, gt.server+"/codereview.cfg", "gerrit: on", 0644)
126	trun(t, gt.server, "git", "add", "codereview.cfg")
127	trun(t, gt.server, "git", "commit", "-m", "codereview.cfg on main")
128	trun(t, gt.client, "git", "pull")
129	defer srv.done()
130
131	hash1 := trim(trun(t, gt.server, "git", "rev-parse", "dev.branch"))
132	hash2 := trim(trun(t, gt.server, "git", "rev-parse", "release.branch"))
133	trun(t, gt.server, "git", "update-ref", "refs/changes/00/100/1", hash1)
134	trun(t, gt.server, "git", "update-ref", "refs/changes/00/100/2", hash2)
135	trun(t, gt.server, "git", "update-ref", "refs/changes/00/100/3", hash1)
136	srv.setReply("/a/changes/100", gerritReply{f: func() gerritReply {
137		changeJSON := `{
138			"current_revision": "HASH",
139			"revisions": {
140				"HASH": {
141					"_number": 3
142				}
143			}
144		}`
145		changeJSON = strings.Replace(changeJSON, "HASH", hash1, -1)
146		return gerritReply{body: ")]}'\n" + changeJSON}
147	}})
148
149	checkChangeCL := func(arg, ref, hash string) {
150		testMain(t, "change", "main")
151		testMain(t, "change", arg)
152		testRan(t,
153			fmt.Sprintf("git fetch -q origin %s", ref),
154			"git checkout -q FETCH_HEAD")
155		if hash != trim(trun(t, gt.client, "git", "rev-parse", "HEAD")) {
156			t.Fatalf("hash do not match for CL %s", arg)
157		}
158	}
159
160	checkChangeCL("100/1", "refs/changes/00/100/1", hash1)
161	checkChangeCL("100/2", "refs/changes/00/100/2", hash2)
162	checkChangeCL("100", "refs/changes/00/100/3", hash1)
163
164	// turn off gerrit, make it look like we are on GitHub
165	write(t, gt.server+"/codereview.cfg", "nothing: here", 0644)
166	trun(t, gt.server, "git", "add", "codereview.cfg")
167	trun(t, gt.server, "git", "commit", "-m", "new codereview.cfg on main")
168	testMain(t, "change", "main")
169	trun(t, gt.client, "git", "pull", "-r")
170	trun(t, gt.client, "git", "remote", "set-url", "origin", "https://github.com/google/not-a-project")
171
172	testMain(t, "change", "-n", "123")
173	testNoStdout(t)
174	testPrintedStderr(t,
175		"git fetch -q origin pull/123/head",
176		"git checkout -q FETCH_HEAD",
177	)
178}
179
180func TestChangeWithMessage(t *testing.T) {
181	gt := newGitTest(t)
182	defer gt.done()
183
184	testMain(t, "change", "new_branch")
185	testMain(t, "change", "-m", "foo: some commit message")
186	testRan(t, "git commit -q --allow-empty -m foo: some commit message")
187}
188