Support cherry picking a commit without specific change
The current cherry pick REST endpoint only allows cherry picking a
revision. This change allows cherry picking a commit which may not be
associated with a change.
Change-Id: Ic6b178dfe267e0e5cc9333470ba792de3c8111d7
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 72c6a39..17b0192 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -2060,6 +2060,60 @@
Ly8gQ29weXJpZ2h0IChDKSAyMDEwIFRoZSBBbmRyb2lkIE9wZW4gU291cmNlIFByb2plY...
----
+
+[[cherry-pick-commit]]
+=== Cherry Pick Commit
+--
+'POST /projects/link:#project-name[\{project-name\}]/commits/link:#commit-id[\{commit-id\}]/cherrypick'
+--
+
+Cherry-picks a commit of a project to a destination branch.
+
+The destination branch must be provided in the request body inside a
+link:rest-api-changes.html#cherrypick-input[CherryPickInput] entity.
+If the commit message is not set, the commit message of the source
+commit will be used.
+
+.Request
+----
+ POST /projects/work%2Fmy-project/commits/a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96/cherrypick HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "message" : "Implementing Feature X",
+ "destination" : "release-branch"
+ }
+----
+
+As response a link:rest-api-changes.html#change-info[ChangeInfo] entity is returned that
+describes the resulting cherry-picked change.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9941",
+ "project": "myProject",
+ "branch": "release-branch",
+ "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9941",
+ "subject": "Implementing Feature X",
+ "status": "NEW",
+ "created": "2013-02-01 09:59:32.126000000",
+ "updated": "2013-02-21 11:16:36.775000000",
+ "mergeable": true,
+ "insertions": 12,
+ "deletions": 11,
+ "_number": 3965,
+ "owner": {
+ "name": "John Doe"
+ }
+ }
+----
+
[[dashboard-endpoints]]
== Dashboard Endpoints
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index f79b5fa..146b5ca 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -23,6 +23,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.acceptance.RestResponse;
@@ -30,11 +31,15 @@
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
+import com.google.gerrit.extensions.common.ChangeMessageInfo;
+import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.MergeInput;
+import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -45,10 +50,13 @@
import com.google.gerrit.testutil.ConfigSuite;
import com.google.gerrit.testutil.FakeEmailSender.Message;
import com.google.gerrit.testutil.TestTimeUtil;
+import java.util.Iterator;
import java.util.List;
+import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -278,6 +286,79 @@
assertCreateSucceeds(in);
}
+ @Test
+ public void cherryPickCommitWithoutChangeId() throws Exception {
+ // This test is a little superfluous, since the current cherry-pick code ignores
+ // the commit message of the to-be-cherry-picked change, using the one in
+ // CherryPickInput instead.
+ CherryPickInput input = new CherryPickInput();
+ input.destination = "foo";
+ input.message = "it goes to foo branch";
+ gApi.projects().name(project.get()).branch(input.destination).create(new BranchInput());
+
+ RevCommit revCommit = createNewCommitWithoutChangeId();
+ ChangeInfo changeInfo =
+ gApi.projects().name(project.get()).commit(revCommit.getName()).cherryPick(input).get();
+
+ assertThat(changeInfo.messages).hasSize(1);
+ Iterator<ChangeMessageInfo> messageIterator = changeInfo.messages.iterator();
+ String expectedMessage =
+ String.format("Patch Set 1: Cherry Picked from commit %s.", revCommit.getName());
+ assertThat(messageIterator.next().message).isEqualTo(expectedMessage);
+
+ RevisionInfo revInfo = changeInfo.revisions.get(changeInfo.currentRevision);
+ assertThat(revInfo).isNotNull();
+ CommitInfo commitInfo = revInfo.commit;
+ assertThat(commitInfo.message)
+ .isEqualTo(input.message + "\n\nChange-Id: " + changeInfo.changeId + "\n");
+ }
+
+ @Test
+ public void cherryPickCommitWithChangeId() throws Exception {
+ CherryPickInput input = new CherryPickInput();
+ input.destination = "foo";
+
+ RevCommit revCommit = createChange().getCommit();
+ List<String> footers = revCommit.getFooterLines("Change-Id");
+ assertThat(footers).hasSize(1);
+ String changeId = footers.get(0);
+
+ input.message = "it goes to foo branch\n\nChange-Id: " + changeId;
+ gApi.projects().name(project.get()).branch(input.destination).create(new BranchInput());
+
+ ChangeInfo changeInfo =
+ gApi.projects().name(project.get()).commit(revCommit.getName()).cherryPick(input).get();
+
+ assertThat(changeInfo.messages).hasSize(1);
+ Iterator<ChangeMessageInfo> messageIterator = changeInfo.messages.iterator();
+ String expectedMessage =
+ String.format("Patch Set 1: Cherry Picked from commit %s.", revCommit.getName());
+ assertThat(messageIterator.next().message).isEqualTo(expectedMessage);
+
+ RevisionInfo revInfo = changeInfo.revisions.get(changeInfo.currentRevision);
+ assertThat(revInfo).isNotNull();
+ assertThat(revInfo.commit.message).isEqualTo(input.message + "\n");
+ }
+
+ private RevCommit createNewCommitWithoutChangeId() throws Exception {
+ try (Repository repo = repoManager.openRepository(project);
+ RevWalk walk = new RevWalk(repo)) {
+ Ref ref = repo.exactRef("refs/heads/master");
+ RevCommit tip = null;
+ if (ref != null) {
+ tip = walk.parseCommit(ref.getObjectId());
+ }
+ TestRepository<?> testSrcRepo = new TestRepository<>(repo);
+ TestRepository<?>.BranchBuilder builder = testSrcRepo.branch("refs/heads/master");
+ RevCommit revCommit =
+ tip == null
+ ? builder.commit().message("commit 1").add("a.txt", "content").create()
+ : builder.commit().parent(tip).message("commit 1").add("a.txt", "content").create();
+ assertThat(GitUtil.getChangeId(testSrcRepo, revCommit).isPresent()).isFalse();
+ return revCommit;
+ }
+ }
+
private ChangeInput newChangeInput(ChangeStatus status) {
ChangeInput in = new ChangeInput();
in.project = project.get();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/CommitApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/CommitApi.java
new file mode 100644
index 0000000..85bd952
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/CommitApi.java
@@ -0,0 +1,33 @@
+// 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.extensions.api.projects;
+
+import com.google.gerrit.extensions.api.changes.ChangeApi;
+import com.google.gerrit.extensions.api.changes.CherryPickInput;
+import com.google.gerrit.extensions.restapi.NotImplementedException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+public interface CommitApi {
+
+ ChangeApi cherryPick(CherryPickInput input) throws RestApiException;
+
+ /** A default implementation for source compatibility when adding new methods to the interface. */
+ class NotImplemented implements CommitApi {
+ @Override
+ public ChangeApi cherryPick(CherryPickInput input) {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index dc2f899..34b298e 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -125,6 +125,14 @@
TagApi tag(String ref) throws RestApiException;
/**
+ * Lookup a commit by its {@Code ObjectId} string.
+ *
+ * @param commit the {@Code ObjectId} string.
+ * @return API for accessing the commit.
+ */
+ CommitApi commit(String commit) throws RestApiException;
+
+ /**
* A default implementation which allows source compatibility when adding new methods to the
* interface.
*/
@@ -218,5 +226,10 @@
public void deleteTags(DeleteTagsInput in) {
throw new NotImplementedException();
}
+
+ @Override
+ public CommitApi commit(String commit) {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/CommitApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/CommitApiImpl.java
new file mode 100644
index 0000000..9e17498
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/CommitApiImpl.java
@@ -0,0 +1,55 @@
+// 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.server.api.projects;
+
+import com.google.gerrit.extensions.api.changes.ChangeApi;
+import com.google.gerrit.extensions.api.changes.Changes;
+import com.google.gerrit.extensions.api.changes.CherryPickInput;
+import com.google.gerrit.extensions.api.projects.CommitApi;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.change.CherryPickCommit;
+import com.google.gerrit.server.project.CommitResource;
+import com.google.gerrit.server.update.UpdateException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
+
+public class CommitApiImpl implements CommitApi {
+ public interface Factory {
+ CommitApiImpl create(CommitResource r);
+ }
+
+ private final Changes changes;
+ private final CherryPickCommit cherryPickCommit;
+ private final CommitResource commitResource;
+
+ @Inject
+ CommitApiImpl(
+ Changes changes, CherryPickCommit cherryPickCommit, @Assisted CommitResource commitResource) {
+ this.changes = changes;
+ this.cherryPickCommit = cherryPickCommit;
+ this.commitResource = commitResource;
+ }
+
+ @Override
+ public ChangeApi cherryPick(CherryPickInput input) throws RestApiException {
+ try {
+ return changes.id(cherryPickCommit.apply(commitResource, input)._number);
+ } catch (OrmException | IOException | UpdateException e) {
+ throw new RestApiException("Cannot cherry pick", e);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/Module.java
index 975e6c1..a4fe39b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/Module.java
@@ -26,5 +26,6 @@
factory(TagApiImpl.Factory.class);
factory(ProjectApiImpl.Factory.class);
factory(ChildProjectApiImpl.Factory.class);
+ factory(CommitApiImpl.Factory.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index e29d633..025b62a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -21,6 +21,7 @@
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.ChildProjectApi;
+import com.google.gerrit.extensions.api.projects.CommitApi;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.api.projects.DeleteBranchesInput;
@@ -39,6 +40,7 @@
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.project.ChildProjectsCollection;
+import com.google.gerrit.server.project.CommitsCollection;
import com.google.gerrit.server.project.CreateProject;
import com.google.gerrit.server.project.DeleteBranches;
import com.google.gerrit.server.project.DeleteTags;
@@ -89,6 +91,8 @@
private final ListTags listTags;
private final DeleteBranches deleteBranches;
private final DeleteTags deleteTags;
+ private final CommitsCollection commitsCollection;
+ private final CommitApiImpl.Factory commitApi;
@AssistedInject
ProjectApiImpl(
@@ -111,6 +115,8 @@
ListTags listTags,
DeleteBranches deleteBranches,
DeleteTags deleteTags,
+ CommitsCollection commitsCollection,
+ CommitApiImpl.Factory commitApi,
@Assisted ProjectResource project) {
this(
user,
@@ -133,6 +139,8 @@
deleteBranches,
deleteTags,
project,
+ commitsCollection,
+ commitApi,
null);
}
@@ -157,6 +165,8 @@
ListTags listTags,
DeleteBranches deleteBranches,
DeleteTags deleteTags,
+ CommitsCollection commitsCollection,
+ CommitApiImpl.Factory commitApi,
@Assisted String name) {
this(
user,
@@ -179,6 +189,8 @@
deleteBranches,
deleteTags,
null,
+ commitsCollection,
+ commitApi,
name);
}
@@ -203,6 +215,8 @@
DeleteBranches deleteBranches,
DeleteTags deleteTags,
ProjectResource project,
+ CommitsCollection commitsCollection,
+ CommitApiImpl.Factory commitApi,
String name) {
this.user = user;
this.createProjectFactory = createProjectFactory;
@@ -225,6 +239,8 @@
this.listTags = listTags;
this.deleteBranches = deleteBranches;
this.deleteTags = deleteTags;
+ this.commitsCollection = commitsCollection;
+ this.commitApi = commitApi;
}
@Override
@@ -393,6 +409,15 @@
}
}
+ @Override
+ public CommitApi commit(String commit) throws RestApiException {
+ try {
+ return commitApi.create(commitsCollection.parse(checkExists(), IdString.fromDecoded(commit)));
+ } catch (IOException e) {
+ throw new RestApiException("Cannot parse commit", e);
+ }
+ }
+
private ProjectResource checkExists() throws ResourceNotFoundException {
if (project == null) {
throw new ResourceNotFoundException(name);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
index 1e5aa45..826c8a0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
@@ -16,6 +16,7 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.FooterConstants;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.restapi.MergeConflictException;
@@ -42,7 +43,6 @@
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.InvalidChangeOperationException;
-import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.query.change.ChangeData;
@@ -59,9 +59,6 @@
import java.sql.Timestamp;
import java.util.List;
import java.util.TimeZone;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
@@ -114,23 +111,42 @@
}
public Change.Id cherryPick(
- Change change,
- PatchSet patch,
- final String message,
- final String ref,
- final RefControl refControl,
- int parent)
- throws NoSuchChangeException, OrmException, MissingObjectException,
- IncorrectObjectTypeException, IOException, InvalidChangeOperationException,
- IntegrationException, UpdateException, RestApiException {
+ Change change, PatchSet patch, String message, String ref, RefControl refControl, int parent)
+ throws OrmException, IOException, InvalidChangeOperationException, IntegrationException,
+ UpdateException, RestApiException {
+ return cherryPick(
+ change.getId(),
+ patch.getId(),
+ change.getDest(),
+ change.getTopic(),
+ change.getProject(),
+ ObjectId.fromString(patch.getRevision().get()),
+ message,
+ ref,
+ refControl,
+ parent);
+ }
- if (Strings.isNullOrEmpty(ref)) {
+ public Change.Id cherryPick(
+ @Nullable Change.Id sourceChangeId,
+ @Nullable PatchSet.Id sourcePatchId,
+ @Nullable Branch.NameKey sourceBranch,
+ @Nullable String sourceChangeTopic,
+ Project.NameKey project,
+ ObjectId sourceCommit,
+ String message,
+ String targetRef,
+ RefControl targetRefControl,
+ int parent)
+ throws OrmException, IOException, InvalidChangeOperationException, IntegrationException,
+ UpdateException, RestApiException {
+
+ if (Strings.isNullOrEmpty(targetRef)) {
throw new InvalidChangeOperationException(
"Cherry Pick: Destination branch cannot be null or empty");
}
- Project.NameKey project = change.getProject();
- String destinationBranch = RefNames.shortName(ref);
+ String destinationBranch = RefNames.shortName(targetRef);
IdentifiedUser identifiedUser = user.get();
try (Repository git = gitManager.openRepository(project);
// This inserter and revwalk *must* be passed to any BatchUpdates
@@ -138,7 +154,7 @@
// before patch sets are updated.
ObjectInserter oi = git.newObjectInserter();
CodeReviewRevWalk revWalk = CodeReviewCommit.newRevWalk(oi.newReader())) {
- Ref destRef = git.getRefDatabase().exactRef(ref);
+ Ref destRef = git.getRefDatabase().exactRef(targetRef);
if (destRef == null) {
throw new InvalidChangeOperationException(
String.format("Branch %s does not exist.", destinationBranch));
@@ -146,8 +162,7 @@
CodeReviewCommit mergeTip = revWalk.parseCommit(destRef.getObjectId());
- CodeReviewCommit commitToCherryPick =
- revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
+ CodeReviewCommit commitToCherryPick = revWalk.parseCommit(sourceCommit);
if (parent <= 0 || parent > commitToCherryPick.getParentCount()) {
throw new InvalidChangeOperationException(
@@ -171,7 +186,7 @@
CodeReviewCommit cherryPickCommit;
try {
- ProjectState projectState = refControl.getProjectControl().getProjectState();
+ ProjectState projectState = targetRefControl.getProjectControl().getProjectState();
cherryPickCommit =
mergeUtilFactory
.create(projectState)
@@ -195,7 +210,7 @@
changeKey = new Change.Key("I" + computedChangeId.name());
}
- Branch.NameKey newDest = new Branch.NameKey(change.getProject(), destRef.getName());
+ Branch.NameKey newDest = new Branch.NameKey(project, destRef.getName());
List<ChangeData> destChanges =
queryProvider.get().setLimit(2).byBranchKey(newDest, changeKey);
if (destChanges.size() > 1) {
@@ -205,32 +220,37 @@
+ " reside on the same branch. "
+ "Cannot create a new patch set.");
}
- try (BatchUpdate bu =
- batchUpdateFactory.create(
- db.get(), change.getDest().getParentKey(), identifiedUser, now)) {
+ try (BatchUpdate bu = batchUpdateFactory.create(db.get(), project, identifiedUser, now)) {
bu.setRepository(git, revWalk, oi);
Change.Id result;
if (destChanges.size() == 1) {
// The change key exists on the destination branch. The cherry pick
// will be added as a new patch set.
ChangeControl destCtl =
- refControl.getProjectControl().controlFor(destChanges.get(0).notes());
+ targetRefControl.getProjectControl().controlFor(destChanges.get(0).notes());
result = insertPatchSet(bu, git, destCtl, cherryPickCommit);
} else {
// Change key not found on destination branch. We can create a new
// change.
String newTopic = null;
- if (!Strings.isNullOrEmpty(change.getTopic())) {
- newTopic = change.getTopic() + "-" + newDest.getShortName();
+ if (!Strings.isNullOrEmpty(sourceChangeTopic)) {
+ newTopic = sourceChangeTopic + "-" + newDest.getShortName();
}
result =
createNewChange(
- bu, cherryPickCommit, refControl.getRefName(), newTopic, change.getDest());
+ bu,
+ cherryPickCommit,
+ targetRefControl.getRefName(),
+ newTopic,
+ sourceBranch,
+ sourceCommit);
- bu.addOp(
- change.getId(),
- new AddMessageToSourceChangeOp(
- changeMessagesUtil, patch.getId(), destinationBranch, cherryPickCommit));
+ if (sourceChangeId != null && sourcePatchId != null) {
+ bu.addOp(
+ sourceChangeId,
+ new AddMessageToSourceChangeOp(
+ changeMessagesUtil, sourcePatchId, destinationBranch, cherryPickCommit));
+ }
}
bu.execute();
return result;
@@ -238,8 +258,6 @@
} catch (MergeIdenticalTreeException | MergeConflictException e) {
throw new IntegrationException("Cherry pick failed: " + e.getMessage());
}
- } catch (RepositoryNotFoundException e) {
- throw new NoSuchChangeException(change.getId(), e);
}
}
@@ -266,7 +284,8 @@
CodeReviewCommit cherryPickCommit,
String refName,
String topic,
- Branch.NameKey sourceBranch)
+ Branch.NameKey sourceBranch,
+ ObjectId sourceCommit)
throws OrmException {
Change.Id changeId = new Change.Id(seq.nextChangeId());
ChangeInserter ins =
@@ -275,7 +294,7 @@
.setValidatePolicy(CommitValidators.Policy.GERRIT)
.setTopic(topic);
- ins.setMessage(messageForDestinationChange(ins.getPatchSetId(), sourceBranch));
+ ins.setMessage(messageForDestinationChange(ins.getPatchSetId(), sourceBranch, sourceCommit));
bu.insertChange(ins);
return changeId;
}
@@ -317,12 +336,16 @@
}
}
- private String messageForDestinationChange(PatchSet.Id patchSetId, Branch.NameKey sourceBranch) {
- return new StringBuilder("Patch Set ")
- .append(patchSetId.get())
- .append(": Cherry Picked from branch ")
- .append(sourceBranch.getShortName())
- .append(".")
- .toString();
+ private String messageForDestinationChange(
+ PatchSet.Id patchSetId, Branch.NameKey sourceBranch, ObjectId sourceCommit) {
+ StringBuilder stringBuilder = new StringBuilder("Patch Set ").append(patchSetId.get());
+
+ if (sourceBranch != null) {
+ stringBuilder.append(": Cherry Picked from branch ").append(sourceBranch.getShortName());
+ } else {
+ stringBuilder.append(": Cherry Picked from commit ").append(sourceCommit.getName());
+ }
+
+ return stringBuilder.append(".").toString();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickCommit.java
new file mode 100644
index 0000000..cf99d37
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickCommit.java
@@ -0,0 +1,98 @@
+// 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.server.change;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.data.Capable;
+import com.google.gerrit.extensions.api.changes.CherryPickInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.git.IntegrationException;
+import com.google.gerrit.server.project.CommitResource;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.update.UpdateException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+@Singleton
+public class CherryPickCommit implements RestModifyView<CommitResource, CherryPickInput> {
+
+ private final CherryPickChange cherryPickChange;
+ private final ChangeJson.Factory json;
+
+ @Inject
+ CherryPickCommit(CherryPickChange cherryPickChange, ChangeJson.Factory json) {
+ this.cherryPickChange = cherryPickChange;
+ this.json = json;
+ }
+
+ @Override
+ public ChangeInfo apply(CommitResource rsrc, CherryPickInput input)
+ throws OrmException, IOException, UpdateException, RestApiException {
+ String message = Strings.nullToEmpty(input.message).trim();
+ String destination = Strings.nullToEmpty(input.destination).trim();
+ int parent = input.parent == null ? 1 : input.parent;
+
+ if (destination.isEmpty()) {
+ throw new BadRequestException("destination must be non-empty");
+ }
+
+ ProjectControl projectControl = rsrc.getProject();
+ Capable capable = projectControl.canPushToAtLeastOneRef();
+ if (capable != Capable.OK) {
+ throw new AuthException(capable.getMessage());
+ }
+
+ RevCommit commit = rsrc.getCommit();
+ String refName = RefNames.fullName(destination);
+ RefControl refControl = projectControl.controlForRef(refName);
+ if (!refControl.canUpload()) {
+ throw new AuthException("Not allowed to cherry pick " + commit + " to " + destination);
+ }
+
+ Project.NameKey project = projectControl.getProject().getNameKey();
+ try {
+ Change.Id cherryPickedChangeId =
+ cherryPickChange.cherryPick(
+ null,
+ null,
+ null,
+ null,
+ project,
+ commit,
+ message.isEmpty() ? commit.getFullMessage() : message,
+ refName,
+ refControl,
+ parent);
+ return json.noOptions().format(project, cherryPickedChangeId);
+ } catch (InvalidChangeOperationException e) {
+ throw new BadRequestException(e.getMessage());
+ } catch (IntegrationException e) {
+ throw new ResourceConflictException(e.getMessage());
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
index d7af195..11f3805 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
@@ -24,6 +24,7 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
+import com.google.gerrit.server.change.CherryPickCommit;
public class Module extends RestApiModule {
@Override
@@ -96,6 +97,8 @@
get(PROJECT_KIND, "config").to(GetConfig.class);
put(PROJECT_KIND, "config").to(PutConfig.class);
+ post(COMMIT_KIND, "cherrypick").to(CherryPickCommit.class);
+
factory(DeleteRef.Factory.class);
}
}