Line data Source code
1 : // Copyright (C) 2021 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.acceptance.testsuite.change;
16 :
17 : import static com.google.common.truth.Truth.assertThat;
18 : import static com.google.common.truth.Truth.assertWithMessage;
19 : import static org.eclipse.jgit.lib.Constants.HEAD;
20 :
21 : import com.google.common.collect.ImmutableList;
22 : import com.google.gerrit.acceptance.GitUtil;
23 : import com.google.gerrit.acceptance.PushOneCommit;
24 : import com.google.gerrit.acceptance.TestAccount;
25 : import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
26 : import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
27 : import com.google.gerrit.entities.LabelId;
28 : import com.google.gerrit.entities.Project;
29 : import com.google.gerrit.extensions.api.GerritApi;
30 : import com.google.gerrit.extensions.api.changes.CherryPickInput;
31 : import com.google.gerrit.extensions.api.changes.ReviewInput;
32 : import com.google.gerrit.extensions.api.changes.RevisionApi;
33 : import com.google.gerrit.extensions.client.ChangeKind;
34 : import com.google.gerrit.extensions.client.ListChangesOption;
35 : import com.google.gerrit.extensions.common.ChangeInfo;
36 : import com.google.gerrit.extensions.common.CommitInfo;
37 : import com.google.inject.Inject;
38 : import java.util.List;
39 : import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
40 : import org.eclipse.jgit.junit.TestRepository;
41 : import org.eclipse.jgit.lib.ObjectId;
42 : import org.eclipse.jgit.lib.PersonIdent;
43 : import org.eclipse.jgit.revwalk.RevCommit;
44 :
45 : /** Helper to create changes of a certain {@link ChangeKind}. */
46 : public class ChangeKindCreator {
47 : private GerritApi gApi;
48 : private PushOneCommit.Factory pushFactory;
49 : private RequestScopeOperations requestScopeOperations;
50 : private ProjectOperations projectOperations;
51 :
52 : @Inject
53 : private ChangeKindCreator(
54 : GerritApi gApi,
55 : PushOneCommit.Factory pushFactory,
56 : RequestScopeOperations requestScopeOperations,
57 2 : ProjectOperations projectOperations) {
58 2 : this.gApi = gApi;
59 2 : this.pushFactory = pushFactory;
60 2 : this.requestScopeOperations = requestScopeOperations;
61 2 : this.projectOperations = projectOperations;
62 2 : }
63 :
64 : /** Creates a change with the given {@link ChangeKind} and returns the change id. */
65 : public String createChange(
66 : ChangeKind kind, TestRepository<InMemoryRepository> testRepo, TestAccount user)
67 : throws Exception {
68 2 : switch (kind) {
69 : case NO_CODE_CHANGE:
70 : case REWORK:
71 : case TRIVIAL_REBASE:
72 : case NO_CHANGE:
73 2 : return createChange(testRepo, user).getChangeId();
74 : case MERGE_FIRST_PARENT_UPDATE:
75 1 : return createChangeForMergeCommit(testRepo, user);
76 : default:
77 0 : throw new IllegalStateException("unexpected change kind: " + kind);
78 : }
79 : }
80 :
81 : /** Updates a change with the given {@link ChangeKind}. */
82 : public void updateChange(
83 : String changeId,
84 : ChangeKind changeKind,
85 : TestRepository<InMemoryRepository> testRepo,
86 : TestAccount user,
87 : Project.NameKey project)
88 : throws Exception {
89 2 : switch (changeKind) {
90 : case NO_CODE_CHANGE:
91 2 : noCodeChange(changeId, testRepo, user);
92 2 : return;
93 : case REWORK:
94 2 : rework(changeId, testRepo, user);
95 2 : return;
96 : case TRIVIAL_REBASE:
97 2 : trivialRebase(changeId, testRepo, user, project);
98 2 : return;
99 : case MERGE_FIRST_PARENT_UPDATE:
100 1 : updateFirstParent(changeId, testRepo, user);
101 1 : return;
102 : case NO_CHANGE:
103 1 : noChange(changeId, testRepo, user);
104 1 : return;
105 : default:
106 0 : assertWithMessage("unexpected change kind: " + changeKind).fail();
107 : }
108 0 : }
109 :
110 : /**
111 : * Creates a cherry pick of the provided change with the given {@link ChangeKind} and returns the
112 : * change id.
113 : */
114 : public String cherryPick(
115 : String changeId,
116 : ChangeKind changeKind,
117 : TestRepository<InMemoryRepository> testRepo,
118 : TestAccount user,
119 : Project.NameKey project)
120 : throws Exception {
121 1 : switch (changeKind) {
122 : case REWORK:
123 : case TRIVIAL_REBASE:
124 1 : break;
125 : case NO_CODE_CHANGE:
126 : case NO_CHANGE:
127 : case MERGE_FIRST_PARENT_UPDATE:
128 : default:
129 0 : assertWithMessage("unexpected change kind: " + changeKind).fail();
130 : }
131 :
132 1 : testRepo.reset(projectOperations.project(project).getHead("master"));
133 1 : PushOneCommit.Result r =
134 : pushFactory
135 1 : .create(
136 1 : user.newIdent(),
137 : testRepo,
138 : PushOneCommit.SUBJECT,
139 : "other.txt",
140 1 : "new content " + System.nanoTime())
141 1 : .to("refs/for/master");
142 1 : r.assertOkStatus();
143 1 : vote(user, r.getChangeId(), 2, 1);
144 1 : merge(r);
145 :
146 : String subject =
147 1 : ChangeKind.TRIVIAL_REBASE.equals(changeKind)
148 1 : ? PushOneCommit.SUBJECT
149 1 : : "Reworked change " + System.nanoTime();
150 1 : CherryPickInput in = new CherryPickInput();
151 1 : in.destination = "master";
152 1 : in.message = String.format("%s\n\nChange-Id: %s", subject, changeId);
153 1 : ChangeInfo c = gApi.changes().id(changeId).current().cherryPick(in).get();
154 1 : return c.changeId;
155 : }
156 :
157 : /** Creates a change that is a merge {@link ChangeKind} and returns the change id. */
158 : public String createChangeForMergeCommit(
159 : TestRepository<InMemoryRepository> testRepo, TestAccount user) throws Exception {
160 1 : ObjectId initial = testRepo.getRepository().exactRef(HEAD).getLeaf().getObjectId();
161 :
162 1 : PushOneCommit.Result parent1 = createChange("parent 1", "p1.txt", "content 1", testRepo, user);
163 :
164 1 : testRepo.reset(initial);
165 1 : PushOneCommit.Result parent2 = createChange("parent 2", "p2.txt", "content 2", testRepo, user);
166 :
167 1 : testRepo.reset(parent1.getCommit());
168 :
169 1 : PushOneCommit merge = pushFactory.create(user.newIdent(), testRepo);
170 1 : merge.setParents(ImmutableList.of(parent1.getCommit(), parent2.getCommit()));
171 1 : PushOneCommit.Result result = merge.to("refs/for/master");
172 1 : result.assertOkStatus();
173 1 : return result.getChangeId();
174 : }
175 :
176 : /** Update the first parent of a merge. */
177 : public void updateFirstParent(
178 : String changeId, TestRepository<InMemoryRepository> testRepo, TestAccount user)
179 : throws Exception {
180 1 : ChangeInfo c = detailedChange(changeId);
181 1 : List<CommitInfo> parents = c.revisions.get(c.currentRevision).commit.parents;
182 1 : String parent1 = parents.get(0).commit;
183 1 : String parent2 = parents.get(1).commit;
184 1 : RevCommit commitParent2 = testRepo.getRevWalk().parseCommit(ObjectId.fromString(parent2));
185 :
186 1 : testRepo.reset(parent1);
187 1 : PushOneCommit.Result newParent1 =
188 1 : createChange("new parent 1", "p1-1.txt", "content 1-1", testRepo, user);
189 :
190 1 : PushOneCommit merge = pushFactory.create(user.newIdent(), testRepo, changeId);
191 1 : merge.setParents(ImmutableList.of(newParent1.getCommit(), commitParent2));
192 1 : PushOneCommit.Result result = merge.to("refs/for/master");
193 1 : result.assertOkStatus();
194 :
195 1 : assertThat(getChangeKind(changeId)).isEqualTo(ChangeKind.MERGE_FIRST_PARENT_UPDATE);
196 1 : }
197 :
198 : /** Update the second parent of a merge. */
199 : public void updateSecondParent(
200 : String changeId, TestRepository<InMemoryRepository> testRepo, TestAccount user)
201 : throws Exception {
202 1 : ChangeInfo c = detailedChange(changeId);
203 1 : List<CommitInfo> parents = c.revisions.get(c.currentRevision).commit.parents;
204 1 : String parent1 = parents.get(0).commit;
205 1 : String parent2 = parents.get(1).commit;
206 1 : RevCommit commitParent1 = testRepo.getRevWalk().parseCommit(ObjectId.fromString(parent1));
207 :
208 1 : testRepo.reset(parent2);
209 1 : PushOneCommit.Result newParent2 =
210 1 : createChange("new parent 2", "p2-2.txt", "content 2-2", testRepo, user);
211 :
212 1 : PushOneCommit merge = pushFactory.create(user.newIdent(), testRepo, changeId);
213 1 : merge.setParents(ImmutableList.of(commitParent1, newParent2.getCommit()));
214 1 : PushOneCommit.Result result = merge.to("refs/for/master");
215 1 : result.assertOkStatus();
216 :
217 1 : assertThat(getChangeKind(changeId)).isEqualTo(ChangeKind.REWORK);
218 1 : }
219 :
220 : private void noCodeChange(
221 : String changeId, TestRepository<InMemoryRepository> testRepo, TestAccount user)
222 : throws Exception {
223 2 : TestRepository<?>.CommitBuilder commitBuilder =
224 2 : testRepo.amendRef("HEAD").insertChangeId(changeId.substring(1));
225 2 : commitBuilder
226 2 : .message("New subject " + System.nanoTime())
227 2 : .author(user.newIdent())
228 2 : .committer(new PersonIdent(user.newIdent(), testRepo.getDate()));
229 2 : commitBuilder.create();
230 2 : GitUtil.pushHead(testRepo, "refs/for/master", false);
231 2 : assertThat(getChangeKind(changeId)).isEqualTo(ChangeKind.NO_CODE_CHANGE);
232 2 : }
233 :
234 : private void noChange(
235 : String changeId, TestRepository<InMemoryRepository> testRepo, TestAccount user)
236 : throws Exception {
237 1 : ChangeInfo change = gApi.changes().id(changeId).get();
238 1 : String commitMessage = change.revisions.get(change.currentRevision).commit.message;
239 :
240 1 : TestRepository<?>.CommitBuilder commitBuilder =
241 1 : testRepo.amendRef("HEAD").insertChangeId(changeId.substring(1));
242 1 : commitBuilder
243 1 : .message(commitMessage)
244 1 : .author(user.newIdent())
245 1 : .committer(new PersonIdent(user.newIdent(), testRepo.getDate()));
246 1 : commitBuilder.create();
247 1 : GitUtil.pushHead(testRepo, "refs/for/master", false);
248 1 : assertThat(getChangeKind(changeId)).isEqualTo(ChangeKind.NO_CHANGE);
249 1 : }
250 :
251 : private void rework(
252 : String changeId, TestRepository<InMemoryRepository> testRepo, TestAccount user)
253 : throws Exception {
254 2 : PushOneCommit push =
255 2 : pushFactory.create(
256 2 : user.newIdent(),
257 : testRepo,
258 : PushOneCommit.SUBJECT,
259 : PushOneCommit.FILE_NAME,
260 2 : "new content " + System.nanoTime(),
261 : changeId);
262 2 : push.to("refs/for/master").assertOkStatus();
263 2 : assertThat(getChangeKind(changeId)).isEqualTo(ChangeKind.REWORK);
264 2 : }
265 :
266 : private void trivialRebase(
267 : String changeId,
268 : TestRepository<InMemoryRepository> testRepo,
269 : TestAccount user,
270 : Project.NameKey project)
271 : throws Exception {
272 2 : requestScopeOperations.setApiUser(user.id());
273 2 : testRepo.reset(projectOperations.project(project).getHead("master"));
274 2 : PushOneCommit push =
275 2 : pushFactory.create(
276 2 : user.newIdent(),
277 : testRepo,
278 : "Other Change",
279 2 : "a" + System.nanoTime() + ".txt",
280 : PushOneCommit.FILE_CONTENT);
281 2 : PushOneCommit.Result r = push.to("refs/for/master");
282 2 : r.assertOkStatus();
283 2 : RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
284 2 : ReviewInput in = new ReviewInput().label(LabelId.CODE_REVIEW, 2).label(LabelId.VERIFIED, 1);
285 2 : revision.review(in);
286 2 : revision.submit();
287 :
288 2 : gApi.changes().id(changeId).current().rebase();
289 2 : assertThat(getChangeKind(changeId)).isEqualTo(ChangeKind.TRIVIAL_REBASE);
290 2 : }
291 :
292 : private ChangeKind getChangeKind(String changeId) throws Exception {
293 2 : ChangeInfo c = gApi.changes().id(changeId).get(ListChangesOption.CURRENT_REVISION);
294 2 : return c.revisions.get(c.currentRevision).kind;
295 : }
296 :
297 : private PushOneCommit.Result createChange(
298 : TestRepository<InMemoryRepository> testRepo, TestAccount user) throws Exception {
299 2 : PushOneCommit push = pushFactory.create(user.newIdent(), testRepo);
300 2 : PushOneCommit.Result result = push.to("refs/for/master");
301 2 : result.assertOkStatus();
302 2 : return result;
303 : }
304 :
305 : private ChangeInfo detailedChange(String changeId) throws Exception {
306 1 : return gApi.changes()
307 1 : .id(changeId)
308 1 : .get(
309 : ListChangesOption.DETAILED_LABELS,
310 : ListChangesOption.CURRENT_REVISION,
311 : ListChangesOption.CURRENT_COMMIT);
312 : }
313 :
314 : private PushOneCommit.Result createChange(
315 : String subject,
316 : String fileName,
317 : String content,
318 : TestRepository<InMemoryRepository> testRepo,
319 : TestAccount user)
320 : throws Exception {
321 1 : PushOneCommit push = pushFactory.create(user.newIdent(), testRepo, subject, fileName, content);
322 1 : return push.to("refs/for/master");
323 : }
324 :
325 : private void vote(TestAccount user, String changeId, int codeReviewVote, int verifiedVote)
326 : throws Exception {
327 1 : requestScopeOperations.setApiUser(user.id());
328 1 : ReviewInput in =
329 : new ReviewInput()
330 1 : .label(LabelId.CODE_REVIEW, codeReviewVote)
331 1 : .label(LabelId.VERIFIED, verifiedVote);
332 1 : gApi.changes().id(changeId).current().review(in);
333 1 : }
334 :
335 : private void merge(PushOneCommit.Result r) throws Exception {
336 1 : gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve());
337 1 : gApi.changes().id(r.getChangeId()).current().submit();
338 1 : }
339 : }
|