Merge "Support specifying the parent for a cherry-pick in the REST API"
This commit is contained in:
@@ -4860,11 +4860,13 @@ Which patchset (if any) generated this message.
|
|||||||
=== CherryPickInput
|
=== CherryPickInput
|
||||||
The `CherryPickInput` entity contains information for cherry-picking a change to a new branch.
|
The `CherryPickInput` entity contains information for cherry-picking a change to a new branch.
|
||||||
|
|
||||||
[options="header",cols="1,6"]
|
[options="header",cols="1,^1,5"]
|
||||||
|===========================
|
|===========================
|
||||||
|Field Name |Description
|
|Field Name ||Description
|
||||||
|`message` |Commit message for the cherry-picked change
|
|`message` ||Commit message for the cherry-picked change
|
||||||
|`destination` |Destination branch
|
|`destination` ||Destination branch
|
||||||
|
|`parent` |optional, defaults to 1|
|
||||||
|
Number of the parent relative to which the cherry-pick should be considered.
|
||||||
|===========================
|
|===========================
|
||||||
|
|
||||||
[[comment-info]]
|
[[comment-info]]
|
||||||
|
@@ -26,6 +26,8 @@ import static org.eclipse.jgit.lib.Constants.HEAD;
|
|||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
import com.google.gerrit.acceptance.PushOneCommit;
|
import com.google.gerrit.acceptance.PushOneCommit;
|
||||||
@@ -47,9 +49,11 @@ import com.google.gerrit.extensions.common.DiffInfo;
|
|||||||
import com.google.gerrit.extensions.common.FileInfo;
|
import com.google.gerrit.extensions.common.FileInfo;
|
||||||
import com.google.gerrit.extensions.common.MergeableInfo;
|
import com.google.gerrit.extensions.common.MergeableInfo;
|
||||||
import com.google.gerrit.extensions.common.RevisionInfo;
|
import com.google.gerrit.extensions.common.RevisionInfo;
|
||||||
|
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||||
import com.google.gerrit.extensions.restapi.BinaryResult;
|
import com.google.gerrit.extensions.restapi.BinaryResult;
|
||||||
import com.google.gerrit.extensions.restapi.ETagView;
|
import com.google.gerrit.extensions.restapi.ETagView;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
|
import com.google.gerrit.reviewdb.client.Branch;
|
||||||
import com.google.gerrit.server.change.GetRevisionActions;
|
import com.google.gerrit.server.change.GetRevisionActions;
|
||||||
import com.google.gerrit.server.change.RevisionResource;
|
import com.google.gerrit.server.change.RevisionResource;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
@@ -356,6 +360,111 @@ public class RevisionIT extends AbstractDaemonTest {
|
|||||||
.isEqualTo("a");
|
.isEqualTo("a");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cherryPickMergeRelativeToDefaultParent() throws Exception {
|
||||||
|
String parent1FileName = "a.txt";
|
||||||
|
String parent2FileName = "b.txt";
|
||||||
|
PushOneCommit.Result mergeChangeResult =
|
||||||
|
createCherryPickableMerge(parent1FileName, parent2FileName);
|
||||||
|
|
||||||
|
String cherryPickBranchName = "branch_for_cherry_pick";
|
||||||
|
createBranch(new Branch.NameKey(project, cherryPickBranchName));
|
||||||
|
|
||||||
|
CherryPickInput cherryPickInput = new CherryPickInput();
|
||||||
|
cherryPickInput.destination = cherryPickBranchName;
|
||||||
|
cherryPickInput.message = "Cherry-pick a merge commit to another branch";
|
||||||
|
|
||||||
|
ChangeInfo cherryPickedChangeInfo = gApi.changes()
|
||||||
|
.id(mergeChangeResult.getChangeId())
|
||||||
|
.current()
|
||||||
|
.cherryPick(cherryPickInput)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
Map<String, FileInfo> cherryPickedFilesByName =
|
||||||
|
cherryPickedChangeInfo.revisions
|
||||||
|
.get(cherryPickedChangeInfo.currentRevision)
|
||||||
|
.files;
|
||||||
|
assertThat(cherryPickedFilesByName).containsKey(parent2FileName);
|
||||||
|
assertThat(cherryPickedFilesByName).doesNotContainKey(parent1FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cherryPickMergeRelativeToSpecificParent() throws Exception {
|
||||||
|
String parent1FileName = "a.txt";
|
||||||
|
String parent2FileName = "b.txt";
|
||||||
|
PushOneCommit.Result mergeChangeResult =
|
||||||
|
createCherryPickableMerge(parent1FileName, parent2FileName);
|
||||||
|
|
||||||
|
String cherryPickBranchName = "branch_for_cherry_pick";
|
||||||
|
createBranch(new Branch.NameKey(project, cherryPickBranchName));
|
||||||
|
|
||||||
|
CherryPickInput cherryPickInput = new CherryPickInput();
|
||||||
|
cherryPickInput.destination = cherryPickBranchName;
|
||||||
|
cherryPickInput.message = "Cherry-pick a merge commit to another branch";
|
||||||
|
cherryPickInput.parent = 2;
|
||||||
|
|
||||||
|
ChangeInfo cherryPickedChangeInfo = gApi.changes()
|
||||||
|
.id(mergeChangeResult.getChangeId())
|
||||||
|
.current()
|
||||||
|
.cherryPick(cherryPickInput)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
Map<String, FileInfo> cherryPickedFilesByName =
|
||||||
|
cherryPickedChangeInfo.revisions
|
||||||
|
.get(cherryPickedChangeInfo.currentRevision)
|
||||||
|
.files;
|
||||||
|
assertThat(cherryPickedFilesByName).containsKey(parent1FileName);
|
||||||
|
assertThat(cherryPickedFilesByName).doesNotContainKey(parent2FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cherryPickMergeUsingInvalidParent() throws Exception {
|
||||||
|
String parent1FileName = "a.txt";
|
||||||
|
String parent2FileName = "b.txt";
|
||||||
|
PushOneCommit.Result mergeChangeResult =
|
||||||
|
createCherryPickableMerge(parent1FileName, parent2FileName);
|
||||||
|
|
||||||
|
String cherryPickBranchName = "branch_for_cherry_pick";
|
||||||
|
createBranch(new Branch.NameKey(project, cherryPickBranchName));
|
||||||
|
|
||||||
|
CherryPickInput cherryPickInput = new CherryPickInput();
|
||||||
|
cherryPickInput.destination = cherryPickBranchName;
|
||||||
|
cherryPickInput.message = "Cherry-pick a merge commit to another branch";
|
||||||
|
cherryPickInput.parent = 0;
|
||||||
|
|
||||||
|
exception.expect(BadRequestException.class);
|
||||||
|
exception.expectMessage("Cherry Pick: Parent 0 does not exist. Please"
|
||||||
|
+ " specify a parent in range [1, 2].");
|
||||||
|
gApi.changes()
|
||||||
|
.id(mergeChangeResult.getChangeId())
|
||||||
|
.current()
|
||||||
|
.cherryPick(cherryPickInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cherryPickMergeUsingNonExistentParent() throws Exception {
|
||||||
|
String parent1FileName = "a.txt";
|
||||||
|
String parent2FileName = "b.txt";
|
||||||
|
PushOneCommit.Result mergeChangeResult =
|
||||||
|
createCherryPickableMerge(parent1FileName, parent2FileName);
|
||||||
|
|
||||||
|
String cherryPickBranchName = "branch_for_cherry_pick";
|
||||||
|
createBranch(new Branch.NameKey(project, cherryPickBranchName));
|
||||||
|
|
||||||
|
CherryPickInput cherryPickInput = new CherryPickInput();
|
||||||
|
cherryPickInput.destination = cherryPickBranchName;
|
||||||
|
cherryPickInput.message = "Cherry-pick a merge commit to another branch";
|
||||||
|
cherryPickInput.parent = 3;
|
||||||
|
|
||||||
|
exception.expect(BadRequestException.class);
|
||||||
|
exception.expectMessage("Cherry Pick: Parent 3 does not exist. Please"
|
||||||
|
+ " specify a parent in range [1, 2].");
|
||||||
|
gApi.changes()
|
||||||
|
.id(mergeChangeResult.getChangeId())
|
||||||
|
.current()
|
||||||
|
.cherryPick(cherryPickInput);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void canRebase() throws Exception {
|
public void canRebase() throws Exception {
|
||||||
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
|
PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
|
||||||
@@ -806,4 +915,35 @@ public class RevisionIT extends AbstractDaemonTest {
|
|||||||
assertDiffForNewFile(diff, pushResult.getCommit(), path,
|
assertDiffForNewFile(diff, pushResult.getCommit(), path,
|
||||||
expectedContentSideB);
|
expectedContentSideB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PushOneCommit.Result createCherryPickableMerge(String parent1FileName,
|
||||||
|
String parent2FileName) throws Exception {
|
||||||
|
RevCommit initialCommit = getHead(repo());
|
||||||
|
|
||||||
|
String branchAName = "branchA";
|
||||||
|
createBranch(new Branch.NameKey(project, branchAName));
|
||||||
|
String branchBName = "branchB";
|
||||||
|
createBranch(new Branch.NameKey(project, branchBName));
|
||||||
|
|
||||||
|
PushOneCommit.Result changeAResult = pushFactory
|
||||||
|
.create(db, admin.getIdent(), testRepo, "change a",
|
||||||
|
parent1FileName, "Content of a")
|
||||||
|
.to("refs/for/" + branchAName);
|
||||||
|
|
||||||
|
testRepo.reset(initialCommit);
|
||||||
|
PushOneCommit.Result changeBResult = pushFactory
|
||||||
|
.create(db, admin.getIdent(), testRepo, "change b",
|
||||||
|
parent2FileName, "Content of b")
|
||||||
|
.to("refs/for/" + branchBName);
|
||||||
|
|
||||||
|
PushOneCommit pushableMergeCommit = pushFactory.create(db, admin.getIdent(),
|
||||||
|
testRepo, "merge", ImmutableMap.of(parent1FileName, "Content of a",
|
||||||
|
parent2FileName, "Content of b"));
|
||||||
|
pushableMergeCommit.setParents(ImmutableList.of(changeAResult.getCommit(),
|
||||||
|
changeBResult.getCommit()));
|
||||||
|
PushOneCommit.Result mergeChangeResult =
|
||||||
|
pushableMergeCommit.to("refs/for/" + branchAName);
|
||||||
|
mergeChangeResult.assertOkStatus();
|
||||||
|
return mergeChangeResult;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,4 +17,5 @@ package com.google.gerrit.extensions.api.changes;
|
|||||||
public class CherryPickInput {
|
public class CherryPickInput {
|
||||||
public String message;
|
public String message;
|
||||||
public String destination;
|
public String destination;
|
||||||
|
public Integer parent;
|
||||||
}
|
}
|
||||||
|
@@ -60,10 +60,12 @@ public class CherryPick implements RestModifyView<RevisionResource, CherryPickIn
|
|||||||
public ChangeInfo apply(RevisionResource revision, CherryPickInput input)
|
public ChangeInfo apply(RevisionResource revision, CherryPickInput input)
|
||||||
throws OrmException, IOException, UpdateException, RestApiException {
|
throws OrmException, IOException, UpdateException, RestApiException {
|
||||||
final ChangeControl control = revision.getControl();
|
final ChangeControl control = revision.getControl();
|
||||||
|
int parent = input.parent == null ? 1 : input.parent;
|
||||||
|
|
||||||
if (input.message == null || input.message.trim().isEmpty()) {
|
if (input.message == null || input.message.trim().isEmpty()) {
|
||||||
throw new BadRequestException("message must be non-empty");
|
throw new BadRequestException("message must be non-empty");
|
||||||
} else if (input.destination == null || input.destination.trim().isEmpty()) {
|
} else if (input.destination == null
|
||||||
|
|| input.destination.trim().isEmpty()) {
|
||||||
throw new BadRequestException("destination must be non-empty");
|
throw new BadRequestException("destination must be non-empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +93,7 @@ public class CherryPick implements RestModifyView<RevisionResource, CherryPickIn
|
|||||||
Change.Id cherryPickedChangeId =
|
Change.Id cherryPickedChangeId =
|
||||||
cherryPickChange.cherryPick(revision.getChange(),
|
cherryPickChange.cherryPick(revision.getChange(),
|
||||||
revision.getPatchSet(), input.message, refName,
|
revision.getPatchSet(), input.message, refName,
|
||||||
refControl);
|
refControl, parent);
|
||||||
return json.create(ChangeJson.NO_OPTIONS).format(revision.getProject(),
|
return json.create(ChangeJson.NO_OPTIONS).format(revision.getProject(),
|
||||||
cherryPickedChangeId);
|
cherryPickedChangeId);
|
||||||
} catch (InvalidChangeOperationException e) {
|
} catch (InvalidChangeOperationException e) {
|
||||||
|
@@ -114,8 +114,8 @@ public class CherryPickChange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Change.Id cherryPick(Change change, PatchSet patch,
|
public Change.Id cherryPick(Change change, PatchSet patch,
|
||||||
final String message, final String ref,
|
final String message, final String ref, final RefControl refControl,
|
||||||
final RefControl refControl) throws NoSuchChangeException,
|
int parent) throws NoSuchChangeException,
|
||||||
OrmException, MissingObjectException,
|
OrmException, MissingObjectException,
|
||||||
IncorrectObjectTypeException, IOException,
|
IncorrectObjectTypeException, IOException,
|
||||||
InvalidChangeOperationException, IntegrationException, UpdateException,
|
InvalidChangeOperationException, IntegrationException, UpdateException,
|
||||||
@@ -147,6 +147,13 @@ public class CherryPickChange {
|
|||||||
CodeReviewCommit commitToCherryPick =
|
CodeReviewCommit commitToCherryPick =
|
||||||
revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
|
revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
|
||||||
|
|
||||||
|
if (parent <= 0 || parent > commitToCherryPick.getParentCount()) {
|
||||||
|
throw new InvalidChangeOperationException(String.format(
|
||||||
|
"Cherry Pick: Parent %s does not exist. Please specify a parent in"
|
||||||
|
+ " range [1, %s].",
|
||||||
|
parent, commitToCherryPick.getParentCount()));
|
||||||
|
}
|
||||||
|
|
||||||
Timestamp now = TimeUtil.nowTs();
|
Timestamp now = TimeUtil.nowTs();
|
||||||
PersonIdent committerIdent =
|
PersonIdent committerIdent =
|
||||||
identifiedUser.newCommitterIdent(now, serverTimeZone);
|
identifiedUser.newCommitterIdent(now, serverTimeZone);
|
||||||
@@ -160,10 +167,12 @@ public class CherryPickChange {
|
|||||||
|
|
||||||
CodeReviewCommit cherryPickCommit;
|
CodeReviewCommit cherryPickCommit;
|
||||||
try {
|
try {
|
||||||
ProjectState projectState = refControl.getProjectControl().getProjectState();
|
ProjectState projectState = refControl.getProjectControl()
|
||||||
cherryPickCommit =
|
.getProjectState();
|
||||||
mergeUtilFactory.create(projectState).createCherryPickFromCommit(git, oi, mergeTip,
|
cherryPickCommit = mergeUtilFactory.create(projectState)
|
||||||
commitToCherryPick, committerIdent, commitMessage, revWalk);
|
.createCherryPickFromCommit(git, oi, mergeTip,
|
||||||
|
commitToCherryPick, committerIdent, commitMessage, revWalk,
|
||||||
|
parent - 1);
|
||||||
|
|
||||||
Change.Key changeKey;
|
Change.Key changeKey;
|
||||||
final List<String> idList = cherryPickCommit.getFooterLines(
|
final List<String> idList = cherryPickCommit.getFooterLines(
|
||||||
|
@@ -184,13 +184,13 @@ public class MergeUtil {
|
|||||||
public CodeReviewCommit createCherryPickFromCommit(Repository repo,
|
public CodeReviewCommit createCherryPickFromCommit(Repository repo,
|
||||||
ObjectInserter inserter, RevCommit mergeTip, RevCommit originalCommit,
|
ObjectInserter inserter, RevCommit mergeTip, RevCommit originalCommit,
|
||||||
PersonIdent cherryPickCommitterIdent, String commitMsg,
|
PersonIdent cherryPickCommitterIdent, String commitMsg,
|
||||||
CodeReviewRevWalk rw)
|
CodeReviewRevWalk rw, int parentIndex)
|
||||||
throws MissingObjectException, IncorrectObjectTypeException, IOException,
|
throws MissingObjectException, IncorrectObjectTypeException, IOException,
|
||||||
MergeIdenticalTreeException, MergeConflictException {
|
MergeIdenticalTreeException, MergeConflictException {
|
||||||
|
|
||||||
final ThreeWayMerger m = newThreeWayMerger(repo, inserter);
|
final ThreeWayMerger m = newThreeWayMerger(repo, inserter);
|
||||||
|
|
||||||
m.setBase(originalCommit.getParent(0));
|
m.setBase(originalCommit.getParent(parentIndex));
|
||||||
if (m.merge(mergeTip, originalCommit)) {
|
if (m.merge(mergeTip, originalCommit)) {
|
||||||
ObjectId tree = m.getResultTreeId();
|
ObjectId tree = m.getResultTreeId();
|
||||||
if (tree.equals(mergeTip.getTree())) {
|
if (tree.equals(mergeTip.getTree())) {
|
||||||
|
@@ -107,7 +107,7 @@ public class CherryPick extends SubmitStrategy {
|
|||||||
try {
|
try {
|
||||||
newCommit = args.mergeUtil.createCherryPickFromCommit(
|
newCommit = args.mergeUtil.createCherryPickFromCommit(
|
||||||
args.repo, args.inserter, args.mergeTip.getCurrentTip(), toMerge,
|
args.repo, args.inserter, args.mergeTip.getCurrentTip(), toMerge,
|
||||||
committer, cherryPickCmtMsg, args.rw);
|
committer, cherryPickCmtMsg, args.rw, 0);
|
||||||
} catch (MergeConflictException mce) {
|
} catch (MergeConflictException mce) {
|
||||||
// Keep going in the case of a single merge failure; the goal is to
|
// Keep going in the case of a single merge failure; the goal is to
|
||||||
// cherry-pick as many commits as possible.
|
// cherry-pick as many commits as possible.
|
||||||
|
Reference in New Issue
Block a user