| // Copyright (C) 2021 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.acceptance.testsuite.change; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| import static org.eclipse.jgit.lib.Constants.HEAD; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.gerrit.acceptance.GitUtil; |
| import com.google.gerrit.acceptance.PushOneCommit; |
| import com.google.gerrit.acceptance.TestAccount; |
| import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; |
| import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; |
| import com.google.gerrit.entities.LabelId; |
| import com.google.gerrit.entities.Project; |
| import com.google.gerrit.extensions.api.GerritApi; |
| import com.google.gerrit.extensions.api.changes.CherryPickInput; |
| import com.google.gerrit.extensions.api.changes.ReviewInput; |
| import com.google.gerrit.extensions.api.changes.RevisionApi; |
| import com.google.gerrit.extensions.client.ChangeKind; |
| import com.google.gerrit.extensions.client.ListChangesOption; |
| import com.google.gerrit.extensions.common.ChangeInfo; |
| import com.google.gerrit.extensions.common.CommitInfo; |
| import com.google.inject.Inject; |
| import java.util.List; |
| import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; |
| import org.eclipse.jgit.junit.TestRepository; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| |
| /** Helper to create changes of a certain {@link ChangeKind}. */ |
| public class ChangeKindCreator { |
| private GerritApi gApi; |
| private PushOneCommit.Factory pushFactory; |
| private RequestScopeOperations requestScopeOperations; |
| private ProjectOperations projectOperations; |
| |
| @Inject |
| private ChangeKindCreator( |
| GerritApi gApi, |
| PushOneCommit.Factory pushFactory, |
| RequestScopeOperations requestScopeOperations, |
| ProjectOperations projectOperations) { |
| this.gApi = gApi; |
| this.pushFactory = pushFactory; |
| this.requestScopeOperations = requestScopeOperations; |
| this.projectOperations = projectOperations; |
| } |
| |
| /** Creates a change with the given {@link ChangeKind} and returns the change id. */ |
| public String createChange( |
| ChangeKind kind, TestRepository<InMemoryRepository> testRepo, TestAccount user) |
| throws Exception { |
| switch (kind) { |
| case NO_CODE_CHANGE: |
| case REWORK: |
| case TRIVIAL_REBASE: |
| case NO_CHANGE: |
| return createChange(testRepo, user).getChangeId(); |
| case MERGE_FIRST_PARENT_UPDATE: |
| return createChangeForMergeCommit(testRepo, user); |
| default: |
| throw new IllegalStateException("unexpected change kind: " + kind); |
| } |
| } |
| |
| /** Updates a change with the given {@link ChangeKind}. */ |
| public void updateChange( |
| String changeId, |
| ChangeKind changeKind, |
| TestRepository<InMemoryRepository> testRepo, |
| TestAccount user, |
| Project.NameKey project) |
| throws Exception { |
| switch (changeKind) { |
| case NO_CODE_CHANGE: |
| noCodeChange(changeId, testRepo, user); |
| return; |
| case REWORK: |
| rework(changeId, testRepo, user); |
| return; |
| case TRIVIAL_REBASE: |
| trivialRebase(changeId, testRepo, user, project); |
| return; |
| case MERGE_FIRST_PARENT_UPDATE: |
| updateFirstParent(changeId, testRepo, user); |
| return; |
| case NO_CHANGE: |
| noChange(changeId, testRepo, user); |
| return; |
| default: |
| assertWithMessage("unexpected change kind: " + changeKind).fail(); |
| } |
| } |
| |
| /** |
| * Creates a cherry pick of the provided change with the given {@link ChangeKind} and returns the |
| * change id. |
| */ |
| public String cherryPick( |
| String changeId, |
| ChangeKind changeKind, |
| TestRepository<InMemoryRepository> testRepo, |
| TestAccount user, |
| Project.NameKey project) |
| throws Exception { |
| switch (changeKind) { |
| case REWORK: |
| case TRIVIAL_REBASE: |
| break; |
| case NO_CODE_CHANGE: |
| case NO_CHANGE: |
| case MERGE_FIRST_PARENT_UPDATE: |
| default: |
| assertWithMessage("unexpected change kind: " + changeKind).fail(); |
| } |
| |
| testRepo.reset(projectOperations.project(project).getHead("master")); |
| PushOneCommit.Result r = |
| pushFactory |
| .create( |
| user.newIdent(), |
| testRepo, |
| PushOneCommit.SUBJECT, |
| "other.txt", |
| "new content " + System.nanoTime()) |
| .to("refs/for/master"); |
| r.assertOkStatus(); |
| vote(user, r.getChangeId(), 2, 1); |
| merge(r); |
| |
| String subject = |
| ChangeKind.TRIVIAL_REBASE.equals(changeKind) |
| ? PushOneCommit.SUBJECT |
| : "Reworked change " + System.nanoTime(); |
| CherryPickInput in = new CherryPickInput(); |
| in.destination = "master"; |
| in.message = String.format("%s\n\nChange-Id: %s", subject, changeId); |
| ChangeInfo c = gApi.changes().id(changeId).current().cherryPick(in).get(); |
| return c.changeId; |
| } |
| |
| /** Creates a change that is a merge {@link ChangeKind} and returns the change id. */ |
| public String createChangeForMergeCommit( |
| TestRepository<InMemoryRepository> testRepo, TestAccount user) throws Exception { |
| ObjectId initial = testRepo.getRepository().exactRef(HEAD).getLeaf().getObjectId(); |
| |
| PushOneCommit.Result parent1 = createChange("parent 1", "p1.txt", "content 1", testRepo, user); |
| |
| testRepo.reset(initial); |
| PushOneCommit.Result parent2 = createChange("parent 2", "p2.txt", "content 2", testRepo, user); |
| |
| testRepo.reset(parent1.getCommit()); |
| |
| PushOneCommit merge = pushFactory.create(user.newIdent(), testRepo); |
| merge.setParents(ImmutableList.of(parent1.getCommit(), parent2.getCommit())); |
| PushOneCommit.Result result = merge.to("refs/for/master"); |
| result.assertOkStatus(); |
| return result.getChangeId(); |
| } |
| |
| /** Update the first parent of a merge. */ |
| public void updateFirstParent( |
| String changeId, TestRepository<InMemoryRepository> testRepo, TestAccount user) |
| throws Exception { |
| ChangeInfo c = detailedChange(changeId); |
| List<CommitInfo> parents = c.revisions.get(c.currentRevision).commit.parents; |
| String parent1 = parents.get(0).commit; |
| String parent2 = parents.get(1).commit; |
| RevCommit commitParent2 = testRepo.getRevWalk().parseCommit(ObjectId.fromString(parent2)); |
| |
| testRepo.reset(parent1); |
| PushOneCommit.Result newParent1 = |
| createChange("new parent 1", "p1-1.txt", "content 1-1", testRepo, user); |
| |
| PushOneCommit merge = pushFactory.create(user.newIdent(), testRepo, changeId); |
| merge.setParents(ImmutableList.of(newParent1.getCommit(), commitParent2)); |
| PushOneCommit.Result result = merge.to("refs/for/master"); |
| result.assertOkStatus(); |
| |
| assertThat(getChangeKind(changeId)).isEqualTo(ChangeKind.MERGE_FIRST_PARENT_UPDATE); |
| } |
| |
| /** Update the second parent of a merge. */ |
| public void updateSecondParent( |
| String changeId, TestRepository<InMemoryRepository> testRepo, TestAccount user) |
| throws Exception { |
| ChangeInfo c = detailedChange(changeId); |
| List<CommitInfo> parents = c.revisions.get(c.currentRevision).commit.parents; |
| String parent1 = parents.get(0).commit; |
| String parent2 = parents.get(1).commit; |
| RevCommit commitParent1 = testRepo.getRevWalk().parseCommit(ObjectId.fromString(parent1)); |
| |
| testRepo.reset(parent2); |
| PushOneCommit.Result newParent2 = |
| createChange("new parent 2", "p2-2.txt", "content 2-2", testRepo, user); |
| |
| PushOneCommit merge = pushFactory.create(user.newIdent(), testRepo, changeId); |
| merge.setParents(ImmutableList.of(commitParent1, newParent2.getCommit())); |
| PushOneCommit.Result result = merge.to("refs/for/master"); |
| result.assertOkStatus(); |
| |
| assertThat(getChangeKind(changeId)).isEqualTo(ChangeKind.REWORK); |
| } |
| |
| private void noCodeChange( |
| String changeId, TestRepository<InMemoryRepository> testRepo, TestAccount user) |
| throws Exception { |
| TestRepository<?>.CommitBuilder commitBuilder = |
| testRepo.amendRef("HEAD").insertChangeId(changeId.substring(1)); |
| commitBuilder |
| .message("New subject " + System.nanoTime()) |
| .author(user.newIdent()) |
| .committer(new PersonIdent(user.newIdent(), testRepo.getDate())); |
| commitBuilder.create(); |
| GitUtil.pushHead(testRepo, "refs/for/master", false); |
| assertThat(getChangeKind(changeId)).isEqualTo(ChangeKind.NO_CODE_CHANGE); |
| } |
| |
| private void noChange( |
| String changeId, TestRepository<InMemoryRepository> testRepo, TestAccount user) |
| throws Exception { |
| ChangeInfo change = gApi.changes().id(changeId).get(); |
| String commitMessage = change.revisions.get(change.currentRevision).commit.message; |
| |
| TestRepository<?>.CommitBuilder commitBuilder = |
| testRepo.amendRef("HEAD").insertChangeId(changeId.substring(1)); |
| commitBuilder |
| .message(commitMessage) |
| .author(user.newIdent()) |
| .committer(new PersonIdent(user.newIdent(), testRepo.getDate())); |
| commitBuilder.create(); |
| GitUtil.pushHead(testRepo, "refs/for/master", false); |
| assertThat(getChangeKind(changeId)).isEqualTo(ChangeKind.NO_CHANGE); |
| } |
| |
| private void rework( |
| String changeId, TestRepository<InMemoryRepository> testRepo, TestAccount user) |
| throws Exception { |
| PushOneCommit push = |
| pushFactory.create( |
| user.newIdent(), |
| testRepo, |
| PushOneCommit.SUBJECT, |
| PushOneCommit.FILE_NAME, |
| "new content " + System.nanoTime(), |
| changeId); |
| push.to("refs/for/master").assertOkStatus(); |
| assertThat(getChangeKind(changeId)).isEqualTo(ChangeKind.REWORK); |
| } |
| |
| private void trivialRebase( |
| String changeId, |
| TestRepository<InMemoryRepository> testRepo, |
| TestAccount user, |
| Project.NameKey project) |
| throws Exception { |
| requestScopeOperations.setApiUser(user.id()); |
| testRepo.reset(projectOperations.project(project).getHead("master")); |
| PushOneCommit push = |
| pushFactory.create( |
| user.newIdent(), |
| testRepo, |
| "Other Change", |
| "a" + System.nanoTime() + ".txt", |
| PushOneCommit.FILE_CONTENT); |
| PushOneCommit.Result r = push.to("refs/for/master"); |
| r.assertOkStatus(); |
| RevisionApi revision = gApi.changes().id(r.getChangeId()).current(); |
| ReviewInput in = new ReviewInput().label(LabelId.CODE_REVIEW, 2).label(LabelId.VERIFIED, 1); |
| revision.review(in); |
| revision.submit(); |
| |
| gApi.changes().id(changeId).current().rebase(); |
| assertThat(getChangeKind(changeId)).isEqualTo(ChangeKind.TRIVIAL_REBASE); |
| } |
| |
| private ChangeKind getChangeKind(String changeId) throws Exception { |
| ChangeInfo c = gApi.changes().id(changeId).get(ListChangesOption.CURRENT_REVISION); |
| return c.revisions.get(c.currentRevision).kind; |
| } |
| |
| private PushOneCommit.Result createChange( |
| TestRepository<InMemoryRepository> testRepo, TestAccount user) throws Exception { |
| PushOneCommit push = pushFactory.create(user.newIdent(), testRepo); |
| PushOneCommit.Result result = push.to("refs/for/master"); |
| result.assertOkStatus(); |
| return result; |
| } |
| |
| private ChangeInfo detailedChange(String changeId) throws Exception { |
| return gApi.changes() |
| .id(changeId) |
| .get( |
| ListChangesOption.DETAILED_LABELS, |
| ListChangesOption.CURRENT_REVISION, |
| ListChangesOption.CURRENT_COMMIT); |
| } |
| |
| private PushOneCommit.Result createChange( |
| String subject, |
| String fileName, |
| String content, |
| TestRepository<InMemoryRepository> testRepo, |
| TestAccount user) |
| throws Exception { |
| PushOneCommit push = pushFactory.create(user.newIdent(), testRepo, subject, fileName, content); |
| return push.to("refs/for/master"); |
| } |
| |
| private void vote(TestAccount user, String changeId, int codeReviewVote, int verifiedVote) |
| throws Exception { |
| requestScopeOperations.setApiUser(user.id()); |
| ReviewInput in = |
| new ReviewInput() |
| .label(LabelId.CODE_REVIEW, codeReviewVote) |
| .label(LabelId.VERIFIED, verifiedVote); |
| gApi.changes().id(changeId).current().review(in); |
| } |
| |
| private void merge(PushOneCommit.Result r) throws Exception { |
| gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve()); |
| gApi.changes().id(r.getChangeId()).current().submit(); |
| } |
| } |