blob: 5a024cc3f64d78f7d0cb4d7a9d0e9fa4d1482701 [file] [log] [blame]
// Copyright (C) 2017 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.api.project;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.toList;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
import com.google.common.truth.Correspondence;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.IncludedInInfo;
import com.google.gerrit.extensions.api.changes.RelatedChangeAndCommitInfo;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.TagInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.truth.NullAwareCorrespondence;
import com.google.inject.Inject;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
@NoHttpd
public class CommitIT extends AbstractDaemonTest {
@Inject private ProjectOperations projectOperations;
@Test
public void getCommitInfo() throws Exception {
Result result = createChange();
String commitId = result.getCommit().getId().name();
CommitInfo info = gApi.projects().name(project.get()).commit(commitId).get();
assertThat(info.commit).isEqualTo(commitId);
assertThat(info.parents.stream().map(c -> c.commit).collect(toList()))
.containsExactly(result.getCommit().getParent(0).name());
assertThat(info.subject).isEqualTo(result.getCommit().getShortMessage());
assertPerson(info.author, admin);
assertPerson(info.committer, admin);
assertThat(info.webLinks).isNull();
}
@Test
public void includedInOpenChange() throws Exception {
Result result = createChange();
assertThat(getIncludedIn(result.getCommit().getId()).branches).isEmpty();
assertThat(getIncludedIn(result.getCommit().getId()).tags).isEmpty();
}
@Test
public void includedInMergedChange() throws Exception {
Result result = createChange();
gApi.changes()
.id(result.getChangeId())
.revision(result.getCommit().name())
.review(ReviewInput.approve());
gApi.changes().id(result.getChangeId()).revision(result.getCommit().name()).submit();
assertThat(getIncludedIn(result.getCommit().getId()).branches).containsExactly("master");
assertThat(getIncludedIn(result.getCommit().getId()).tags).isEmpty();
projectOperations
.project(project)
.forUpdate()
.add(allow(Permission.CREATE_TAG).ref(R_TAGS + "*").group(adminGroupUuid()))
.update();
gApi.projects().name(result.getChange().project().get()).tag("test-tag").create(new TagInput());
assertThat(getIncludedIn(result.getCommit().getId()).tags).containsExactly("test-tag");
createBranch(BranchNameKey.create(project, "test-branch"));
assertThat(getIncludedIn(result.getCommit().getId()).branches)
.containsExactly("master", "test-branch");
}
@Test
public void includedInMergedChange_filtersOutNonVisibleBranches() throws Exception {
Result baseChange = createAndSubmitChange("refs/for/master");
createBranch(BranchNameKey.create(project, "test-branch-1"));
createBranch(BranchNameKey.create(project, "test-branch-2"));
createAndSubmitChange("refs/for/test-branch-1");
createAndSubmitChange("refs/for/test-branch-2");
assertThat(getIncludedIn(baseChange.getCommit().getId()).branches)
.containsExactly("master", "test-branch-1", "test-branch-2");
projectOperations
.project(project)
.forUpdate()
.add(block(Permission.READ).ref("refs/heads/test-branch-1").group(REGISTERED_USERS))
.update();
assertThat(getIncludedIn(baseChange.getCommit().getId()).branches)
.containsExactly("master", "test-branch-2");
}
@Test
public void includedInMergedChange_filtersOutNonVisibleTags() throws Exception {
String tagBase = "tag_base";
String tagBranch1 = "tag_1";
Result baseChange = createAndSubmitChange("refs/for/master");
createLightWeightTag(tagBase);
assertThat(getIncludedIn(baseChange.getCommit().getId()).tags).containsExactly(tagBase);
createBranch(BranchNameKey.create(project, "test-branch-1"));
createAndSubmitChange("refs/for/test-branch-1");
createLightWeightTag(tagBranch1);
assertThat(getIncludedIn(baseChange.getCommit().getId()).tags)
.containsExactly(tagBase, tagBranch1);
projectOperations
.project(project)
.forUpdate()
// Tag permissions are controlled by read permissions on branches. Blocking read permission
// on test-branch-1 so that tagBranch1 becomes non-visible
.add(block(Permission.READ).ref("refs/heads/test-branch-1").group(REGISTERED_USERS))
.update();
assertThat(getIncludedIn(baseChange.getCommit().getId()).tags).containsExactly(tagBase);
}
@Test
public void cherryPickWithoutMessageSameBranch() throws Exception {
String destBranch = "master";
// Create change to cherry-pick
PushOneCommit.Result r = createChange();
ChangeInfo changeToCherryPick = info(r.getChangeId());
RevCommit commitToCherryPick = r.getCommit();
// Cherry-pick without message.
CherryPickInput input = new CherryPickInput();
input.destination = destBranch;
ChangeInfo cherryPickResult =
gApi.projects()
.name(project.get())
.commit(commitToCherryPick.name())
.cherryPick(input)
.get();
// Expect that the Change-Id of the cherry-picked commit was used for the cherry-pick change.
// New patch-set to existing change was uploaded.
assertThat(cherryPickResult._number).isEqualTo(changeToCherryPick._number);
assertThat(cherryPickResult.revisions).hasSize(2);
assertThat(cherryPickResult.changeId).isEqualTo(changeToCherryPick.changeId);
assertThat(cherryPickResult.messages).hasSize(2);
// Cherry-pick of is not set, because the source change was not provided.
assertThat(cherryPickResult.cherryPickOfChange).isNull();
assertThat(cherryPickResult.cherryPickOfPatchSet).isNull();
// Expect that the message of the cherry-picked commit was used for the cherry-pick change.
RevisionInfo revInfo = cherryPickResult.revisions.get(cherryPickResult.currentRevision);
assertThat(revInfo).isNotNull();
assertThat(revInfo.commit.message).isEqualTo(commitToCherryPick.getFullMessage());
}
@Test
public void cherryPickWithoutMessageOtherBranch() throws Exception {
String destBranch = "foo";
createBranch(BranchNameKey.create(project, destBranch));
// Create change to cherry-pick
PushOneCommit.Result r = createChange();
ChangeInfo changeToCherryPick = info(r.getChangeId());
RevCommit commitToCherryPick = r.getCommit();
// Cherry-pick without message.
CherryPickInput input = new CherryPickInput();
input.destination = destBranch;
ChangeInfo cherryPickResult =
gApi.projects()
.name(project.get())
.commit(commitToCherryPick.name())
.cherryPick(input)
.get();
// Expect that the Change-Id of the cherry-picked commit was used for the cherry-pick change.
// New change in destination branch was created.
assertThat(cherryPickResult._number).isGreaterThan(changeToCherryPick._number);
assertThat(cherryPickResult.revisions).hasSize(1);
assertThat(cherryPickResult.changeId).isEqualTo(changeToCherryPick.changeId);
assertThat(cherryPickResult.messages).hasSize(1);
// Cherry-pick of is not set, because the source change was not provided.
assertThat(cherryPickResult.cherryPickOfChange).isNull();
assertThat(cherryPickResult.cherryPickOfPatchSet).isNull();
// Expect that the message of the cherry-picked commit was used for the cherry-pick change.
RevisionInfo revInfo = cherryPickResult.revisions.get(cherryPickResult.currentRevision);
assertThat(revInfo).isNotNull();
assertThat(revInfo.commit.message).isEqualTo(commitToCherryPick.getFullMessage());
}
@Test
public void cherryPickCommitWithoutChangeIdCreateNewChange() throws Exception {
String destBranch = "foo";
createBranch(BranchNameKey.create(project, destBranch));
CherryPickInput input = new CherryPickInput();
input.destination = destBranch;
input.message = "it goes to foo branch";
RevCommit commitToCherryPick =
createNewCommitWithoutChangeId("refs/heads/master", "a.txt", "content");
ChangeInfo cherryPickResult =
gApi.projects()
.name(project.get())
.commit(commitToCherryPick.getName())
.cherryPick(input)
.get();
assertThat(cherryPickResult.messages).hasSize(1);
Iterator<ChangeMessageInfo> messageIterator = cherryPickResult.messages.iterator();
String expectedMessage =
String.format("Patch Set 1: Cherry Picked from commit %s.", commitToCherryPick.getName());
assertThat(messageIterator.next().message).isEqualTo(expectedMessage);
RevisionInfo revInfo = cherryPickResult.revisions.get(cherryPickResult.currentRevision);
assertThat(revInfo).isNotNull();
CommitInfo commitInfo = revInfo.commit;
assertThat(commitInfo.message)
.isEqualTo(input.message + "\n\nChange-Id: " + cherryPickResult.changeId + "\n");
}
@Test
public void cherryPickCommitWithChangeIdCreateNewChange() throws Exception {
String destBranch = "foo";
createBranch(BranchNameKey.create(project, destBranch));
PushOneCommit.Result r = createChange();
ChangeInfo changeToCherryPick = info(r.getChangeId());
RevCommit commitToCherryPick = r.getCommit();
List<String> footers = commitToCherryPick.getFooterLines("Change-Id");
assertThat(footers).hasSize(1);
String changeId = footers.get(0);
CherryPickInput input = new CherryPickInput();
input.destination = destBranch;
input.message =
String.format(
"it goes to foo branch\n\nChange-Id: Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef\n\nChange-Id: %s\n",
changeId);
ChangeInfo cherryPickResult =
gApi.projects()
.name(project.get())
.commit(commitToCherryPick.getName())
.cherryPick(input)
.get();
// No change was found in destination branch with the provided Change-Id.
assertThat(cherryPickResult._number).isGreaterThan(changeToCherryPick._number);
assertThat(cherryPickResult.changeId).isEqualTo(changeId);
assertThat(cherryPickResult.revisions).hasSize(1);
assertThat(cherryPickResult.messages).hasSize(1);
Iterator<ChangeMessageInfo> messageIterator = cherryPickResult.messages.iterator();
String expectedMessage =
String.format("Patch Set 1: Cherry Picked from commit %s.", commitToCherryPick.getName());
assertThat(messageIterator.next().message).isEqualTo(expectedMessage);
// Cherry-pick of is not set, because the source change was not provided.
assertThat(cherryPickResult.cherryPickOfChange).isNull();
assertThat(cherryPickResult.cherryPickOfPatchSet).isNull();
RevisionInfo revInfo = cherryPickResult.revisions.get(cherryPickResult.currentRevision);
assertThat(revInfo).isNotNull();
assertThat(revInfo.commit.message).isEqualTo(input.message);
}
@Test
public void cherryPickCommitToExistingChange() throws Exception {
String destBranch = "foo";
createBranch(BranchNameKey.create(project, destBranch));
PushOneCommit.Result r = createChange("refs/for/" + destBranch);
ChangeInfo existingDestChange = info(r.getChangeId());
String commitToCherryPick = createChange().getCommit().getName();
CherryPickInput input = new CherryPickInput();
input.destination = destBranch;
input.message =
String.format(
"it goes to foo branch\n\nChange-Id: Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef\n\nChange-Id: %s\n",
existingDestChange.changeId);
input.allowConflicts = true;
input.allowEmpty = true;
ChangeInfo cherryPickResult =
gApi.projects().name(project.get()).commit(commitToCherryPick).cherryPick(input).get();
// New patch-set to existing change was uploaded.
assertThat(cherryPickResult._number).isEqualTo(existingDestChange._number);
assertThat(cherryPickResult.changeId).isEqualTo(existingDestChange.changeId);
assertThat(cherryPickResult.messages).hasSize(2);
assertThat(cherryPickResult.revisions).hasSize(2);
Iterator<ChangeMessageInfo> messageIterator = cherryPickResult.messages.iterator();
assertThat(messageIterator.next().message).isEqualTo("Uploaded patch set 1.");
assertThat(messageIterator.next().message).isEqualTo("Uploaded patch set 2.");
// Cherry-pick of is not set, because the source change was not provided.
assertThat(cherryPickResult.cherryPickOfChange).isNull();
assertThat(cherryPickResult.cherryPickOfPatchSet).isNull();
RevisionInfo revInfo = cherryPickResult.revisions.get(cherryPickResult.currentRevision);
assertThat(revInfo).isNotNull();
assertThat(revInfo.commit.message).isEqualTo(input.message);
}
@Test
public void cherryPickCommitToExistingCherryPickedChange() throws Exception {
String destBranch = "foo";
createBranch(BranchNameKey.create(project, destBranch));
PushOneCommit.Result r = createChange("refs/for/" + destBranch);
ChangeInfo existingDestChange = info(r.getChangeId());
r = createChange();
ChangeInfo changeToCherryPick = info(r.getChangeId());
RevCommit commitToCherryPick = r.getCommit();
CherryPickInput input = new CherryPickInput();
input.destination = destBranch;
input.message =
String.format("it goes to foo branch\n\nChange-Id: %s\n", existingDestChange.changeId);
input.allowConflicts = true;
input.allowEmpty = true;
// Use RevisionAPI to submit initial cherryPick.
ChangeInfo cherryPickResult =
gApi.changes().id(changeToCherryPick.changeId).current().cherryPick(input).get();
assertThat(cherryPickResult.changeId).isEqualTo(existingDestChange.changeId);
// Cherry-pick was set.
assertThat(cherryPickResult.cherryPickOfChange).isEqualTo(changeToCherryPick._number);
assertThat(cherryPickResult.cherryPickOfPatchSet).isEqualTo(1);
RevisionInfo revInfo = cherryPickResult.revisions.get(cherryPickResult.currentRevision);
assertThat(revInfo).isNotNull();
assertThat(revInfo.commit.message).isEqualTo(input.message);
// Use CommitApi to update the cherryPick change.
cherryPickResult =
gApi.projects()
.name(project.get())
.commit(commitToCherryPick.getName())
.cherryPick(input)
.get();
assertThat(cherryPickResult.changeId).isEqualTo(existingDestChange.changeId);
assertThat(cherryPickResult.messages).hasSize(3);
Iterator<ChangeMessageInfo> messageIterator = cherryPickResult.messages.iterator();
assertThat(messageIterator.next().message).isEqualTo("Uploaded patch set 1.");
assertThat(messageIterator.next().message).isEqualTo("Uploaded patch set 2.");
assertThat(messageIterator.next().message).isEqualTo("Uploaded patch set 3.");
// Cherry-pick was reset to empty value.
assertThat(cherryPickResult._number).isEqualTo(existingDestChange._number);
assertThat(cherryPickResult.cherryPickOfChange).isNull();
assertThat(cherryPickResult.cherryPickOfPatchSet).isNull();
}
@Test
public void cherryPickCommitWithChangeIdToClosedChange() throws Exception {
String destBranch = "refs/heads/foo";
createBranch(BranchNameKey.create(project, destBranch));
PushOneCommit.Result r = createChange("refs/for/" + destBranch);
ChangeInfo existingDestChange = info(r.getChangeId());
String commitToCherryPick = createChange().getCommit().getName();
gApi.changes().id(existingDestChange.changeId).current().review(ReviewInput.approve());
gApi.changes().id(existingDestChange.changeId).current().submit();
CherryPickInput input = new CherryPickInput();
input.destination = destBranch;
input.message =
String.format("it goes to foo branch\n\nChange-Id: %s\n", existingDestChange.changeId);
input.allowConflicts = true;
input.allowEmpty = true;
BadRequestException thrown =
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(project.get()).commit(commitToCherryPick).cherryPick(input));
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
String.format(
"Cherry-pick with Change-Id %s could not update the existing change %d in "
+ "destination branch %s of project %s, because the change was closed (MERGED)",
existingDestChange.changeId,
existingDestChange._number,
destBranch,
project.get()));
}
@Test
public void cherryPickCommitWithSetTopic() throws Exception {
String branch = "foo";
RevCommit revCommit = createChange().getCommit();
gApi.projects().name(project.get()).branch(branch).create(new BranchInput());
CherryPickInput input = new CherryPickInput();
input.destination = branch;
input.topic = "topic";
String changeId =
gApi.projects().name(project.get()).commit(revCommit.name()).cherryPick(input).get().id;
ChangeInfo changeInfo = gApi.changes().id(changeId).get();
assertThat(changeInfo.topic).isEqualTo(input.topic);
}
@Test
public void cherryPickOnTopOfOpenChange() throws Exception {
BranchNameKey srcBranch = BranchNameKey.create(project, "master");
// Create a target branch
BranchNameKey destBranch = BranchNameKey.create(project, "foo");
createBranch(destBranch);
// Create base change on the target branch
PushOneCommit.Result r = createChange("refs/for/" + destBranch.shortName());
String base = r.getCommit().name();
int baseChangeNumber = r.getChange().getId().get();
// Create commit to cherry-pick on the source branch (no change exists for this commit)
String changeId = "Ideadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
RevCommit commitToCherryPick;
try (Repository repo = repoManager.openRepository(project);
TestRepository<Repository> tr = new TestRepository<>(repo)) {
commitToCherryPick =
tr.commit()
.parent(repo.parseCommit(repo.exactRef(srcBranch.branch()).getObjectId()))
.message(String.format("Commit to be cherry-picked\n\nChange-Id: %s\n", changeId))
.add("file.txt", "content")
.create();
tr.branch(srcBranch.branch()).update(commitToCherryPick);
}
// Perform the cherry-pick (cherry-pick on top of the base change)
CherryPickInput input = new CherryPickInput();
input.destination = destBranch.shortName();
input.base = base;
ChangeInfo cherryPickChange =
gApi.projects()
.name(project.get())
.commit(commitToCherryPick.name())
.cherryPick(input)
.get();
// Verify that a new change in destination branch was created.
assertThat(cherryPickChange._number).isGreaterThan(baseChangeNumber);
assertThat(cherryPickChange.branch).isEqualTo(destBranch.shortName());
assertThat(cherryPickChange.revisions).hasSize(1);
assertThat(cherryPickChange.messages).hasSize(1);
// Verify that the Change-Id of the cherry-picked commit is used for the cherry pick change.
assertThat(cherryPickChange.changeId).isEqualTo(changeId);
// Verify that cherry-pick-of is not set, since we cherry-picked a commit and not a change.
assertThat(cherryPickChange.cherryPickOfChange).isNull();
assertThat(cherryPickChange.cherryPickOfPatchSet).isNull();
// Verify that the message of the cherry-picked commit was used for the cherry-pick change.
RevisionInfo revInfo = cherryPickChange.revisions.get(cherryPickChange.currentRevision);
assertThat(revInfo).isNotNull();
assertThat(revInfo.commit.message).isEqualTo(commitToCherryPick.getFullMessage());
// Verify that the provided base commit is the parent commit of the cherry pick revision.
assertThat(revInfo.commit.parents).hasSize(1);
assertThat(revInfo.commit.parents.get(0).commit).isEqualTo(input.base);
// Verify that the related changes contain the base change and the cherry-pick change (no matter
// for which of these changes the related changes are retrieved).
assertThat(gApi.changes().id(cherryPickChange._number).current().related().changes)
.comparingElementsUsing(hasId())
.containsExactly(baseChangeNumber, cherryPickChange._number);
assertThat(gApi.changes().id(baseChangeNumber).current().related().changes)
.comparingElementsUsing(hasId())
.containsExactly(baseChangeNumber, cherryPickChange._number);
}
private IncludedInInfo getIncludedIn(ObjectId id) throws Exception {
return gApi.projects().name(project.get()).commit(id.name()).includedIn();
}
private static void assertPerson(GitPerson actual, TestAccount expected) {
assertThat(actual.email).isEqualTo(expected.email());
assertThat(actual.name).isEqualTo(expected.fullName());
}
private Result createAndSubmitChange(String branch) throws Exception {
Result r = createChange(branch);
approve(r.getChangeId());
gApi.changes().id(r.getChangeId()).current().submit();
return r;
}
private void createLightWeightTag(String tagName) throws Exception {
pushHead(testRepo, RefNames.REFS_TAGS + tagName, false, false);
}
private static Correspondence<RelatedChangeAndCommitInfo, Integer> hasId() {
return NullAwareCorrespondence.transforming(
relatedChangeAndCommitInfo -> relatedChangeAndCommitInfo._changeNumber, "hasId");
}
}