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
This commit is contained in:

committed by
Han-Wen Nienhuys

parent
13c1df76b8
commit
9d2ec04c1b
@@ -2060,6 +2060,60 @@ The content is returned as base64 encoded string.
|
||||
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
|
||||
|
||||
|
@@ -23,6 +23,7 @@ import static org.eclipse.jgit.lib.Constants.SIGNED_OFF_BY_TAG;
|
||||
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.ChangeApi;
|
||||
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.server.git.ChangeAlreadyMergedException;
|
||||
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 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
||||
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();
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -124,6 +124,14 @@ public interface ProjectApi {
|
||||
*/
|
||||
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 interface ProjectApi {
|
||||
public void deleteTags(DeleteTagsInput in) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommitApi commit(String commit) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -26,5 +26,6 @@ public class Module extends FactoryModule {
|
||||
factory(TagApiImpl.Factory.class);
|
||||
factory(ProjectApiImpl.Factory.class);
|
||||
factory(ChildProjectApiImpl.Factory.class);
|
||||
factory(CommitApiImpl.Factory.class);
|
||||
}
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ import com.google.gerrit.extensions.api.access.ProjectAccessInput;
|
||||
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.RestApiException;
|
||||
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 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
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 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
ListTags listTags,
|
||||
DeleteBranches deleteBranches,
|
||||
DeleteTags deleteTags,
|
||||
CommitsCollection commitsCollection,
|
||||
CommitApiImpl.Factory commitApi,
|
||||
@Assisted ProjectResource project) {
|
||||
this(
|
||||
user,
|
||||
@@ -133,6 +139,8 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
deleteBranches,
|
||||
deleteTags,
|
||||
project,
|
||||
commitsCollection,
|
||||
commitApi,
|
||||
null);
|
||||
}
|
||||
|
||||
@@ -157,6 +165,8 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
ListTags listTags,
|
||||
DeleteBranches deleteBranches,
|
||||
DeleteTags deleteTags,
|
||||
CommitsCollection commitsCollection,
|
||||
CommitApiImpl.Factory commitApi,
|
||||
@Assisted String name) {
|
||||
this(
|
||||
user,
|
||||
@@ -179,6 +189,8 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
deleteBranches,
|
||||
deleteTags,
|
||||
null,
|
||||
commitsCollection,
|
||||
commitApi,
|
||||
name);
|
||||
}
|
||||
|
||||
@@ -203,6 +215,8 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
DeleteBranches deleteBranches,
|
||||
DeleteTags deleteTags,
|
||||
ProjectResource project,
|
||||
CommitsCollection commitsCollection,
|
||||
CommitApiImpl.Factory commitApi,
|
||||
String name) {
|
||||
this.user = user;
|
||||
this.createProjectFactory = createProjectFactory;
|
||||
@@ -225,6 +239,8 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
this.listTags = listTags;
|
||||
this.deleteBranches = deleteBranches;
|
||||
this.deleteTags = deleteTags;
|
||||
this.commitsCollection = commitsCollection;
|
||||
this.commitApi = commitApi;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -393,6 +409,15 @@ public class ProjectApiImpl implements ProjectApi {
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
|
@@ -16,6 +16,7 @@ package com.google.gerrit.server.change;
|
||||
|
||||
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.MergeUtil;
|
||||
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.io.IOException;
|
||||
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 class CherryPickChange {
|
||||
}
|
||||
|
||||
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 @@ public class CherryPickChange {
|
||||
// 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 @@ public class CherryPickChange {
|
||||
|
||||
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 @@ public class CherryPickChange {
|
||||
|
||||
CodeReviewCommit cherryPickCommit;
|
||||
try {
|
||||
ProjectState projectState = refControl.getProjectControl().getProjectState();
|
||||
ProjectState projectState = targetRefControl.getProjectControl().getProjectState();
|
||||
cherryPickCommit =
|
||||
mergeUtilFactory
|
||||
.create(projectState)
|
||||
@@ -195,7 +210,7 @@ public class CherryPickChange {
|
||||
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 @@ public class CherryPickChange {
|
||||
+ " 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 @@ public class CherryPickChange {
|
||||
} 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 @@ public class CherryPickChange {
|
||||
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 @@ public class CherryPickChange {
|
||||
.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 @@ public class CherryPickChange {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -24,6 +24,7 @@ import static com.google.gerrit.server.project.TagResource.TAG_KIND;
|
||||
|
||||
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 @@ public class Module extends RestApiModule {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user