Merge changes from topic 'addUpdateChange'
* changes: Add new REST api for creating a merge patch set for change Refactor ReplaceOp for updating change rest api
This commit is contained in:
@@ -517,6 +517,61 @@ describes the change.
|
|||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[[create-merge-patch-set-for-change]]
|
||||||
|
=== Create Merge Patch Set For Change
|
||||||
|
--
|
||||||
|
'POST /changes/link:#change-id[\{change-id\}]/merge'
|
||||||
|
--
|
||||||
|
|
||||||
|
Update an existing change by using a
|
||||||
|
link:#merge-patch-set-input[MergePatchSetInput] entity.
|
||||||
|
|
||||||
|
Gerrit will create a merge commit based on the information of
|
||||||
|
MergePatchSetInput and add a new patch set to the change corresponding
|
||||||
|
to the new merge commit.
|
||||||
|
|
||||||
|
.Request
|
||||||
|
----
|
||||||
|
POST /changes/test~master~Ic5466d107c5294414710935a8ef3b0180fb848dc/merge HTTP/1.0
|
||||||
|
Content-Type: application/json; charset=UTF-8
|
||||||
|
|
||||||
|
{
|
||||||
|
"source": "refs/12/1234/1"
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
As response a link:#change-info[ChangeInfo] entity with current revision is
|
||||||
|
returned that describes the resulting change.
|
||||||
|
|
||||||
|
.Response
|
||||||
|
----
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Disposition: attachment
|
||||||
|
Content-Type: application/json; charset=UTF-8
|
||||||
|
|
||||||
|
)]}'
|
||||||
|
{
|
||||||
|
"id": "test~master~Ic5466d107c5294414710935a8ef3b0180fb848dc",
|
||||||
|
"project": "test",
|
||||||
|
"branch": "master",
|
||||||
|
"hashtags": [],
|
||||||
|
"change_id": "Ic5466d107c5294414710935a8ef3b0180fb848dc",
|
||||||
|
"subject": "Merge dev_branch into master",
|
||||||
|
"status": "NEW",
|
||||||
|
"created": "2016-09-23 18:08:53.238000000",
|
||||||
|
"updated": "2016-09-23 18:09:25.934000000",
|
||||||
|
"submit_type": "MERGE_IF_NECESSARY",
|
||||||
|
"mergeable": true,
|
||||||
|
"insertions": 5,
|
||||||
|
"deletions": 0,
|
||||||
|
"_number": 72,
|
||||||
|
"owner": {
|
||||||
|
"_account_id": 1000000
|
||||||
|
},
|
||||||
|
"current_revision": "27cc4558b5a3d3387dd11ee2df7a117e7e581822"
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
[[get-change-detail]]
|
[[get-change-detail]]
|
||||||
=== Get Change Detail
|
=== Get Change Detail
|
||||||
--
|
--
|
||||||
@@ -5367,6 +5422,25 @@ The strategy of the merge, can be `recursive`, `resolve`,
|
|||||||
`simple-two-way-in-core`, `ours` or `theirs`, default will use project settings.
|
`simple-two-way-in-core`, `ours` or `theirs`, default will use project settings.
|
||||||
|============================
|
|============================
|
||||||
|
|
||||||
|
[[merge-patch-set-input]]
|
||||||
|
=== MergePatchSetInput
|
||||||
|
The `MergePatchSetInput` entity contains information about updating a new
|
||||||
|
change by creating a new merge commit.
|
||||||
|
|
||||||
|
[options="header",cols="1,^1,5"]
|
||||||
|
|==================================
|
||||||
|
|Field Name ||Description
|
||||||
|
|`subject` |optional|
|
||||||
|
The new subject for the change, if not specified, will reuse the current patch
|
||||||
|
set's subject
|
||||||
|
|`inheritParent` |optional, default to `false`|
|
||||||
|
Use the current patch set's first parent as the merge tip when set to `true`.
|
||||||
|
Otherwise, use the current branch tip of the destination branch.
|
||||||
|
|`merge` ||
|
||||||
|
The detail of the source commit for merge as a link:#merge-input[MergeInput]
|
||||||
|
entity.
|
||||||
|
|==================================
|
||||||
|
|
||||||
[[move-input]]
|
[[move-input]]
|
||||||
=== MoveInput
|
=== MoveInput
|
||||||
The `MoveInput` entity contains information for moving a change to a new branch.
|
The `MoveInput` entity contains information for moving a change to a new branch.
|
||||||
|
|||||||
@@ -61,9 +61,11 @@ import com.google.gerrit.extensions.common.ApprovalInfo;
|
|||||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
import com.google.gerrit.extensions.common.ChangeInput;
|
import com.google.gerrit.extensions.common.ChangeInput;
|
||||||
import com.google.gerrit.extensions.common.ChangeMessageInfo;
|
import com.google.gerrit.extensions.common.ChangeMessageInfo;
|
||||||
|
import com.google.gerrit.extensions.common.MergePatchSetInput;
|
||||||
import com.google.gerrit.extensions.common.CommitInfo;
|
import com.google.gerrit.extensions.common.CommitInfo;
|
||||||
import com.google.gerrit.extensions.common.GitPerson;
|
import com.google.gerrit.extensions.common.GitPerson;
|
||||||
import com.google.gerrit.extensions.common.LabelInfo;
|
import com.google.gerrit.extensions.common.LabelInfo;
|
||||||
|
import com.google.gerrit.extensions.common.MergeInput;
|
||||||
import com.google.gerrit.extensions.common.RevisionInfo;
|
import com.google.gerrit.extensions.common.RevisionInfo;
|
||||||
import com.google.gerrit.extensions.restapi.AuthException;
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
@@ -71,6 +73,7 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
|||||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.client.AccountGroup;
|
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||||
|
import com.google.gerrit.reviewdb.client.Branch;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.LabelId;
|
import com.google.gerrit.reviewdb.client.LabelId;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
@@ -1921,6 +1924,86 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
+ r1.getChange().getId().id + ".");
|
+ r1.getChange().getId().id + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateMergePatchSet() throws Exception {
|
||||||
|
PushOneCommit.Result start = pushTo("refs/heads/master");
|
||||||
|
start.assertOkStatus();
|
||||||
|
// create a change for master
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
r.assertOkStatus();
|
||||||
|
String changeId = r.getChangeId();
|
||||||
|
|
||||||
|
testRepo.reset(start.getCommit());
|
||||||
|
PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
|
||||||
|
currentMaster.assertOkStatus();
|
||||||
|
String parent = currentMaster.getCommit().getName();
|
||||||
|
|
||||||
|
// push a commit into dev branch
|
||||||
|
createBranch(new Branch.NameKey(project, "dev"));
|
||||||
|
PushOneCommit.Result changeA = pushFactory
|
||||||
|
.create(db, user.getIdent(), testRepo, "change A", "A.txt", "A content")
|
||||||
|
.to("refs/heads/dev");
|
||||||
|
changeA.assertOkStatus();
|
||||||
|
MergeInput mergeInput = new MergeInput();
|
||||||
|
mergeInput.source = "dev";
|
||||||
|
MergePatchSetInput in = new MergePatchSetInput();
|
||||||
|
in.merge = mergeInput;
|
||||||
|
in.subject = "update change by merge ps2";
|
||||||
|
gApi.changes().id(changeId).createMergePatchSet(in);
|
||||||
|
ChangeInfo changeInfo = gApi.changes().id(changeId)
|
||||||
|
.get(EnumSet.of(ListChangesOption.ALL_REVISIONS,
|
||||||
|
ListChangesOption.CURRENT_COMMIT,
|
||||||
|
ListChangesOption.CURRENT_REVISION));
|
||||||
|
assertThat(changeInfo.revisions.size()).isEqualTo(2);
|
||||||
|
assertThat(changeInfo.subject).isEqualTo(in.subject);
|
||||||
|
assertThat(
|
||||||
|
changeInfo.revisions.get(changeInfo.currentRevision).commit.parents
|
||||||
|
.get(0).commit).isEqualTo(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateMergePatchSetInheritParent() throws Exception {
|
||||||
|
PushOneCommit.Result start = pushTo("refs/heads/master");
|
||||||
|
start.assertOkStatus();
|
||||||
|
// create a change for master
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
r.assertOkStatus();
|
||||||
|
String changeId = r.getChangeId();
|
||||||
|
String parent = r.getCommit().getParent(0).getName();
|
||||||
|
|
||||||
|
// advance master branch
|
||||||
|
testRepo.reset(start.getCommit());
|
||||||
|
PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
|
||||||
|
currentMaster.assertOkStatus();
|
||||||
|
|
||||||
|
// push a commit into dev branch
|
||||||
|
createBranch(new Branch.NameKey(project, "dev"));
|
||||||
|
PushOneCommit.Result changeA = pushFactory
|
||||||
|
.create(db, user.getIdent(), testRepo, "change A", "A.txt", "A content")
|
||||||
|
.to("refs/heads/dev");
|
||||||
|
changeA.assertOkStatus();
|
||||||
|
MergeInput mergeInput = new MergeInput();
|
||||||
|
mergeInput.source = "dev";
|
||||||
|
MergePatchSetInput in = new MergePatchSetInput();
|
||||||
|
in.merge = mergeInput;
|
||||||
|
in.subject = "update change by merge ps2 inherit parent of ps1";
|
||||||
|
in.inheritParent = true;
|
||||||
|
gApi.changes().id(changeId).createMergePatchSet(in);
|
||||||
|
ChangeInfo changeInfo = gApi.changes().id(changeId)
|
||||||
|
.get(EnumSet.of(ListChangesOption.ALL_REVISIONS,
|
||||||
|
ListChangesOption.CURRENT_COMMIT,
|
||||||
|
ListChangesOption.CURRENT_REVISION));
|
||||||
|
|
||||||
|
assertThat(changeInfo.revisions.size()).isEqualTo(2);
|
||||||
|
assertThat(changeInfo.subject).isEqualTo(in.subject);
|
||||||
|
assertThat(
|
||||||
|
changeInfo.revisions.get(changeInfo.currentRevision).commit.parents
|
||||||
|
.get(0).commit).isEqualTo(parent);
|
||||||
|
assertThat(
|
||||||
|
changeInfo.revisions.get(changeInfo.currentRevision).commit.parents
|
||||||
|
.get(0).commit).isNotEqualTo(currentMaster.getCommit().getName());
|
||||||
|
}
|
||||||
|
|
||||||
private static Iterable<Account.Id> getReviewers(
|
private static Iterable<Account.Id> getReviewers(
|
||||||
Collection<AccountInfo> r) {
|
Collection<AccountInfo> r) {
|
||||||
return Iterables.transform(r, a -> new Account.Id(a._accountId));
|
return Iterables.transform(r, a -> new Account.Id(a._accountId));
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package com.google.gerrit.extensions.api.changes;
|
|||||||
import com.google.gerrit.extensions.client.ListChangesOption;
|
import com.google.gerrit.extensions.client.ListChangesOption;
|
||||||
import com.google.gerrit.extensions.common.AccountInfo;
|
import com.google.gerrit.extensions.common.AccountInfo;
|
||||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
|
import com.google.gerrit.extensions.common.MergePatchSetInput;
|
||||||
import com.google.gerrit.extensions.common.CommentInfo;
|
import com.google.gerrit.extensions.common.CommentInfo;
|
||||||
import com.google.gerrit.extensions.common.EditInfo;
|
import com.google.gerrit.extensions.common.EditInfo;
|
||||||
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
|
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
|
||||||
@@ -95,6 +96,9 @@ public interface ChangeApi {
|
|||||||
*/
|
*/
|
||||||
ChangeApi revert(RevertInput in) throws RestApiException;
|
ChangeApi revert(RevertInput in) throws RestApiException;
|
||||||
|
|
||||||
|
/** Create a merge patch set for the change. */
|
||||||
|
ChangeInfo createMergePatchSet(MergePatchSetInput in) throws RestApiException;
|
||||||
|
|
||||||
List<ChangeInfo> submittedTogether() throws RestApiException;
|
List<ChangeInfo> submittedTogether() throws RestApiException;
|
||||||
SubmittedTogetherInfo submittedTogether(
|
SubmittedTogetherInfo submittedTogether(
|
||||||
EnumSet<SubmittedTogetherOption> options) throws RestApiException;
|
EnumSet<SubmittedTogetherOption> options) throws RestApiException;
|
||||||
@@ -412,5 +416,11 @@ public interface ChangeApi {
|
|||||||
EnumSet<SubmittedTogetherOption> b) throws RestApiException {
|
EnumSet<SubmittedTogetherOption> b) throws RestApiException {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChangeInfo createMergePatchSet(MergePatchSetInput in)
|
||||||
|
throws RestApiException {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (C) 2016 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.common;
|
||||||
|
|
||||||
|
public class MergePatchSetInput {
|
||||||
|
public String subject;
|
||||||
|
public boolean inheritParent;
|
||||||
|
public MergeInput merge;
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ import com.google.gerrit.extensions.api.changes.SubmittedTogetherOption;
|
|||||||
import com.google.gerrit.extensions.client.ListChangesOption;
|
import com.google.gerrit.extensions.client.ListChangesOption;
|
||||||
import com.google.gerrit.extensions.common.AccountInfo;
|
import com.google.gerrit.extensions.common.AccountInfo;
|
||||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
|
import com.google.gerrit.extensions.common.MergePatchSetInput;
|
||||||
import com.google.gerrit.extensions.common.CommentInfo;
|
import com.google.gerrit.extensions.common.CommentInfo;
|
||||||
import com.google.gerrit.extensions.common.EditInfo;
|
import com.google.gerrit.extensions.common.EditInfo;
|
||||||
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
|
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
|
||||||
@@ -63,7 +64,9 @@ import com.google.gerrit.server.change.Reviewers;
|
|||||||
import com.google.gerrit.server.change.Revisions;
|
import com.google.gerrit.server.change.Revisions;
|
||||||
import com.google.gerrit.server.change.SubmittedTogether;
|
import com.google.gerrit.server.change.SubmittedTogether;
|
||||||
import com.google.gerrit.server.change.SuggestChangeReviewers;
|
import com.google.gerrit.server.change.SuggestChangeReviewers;
|
||||||
|
import com.google.gerrit.server.change.CreateMergePatchSet;
|
||||||
import com.google.gerrit.server.git.UpdateException;
|
import com.google.gerrit.server.git.UpdateException;
|
||||||
|
import com.google.gerrit.server.project.InvalidChangeOperationException;
|
||||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
@@ -91,6 +94,7 @@ class ChangeApiImpl implements ChangeApi {
|
|||||||
private final Abandon abandon;
|
private final Abandon abandon;
|
||||||
private final Revert revert;
|
private final Revert revert;
|
||||||
private final Restore restore;
|
private final Restore restore;
|
||||||
|
private final CreateMergePatchSet updateByMerge;
|
||||||
private final Provider<SubmittedTogether> submittedTogether;
|
private final Provider<SubmittedTogether> submittedTogether;
|
||||||
private final PublishDraftPatchSet.CurrentRevision
|
private final PublishDraftPatchSet.CurrentRevision
|
||||||
publishDraftChange;
|
publishDraftChange;
|
||||||
@@ -122,6 +126,7 @@ class ChangeApiImpl implements ChangeApi {
|
|||||||
Abandon abandon,
|
Abandon abandon,
|
||||||
Revert revert,
|
Revert revert,
|
||||||
Restore restore,
|
Restore restore,
|
||||||
|
CreateMergePatchSet updateByMerge,
|
||||||
Provider<SubmittedTogether> submittedTogether,
|
Provider<SubmittedTogether> submittedTogether,
|
||||||
PublishDraftPatchSet.CurrentRevision publishDraftChange,
|
PublishDraftPatchSet.CurrentRevision publishDraftChange,
|
||||||
DeleteDraftChange deleteDraftChange,
|
DeleteDraftChange deleteDraftChange,
|
||||||
@@ -151,6 +156,7 @@ class ChangeApiImpl implements ChangeApi {
|
|||||||
this.suggestReviewers = suggestReviewers;
|
this.suggestReviewers = suggestReviewers;
|
||||||
this.abandon = abandon;
|
this.abandon = abandon;
|
||||||
this.restore = restore;
|
this.restore = restore;
|
||||||
|
this.updateByMerge = updateByMerge;
|
||||||
this.submittedTogether = submittedTogether;
|
this.submittedTogether = submittedTogether;
|
||||||
this.publishDraftChange = publishDraftChange;
|
this.publishDraftChange = publishDraftChange;
|
||||||
this.deleteDraftChange = deleteDraftChange;
|
this.deleteDraftChange = deleteDraftChange;
|
||||||
@@ -267,6 +273,17 @@ class ChangeApiImpl implements ChangeApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChangeInfo createMergePatchSet(MergePatchSetInput in)
|
||||||
|
throws RestApiException {
|
||||||
|
try {
|
||||||
|
return updateByMerge.apply(change, in).value();
|
||||||
|
} catch (IOException | UpdateException | InvalidChangeOperationException
|
||||||
|
| NoSuchChangeException | OrmException e) {
|
||||||
|
throw new RestApiException("Cannot update change by merge", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ChangeInfo> submittedTogether() throws RestApiException {
|
public List<ChangeInfo> submittedTogether() throws RestApiException {
|
||||||
SubmittedTogetherInfo info = submittedTogether(
|
SubmittedTogetherInfo info = submittedTogether(
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
// Copyright (C) 2016 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.MoreObjects;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.gerrit.common.TimeUtil;
|
||||||
|
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
||||||
|
import com.google.gerrit.extensions.client.ListChangesOption;
|
||||||
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
|
import com.google.gerrit.extensions.common.MergePatchSetInput;
|
||||||
|
import com.google.gerrit.extensions.common.MergeInput;
|
||||||
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
|
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||||
|
import com.google.gerrit.extensions.restapi.MergeConflictException;
|
||||||
|
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||||
|
import com.google.gerrit.extensions.restapi.Response;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||||
|
import com.google.gerrit.reviewdb.client.Branch;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
|
import com.google.gerrit.server.ChangeUtil;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gerrit.server.PatchSetUtil;
|
||||||
|
import com.google.gerrit.server.git.BatchUpdate;
|
||||||
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
|
import com.google.gerrit.server.git.MergeIdenticalTreeException;
|
||||||
|
import com.google.gerrit.server.git.MergeUtil;
|
||||||
|
import com.google.gerrit.server.git.UpdateException;
|
||||||
|
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.ProjectControl;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
|
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;
|
||||||
|
import org.eclipse.jgit.util.ChangeIdUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class CreateMergePatchSet implements
|
||||||
|
RestModifyView<ChangeResource, MergePatchSetInput> {
|
||||||
|
|
||||||
|
private final Provider<ReviewDb> db;
|
||||||
|
private final GitRepositoryManager gitManager;
|
||||||
|
private final TimeZone serverTimeZone;
|
||||||
|
private final Provider<CurrentUser> user;
|
||||||
|
private final ChangeJson.Factory jsonFactory;
|
||||||
|
private final PatchSetUtil psUtil;
|
||||||
|
private final MergeUtil.Factory mergeUtilFactory;
|
||||||
|
private final BatchUpdate.Factory batchUpdateFactory;
|
||||||
|
private final PatchSetInserter.Factory patchSetInserterFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CreateMergePatchSet(Provider<ReviewDb> db,
|
||||||
|
GitRepositoryManager gitManager,
|
||||||
|
@GerritPersonIdent PersonIdent myIdent,
|
||||||
|
Provider<CurrentUser> user,
|
||||||
|
ChangeJson.Factory json,
|
||||||
|
PatchSetUtil psUtil,
|
||||||
|
MergeUtil.Factory mergeUtilFactory,
|
||||||
|
BatchUpdate.Factory batchUpdateFactory,
|
||||||
|
PatchSetInserter.Factory patchSetInserterFactory) {
|
||||||
|
this.db = db;
|
||||||
|
this.gitManager = gitManager;
|
||||||
|
this.serverTimeZone = myIdent.getTimeZone();
|
||||||
|
this.user = user;
|
||||||
|
this.jsonFactory = json;
|
||||||
|
this.psUtil = psUtil;
|
||||||
|
this.mergeUtilFactory = mergeUtilFactory;
|
||||||
|
this.batchUpdateFactory = batchUpdateFactory;
|
||||||
|
this.patchSetInserterFactory = patchSetInserterFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<ChangeInfo> apply(ChangeResource req, MergePatchSetInput in)
|
||||||
|
throws NoSuchChangeException, OrmException, IOException,
|
||||||
|
InvalidChangeOperationException, RestApiException, UpdateException {
|
||||||
|
if (in.merge == null) {
|
||||||
|
throw new BadRequestException("merge field is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
MergeInput merge = in.merge;
|
||||||
|
if (Strings.isNullOrEmpty(merge.source)) {
|
||||||
|
throw new BadRequestException("merge.source must be non-empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangeControl ctl = req.getControl();
|
||||||
|
if (!ctl.isVisible(db.get())) {
|
||||||
|
throw new InvalidChangeOperationException(
|
||||||
|
"Base change not found: " + req.getId());
|
||||||
|
}
|
||||||
|
PatchSet ps = psUtil.current(db.get(), ctl.getNotes());
|
||||||
|
if (!ctl.canAddPatchSet(db.get())) {
|
||||||
|
throw new AuthException("cannot add patch set");
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectControl projectControl = ctl.getProjectControl();
|
||||||
|
Change change = ctl.getChange();
|
||||||
|
Project.NameKey project = change.getProject();
|
||||||
|
Branch.NameKey dest = change.getDest();
|
||||||
|
try (Repository git = gitManager.openRepository(project);
|
||||||
|
ObjectInserter oi = git.newObjectInserter();
|
||||||
|
RevWalk rw = new RevWalk(oi.newReader())) {
|
||||||
|
|
||||||
|
RevCommit sourceCommit =
|
||||||
|
MergeUtil.resolveCommit(git, rw, merge.source);
|
||||||
|
if (!projectControl.canReadCommit(db.get(), git, sourceCommit)) {
|
||||||
|
throw new ResourceNotFoundException(
|
||||||
|
"cannot find source commit: " + merge.source + " to merge.");
|
||||||
|
}
|
||||||
|
|
||||||
|
RevCommit currentPsCommit =
|
||||||
|
rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
|
||||||
|
|
||||||
|
Timestamp now = TimeUtil.nowTs();
|
||||||
|
IdentifiedUser me = user.get().asIdentifiedUser();
|
||||||
|
PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
|
||||||
|
|
||||||
|
RevCommit newCommit =
|
||||||
|
createMergeCommit(in, projectControl, dest, git, oi, rw,
|
||||||
|
currentPsCommit, sourceCommit, author,
|
||||||
|
ObjectId.fromString(change.getKey().get().substring(1)));
|
||||||
|
|
||||||
|
PatchSet.Id nextPsId = ChangeUtil.nextPatchSetId(ps.getId());
|
||||||
|
PatchSetInserter psInserter =
|
||||||
|
patchSetInserterFactory.create(ctl, nextPsId, newCommit);
|
||||||
|
try (BatchUpdate bu = batchUpdateFactory
|
||||||
|
.create(db.get(), project, me, now)) {
|
||||||
|
bu.setRepository(git, rw, oi);
|
||||||
|
bu.addOp(ctl.getId(), psInserter
|
||||||
|
.setMessage("Uploaded patch set " + nextPsId.get() + ".")
|
||||||
|
.setDraft(ps.isDraft())
|
||||||
|
.setNotify(NotifyHandling.NONE));
|
||||||
|
bu.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangeJson json =
|
||||||
|
jsonFactory.create(EnumSet.of(ListChangesOption.CURRENT_REVISION));
|
||||||
|
return Response.ok(json.format(psInserter.getChange()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RevCommit createMergeCommit(MergePatchSetInput in,
|
||||||
|
ProjectControl projectControl, Branch.NameKey dest, Repository git,
|
||||||
|
ObjectInserter oi, RevWalk rw, RevCommit currentPsCommit,
|
||||||
|
RevCommit sourceCommit, PersonIdent author, ObjectId changeId)
|
||||||
|
throws ResourceNotFoundException, MergeIdenticalTreeException,
|
||||||
|
MergeConflictException, IOException {
|
||||||
|
|
||||||
|
ObjectId parentCommit;
|
||||||
|
if (in.inheritParent) {
|
||||||
|
// inherit first parent from previous patch set
|
||||||
|
parentCommit = currentPsCommit.getParent(0);
|
||||||
|
} else {
|
||||||
|
// get the current branch tip of destination branch
|
||||||
|
Ref destRef = git.getRefDatabase().exactRef(dest.get());
|
||||||
|
if (destRef != null) {
|
||||||
|
parentCommit = destRef.getObjectId();
|
||||||
|
} else {
|
||||||
|
throw new ResourceNotFoundException("cannot find destination branch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RevCommit mergeTip = rw.parseCommit(parentCommit);
|
||||||
|
|
||||||
|
String commitMsg;
|
||||||
|
if (Strings.emptyToNull(in.subject) != null) {
|
||||||
|
commitMsg = ChangeIdUtil.insertId(in.subject, changeId);
|
||||||
|
} else {
|
||||||
|
// reuse previous patch set commit message
|
||||||
|
commitMsg = currentPsCommit.getFullMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
String mergeStrategy = MoreObjects.firstNonNull(
|
||||||
|
Strings.emptyToNull(in.merge.strategy),
|
||||||
|
mergeUtilFactory.create(projectControl.getProjectState())
|
||||||
|
.mergeStrategyName());
|
||||||
|
|
||||||
|
return MergeUtil.createMergeCommit(git, oi, mergeTip, sourceCommit,
|
||||||
|
mergeStrategy, author, commitMsg, rw);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,6 +53,7 @@ public class Module extends RestApiModule {
|
|||||||
DynamicMap.mapOf(binder(), VOTE_KIND);
|
DynamicMap.mapOf(binder(), VOTE_KIND);
|
||||||
|
|
||||||
get(CHANGE_KIND).to(GetChange.class);
|
get(CHANGE_KIND).to(GetChange.class);
|
||||||
|
post(CHANGE_KIND, "merge").to(CreateMergePatchSet.class);
|
||||||
get(CHANGE_KIND, "detail").to(GetDetail.class);
|
get(CHANGE_KIND, "detail").to(GetDetail.class);
|
||||||
get(CHANGE_KIND, "topic").to(GetTopic.class);
|
get(CHANGE_KIND, "topic").to(GetTopic.class);
|
||||||
get(CHANGE_KIND, "in").to(IncludedIn.class);
|
get(CHANGE_KIND, "in").to(IncludedIn.class);
|
||||||
|
|||||||
@@ -2433,10 +2433,12 @@ public class ReceiveCommits {
|
|||||||
rw.parseBody(newCommit);
|
rw.parseBody(newCommit);
|
||||||
|
|
||||||
RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
|
RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
|
||||||
replaceOp = replaceOpFactory.create(requestScopePropagator,
|
replaceOp = replaceOpFactory
|
||||||
projectControl, notes.getChange().getDest(), checkMergedInto,
|
.create(projectControl, notes.getChange().getDest(), checkMergedInto,
|
||||||
priorPatchSet, priorCommit, psId, newCommit, info, groups,
|
priorPatchSet, priorCommit, psId, newCommit, info, groups,
|
||||||
magicBranch, rp.getPushCertificate());
|
magicBranch, rp.getPushCertificate())
|
||||||
|
.setRequestScopePropagator(requestScopePropagator)
|
||||||
|
.setUpdateRef(false);
|
||||||
bu.addOp(notes.getChangeId(), replaceOp);
|
bu.addOp(notes.getChangeId(), replaceOp);
|
||||||
if (progress != null) {
|
if (progress != null) {
|
||||||
bu.addOp(notes.getChangeId(), new ChangeProgressOp(progress));
|
bu.addOp(notes.getChangeId(), new ChangeProgressOp(progress));
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ import org.eclipse.jgit.lib.RefDatabase;
|
|||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.eclipse.jgit.transport.PushCertificate;
|
import org.eclipse.jgit.transport.PushCertificate;
|
||||||
|
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -78,7 +79,6 @@ import java.util.concurrent.ExecutorService;
|
|||||||
public class ReplaceOp extends BatchUpdate.Op {
|
public class ReplaceOp extends BatchUpdate.Op {
|
||||||
public interface Factory {
|
public interface Factory {
|
||||||
ReplaceOp create(
|
ReplaceOp create(
|
||||||
RequestScopePropagator requestScopePropagator,
|
|
||||||
ProjectControl projectControl,
|
ProjectControl projectControl,
|
||||||
Branch.NameKey dest,
|
Branch.NameKey dest,
|
||||||
boolean checkMergedInto,
|
boolean checkMergedInto,
|
||||||
@@ -112,7 +112,6 @@ public class ReplaceOp extends BatchUpdate.Op {
|
|||||||
private final PatchSetUtil psUtil;
|
private final PatchSetUtil psUtil;
|
||||||
private final ReplacePatchSetSender.Factory replacePatchSetFactory;
|
private final ReplacePatchSetSender.Factory replacePatchSetFactory;
|
||||||
|
|
||||||
private final RequestScopePropagator requestScopePropagator;
|
|
||||||
private final ProjectControl projectControl;
|
private final ProjectControl projectControl;
|
||||||
private final Branch.NameKey dest;
|
private final Branch.NameKey dest;
|
||||||
private final boolean checkMergedInto;
|
private final boolean checkMergedInto;
|
||||||
@@ -133,6 +132,8 @@ public class ReplaceOp extends BatchUpdate.Op {
|
|||||||
private ChangeMessage msg;
|
private ChangeMessage msg;
|
||||||
private String rejectMessage;
|
private String rejectMessage;
|
||||||
private MergedByPushOp mergedByPushOp;
|
private MergedByPushOp mergedByPushOp;
|
||||||
|
private RequestScopePropagator requestScopePropagator;
|
||||||
|
private boolean updateRef;
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
ReplaceOp(AccountResolver accountResolver,
|
ReplaceOp(AccountResolver accountResolver,
|
||||||
@@ -149,7 +150,6 @@ public class ReplaceOp extends BatchUpdate.Op {
|
|||||||
PatchSetUtil psUtil,
|
PatchSetUtil psUtil,
|
||||||
ReplacePatchSetSender.Factory replacePatchSetFactory,
|
ReplacePatchSetSender.Factory replacePatchSetFactory,
|
||||||
@SendEmailExecutor ExecutorService sendEmailExecutor,
|
@SendEmailExecutor ExecutorService sendEmailExecutor,
|
||||||
@Assisted RequestScopePropagator requestScopePropagator,
|
|
||||||
@Assisted ProjectControl projectControl,
|
@Assisted ProjectControl projectControl,
|
||||||
@Assisted Branch.NameKey dest,
|
@Assisted Branch.NameKey dest,
|
||||||
@Assisted boolean checkMergedInto,
|
@Assisted boolean checkMergedInto,
|
||||||
@@ -176,7 +176,6 @@ public class ReplaceOp extends BatchUpdate.Op {
|
|||||||
this.replacePatchSetFactory = replacePatchSetFactory;
|
this.replacePatchSetFactory = replacePatchSetFactory;
|
||||||
this.sendEmailExecutor = sendEmailExecutor;
|
this.sendEmailExecutor = sendEmailExecutor;
|
||||||
|
|
||||||
this.requestScopePropagator = requestScopePropagator;
|
|
||||||
this.projectControl = projectControl;
|
this.projectControl = projectControl;
|
||||||
this.dest = dest;
|
this.dest = dest;
|
||||||
this.checkMergedInto = checkMergedInto;
|
this.checkMergedInto = checkMergedInto;
|
||||||
@@ -188,6 +187,7 @@ public class ReplaceOp extends BatchUpdate.Op {
|
|||||||
this.groups = groups;
|
this.groups = groups;
|
||||||
this.magicBranch = magicBranch;
|
this.magicBranch = magicBranch;
|
||||||
this.pushCertificate = pushCertificate;
|
this.pushCertificate = pushCertificate;
|
||||||
|
this.updateRef = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -203,6 +203,12 @@ public class ReplaceOp extends BatchUpdate.Op {
|
|||||||
requestScopePropagator, patchSetId, mergedInto.getName());
|
requestScopePropagator, patchSetId, mergedInto.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (updateRef) {
|
||||||
|
ctx.addRefUpdate(
|
||||||
|
new ReceiveCommand(ObjectId.zeroId(), commit,
|
||||||
|
patchSetId.toRefName()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -366,8 +372,10 @@ public class ReplaceOp extends BatchUpdate.Op {
|
|||||||
// BatchUpdate's perspective there is no ref update. Thus we have to fire it
|
// BatchUpdate's perspective there is no ref update. Thus we have to fire it
|
||||||
// manually.
|
// manually.
|
||||||
final Account account = ctx.getAccount();
|
final Account account = ctx.getAccount();
|
||||||
gitRefUpdated.fire(ctx.getProject(), newPatchSet.getRefName(),
|
if (!updateRef) {
|
||||||
ObjectId.zeroId(), commit, account);
|
gitRefUpdated.fire(ctx.getProject(), newPatchSet.getRefName(),
|
||||||
|
ObjectId.zeroId(), commit, account);
|
||||||
|
}
|
||||||
|
|
||||||
if (changeKind != ChangeKind.TRIVIAL_REBASE) {
|
if (changeKind != ChangeKind.TRIVIAL_REBASE) {
|
||||||
Runnable sender = new Runnable() {
|
Runnable sender = new Runnable() {
|
||||||
@@ -454,10 +462,25 @@ public class ReplaceOp extends BatchUpdate.Op {
|
|||||||
return newPatchSet;
|
return newPatchSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Change getChange() {
|
||||||
|
return change;
|
||||||
|
}
|
||||||
|
|
||||||
public String getRejectMessage() {
|
public String getRejectMessage() {
|
||||||
return rejectMessage;
|
return rejectMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReplaceOp setUpdateRef(boolean updateRef) {
|
||||||
|
this.updateRef = updateRef;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplaceOp setRequestScopePropagator(
|
||||||
|
RequestScopePropagator requestScopePropagator) {
|
||||||
|
this.requestScopePropagator = requestScopePropagator;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
private Ref findMergedInto(Context ctx, String first, RevCommit commit) {
|
private Ref findMergedInto(Context ctx, String first, RevCommit commit) {
|
||||||
try {
|
try {
|
||||||
RefDatabase refDatabase = ctx.getRepository().getRefDatabase();
|
RefDatabase refDatabase = ctx.getRepository().getRefDatabase();
|
||||||
|
|||||||
Reference in New Issue
Block a user