Add more information to MergeableInfo to indicate a merged commit
Add two fields contentMerged and commitMerged into MergeableInfo. contentMerged is true if the content of the commit is merged, commitMerged is true if the commit itself is merged. Also add more tests for CheckMergeability class. Change-Id: Ia756644411f217b8d8bae06f44f14a24597544bd
This commit is contained in:
@@ -4915,6 +4915,10 @@ The strategy of the merge, can be `recursive`, `resolve`,
|
||||
`simple-two-way-in-core`, `ours` or `theirs`.
|
||||
|`mergeable` ||
|
||||
`true` if this change is cleanly mergeable, `false` otherwise
|
||||
|`commit_merged` |optional|
|
||||
`true` if this change is already merged, `false` otherwise
|
||||
|`content_merged` |optional|
|
||||
`true` if the content of this change is already merged, `false` otherwise
|
||||
|`conflicts`|optional|
|
||||
A list of paths with conflicts
|
||||
|`mergeable_into`|optional|
|
||||
|
@@ -1363,8 +1363,9 @@ The content is returned as base64 encoded string.
|
||||
|
||||
Gets whether the source is mergeable with the target branch.
|
||||
|
||||
The `source` query parameter is required, which can be anything that could be resolved
|
||||
to a commit, see examples in link:rest-api-changes.html#merge-input[MergeInput].
|
||||
The `source` query parameter is required, which can be anything that could be
|
||||
resolved to a commit, see examples of the `source` attribute in
|
||||
link:rest-api-changes.html#merge-input[MergeInput].
|
||||
|
||||
Also takes an optional parameter `strategy`, which can be `recursive`, `resolve`,
|
||||
`simple-two-way-in-core`, `ours` or `theirs`, default will use project settings.
|
||||
@@ -1374,7 +1375,7 @@ Also takes an optional parameter `strategy`, which can be `recursive`, `resolve`
|
||||
GET /projects/test/branches/master/mergeable?source=testbranch&strategy=recursive HTTP/1.0
|
||||
----
|
||||
|
||||
As response a link:#mergeable-info[MergeableInfo] entity is returned.
|
||||
As response a link:rest-api-changes.html#mergeable-info[MergeableInfo] entity is returned.
|
||||
|
||||
.Response
|
||||
----
|
||||
@@ -1384,9 +1385,67 @@ As response a link:#mergeable-info[MergeableInfo] entity is returned.
|
||||
|
||||
)]}'
|
||||
{
|
||||
submit_type: "MERGE_IF_NECESSARY",
|
||||
strategy: "recursive",
|
||||
mergeable: true
|
||||
"submit_type": "MERGE_IF_NECESSARY",
|
||||
"strategy": "recursive",
|
||||
"mergeable": true,
|
||||
"commit_merged": false,
|
||||
"content_merged": false
|
||||
}
|
||||
----
|
||||
|
||||
or when there were conflicts.
|
||||
|
||||
.Response
|
||||
----
|
||||
HTTP/1.1 200 OK
|
||||
Content-Disposition: attachment
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
|
||||
)]}'
|
||||
{
|
||||
"submit_type": "MERGE_IF_NECESSARY",
|
||||
"strategy": "recursive",
|
||||
"mergeable": false,
|
||||
"conflicts": [
|
||||
"common.txt",
|
||||
"shared.txt"
|
||||
]
|
||||
}
|
||||
----
|
||||
|
||||
or when the 'testbranch' has been already merged.
|
||||
|
||||
.Response
|
||||
----
|
||||
HTTP/1.1 200 OK
|
||||
Content-Disposition: attachment
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
|
||||
)]}'
|
||||
{
|
||||
"submit_type": "MERGE_IF_NECESSARY",
|
||||
"strategy": "recursive",
|
||||
"mergeable": true,
|
||||
"commit_merged": true,
|
||||
"content_merged": true
|
||||
}
|
||||
----
|
||||
|
||||
or when only the content of 'testbranch' has been merged.
|
||||
|
||||
.Response
|
||||
----
|
||||
HTTP/1.1 200 OK
|
||||
Content-Disposition: attachment
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
|
||||
)]}'
|
||||
{
|
||||
"submit_type": "MERGE_IF_NECESSARY",
|
||||
"strategy": "recursive",
|
||||
"mergeable": true,
|
||||
"commit_merged": false,
|
||||
"content_merged": true
|
||||
}
|
||||
----
|
||||
|
||||
|
@@ -20,31 +20,37 @@ import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
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.PushOneCommit;
|
||||
import com.google.gerrit.acceptance.PushOneCommit.Result;
|
||||
import com.google.gerrit.acceptance.RestResponse;
|
||||
import com.google.gerrit.extensions.api.changes.ChangeApi;
|
||||
import com.google.gerrit.extensions.api.changes.CherryPickInput;
|
||||
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||
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.MergeInput;
|
||||
import com.google.gerrit.extensions.common.MergeableInfo;
|
||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.reviewdb.client.Branch;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
|
||||
import com.google.gerrit.server.git.ChangeAlreadyMergedException;
|
||||
import com.google.gerrit.testutil.ConfigSuite;
|
||||
import com.google.gerrit.testutil.TestTimeUtil;
|
||||
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.transport.RefSpec;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
@@ -142,6 +148,99 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMergeChange() throws Exception {
|
||||
changeInTwoBranches("branchA", "a.txt", "branchB", "b.txt");
|
||||
ChangeInput in =
|
||||
newMergeChangeInput("branchA", "branchB", "");
|
||||
assertCreateSucceeds(in);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMergeChange_Conflicts() throws Exception {
|
||||
changeInTwoBranches("branchA", "shared.txt", "branchB", "shared.txt");
|
||||
ChangeInput in =
|
||||
newMergeChangeInput("branchA", "branchB", "");
|
||||
assertCreateFails(in, RestApiException.class, "merge conflict");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMergeChange_Conflicts_Ours() throws Exception {
|
||||
changeInTwoBranches("branchA", "shared.txt", "branchB", "shared.txt");
|
||||
ChangeInput in =
|
||||
newMergeChangeInput("branchA", "branchB", "ours");
|
||||
assertCreateSucceeds(in);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidSource() throws Exception {
|
||||
changeInTwoBranches("branchA", "a.txt", "branchB", "b.txt");
|
||||
ChangeInput in =
|
||||
newMergeChangeInput("branchA", "invalid", "");
|
||||
assertCreateFails(in, BadRequestException.class,
|
||||
"Cannot resolve 'invalid' to a commit");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidStrategy() throws Exception {
|
||||
changeInTwoBranches("branchA", "a.txt", "branchB", "b.txt");
|
||||
ChangeInput in =
|
||||
newMergeChangeInput("branchA", "branchB", "octopus");
|
||||
assertCreateFails(in, BadRequestException.class,
|
||||
"invalid merge strategy: octopus");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void alreadyMerged() throws Exception {
|
||||
ObjectId c0 = testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("first commit")
|
||||
.add("a.txt", "a contents ")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/master")).call();
|
||||
|
||||
testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("second commit")
|
||||
.add("b.txt", "b contents ")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/master")).call();
|
||||
|
||||
ChangeInput in =
|
||||
newMergeChangeInput("master", c0.getName(), "");
|
||||
assertCreateFails(in, ChangeAlreadyMergedException.class,
|
||||
"'" + c0.getName() + "' has already been merged");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onlyContentMerged() throws Exception {
|
||||
testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("first commit")
|
||||
.add("a.txt", "a contents ")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/master")).call();
|
||||
|
||||
// create a change, and cherrypick into master
|
||||
PushOneCommit.Result cId = createChange();
|
||||
RevCommit commitId = cId.getCommit();
|
||||
CherryPickInput cpi = new CherryPickInput();
|
||||
cpi.destination = "master";
|
||||
cpi.message = "cherry pick the commit";
|
||||
ChangeApi orig = gApi.changes()
|
||||
.id(cId.getChangeId());
|
||||
ChangeApi cherry = orig.current().cherryPick(cpi);
|
||||
cherry.current().review(ReviewInput.approve());
|
||||
cherry.current().submit();
|
||||
|
||||
ObjectId remoteId = getRemoteHead();
|
||||
assertThat(remoteId).isNotEqualTo(commitId);
|
||||
|
||||
ChangeInput in =
|
||||
newMergeChangeInput("master", commitId.getName(), "");
|
||||
assertCreateSucceeds(in);
|
||||
}
|
||||
|
||||
private ChangeInput newChangeInput(ChangeStatus status) {
|
||||
ChangeInput in = new ChangeInput();
|
||||
in.project = project.get();
|
||||
@@ -197,58 +296,25 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
||||
assertThat(o.signedOffBy).isTrue();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void createMergeChange() throws Exception {
|
||||
changeInTwoBranches("a.txt", "b.txt");
|
||||
ChangeInput in =
|
||||
newMergeChangeInput("master", "branchA", ChangeStatus.NEW);
|
||||
assertCreateSucceeds(in);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMergeChange_Conflicts() throws Exception {
|
||||
changeInTwoBranches("shared.txt", "shared.txt");
|
||||
ChangeInput in =
|
||||
newMergeChangeInput("master", "branchA", ChangeStatus.NEW);
|
||||
assertCreateFails(in, RestApiException.class, "merge conflict");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dryRunMerge() throws Exception {
|
||||
changeInTwoBranches("a.txt", "b.txt");
|
||||
RestResponse r = adminRestSession.get("/projects/" + project.get()
|
||||
+ "/branches/master/mergeable?source=branchA");
|
||||
MergeableInfo m = newGson().fromJson(r.getReader(), MergeableInfo.class);
|
||||
assertThat(m.mergeable).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dryRunMerge_Conflicts() throws Exception {
|
||||
changeInTwoBranches("a.txt", "a.txt");
|
||||
RestResponse r = adminRestSession.get("/projects/" + project.get()
|
||||
+ "/branches/master/mergeable?source=branchA");
|
||||
MergeableInfo m = newGson().fromJson(r.getReader(), MergeableInfo.class);
|
||||
assertThat(m.mergeable).isFalse();
|
||||
assertThat(m.conflicts).containsExactly("a.txt");
|
||||
}
|
||||
|
||||
private ChangeInput newMergeChangeInput(String targetBranch,
|
||||
String sourceRef, ChangeStatus status) {
|
||||
private ChangeInput newMergeChangeInput(String targetBranch, String sourceRef,
|
||||
String strategy) {
|
||||
// create a merge change from branchA to master in gerrit
|
||||
ChangeInput in = new ChangeInput();
|
||||
in.project = project.get();
|
||||
in.branch = targetBranch;
|
||||
in.subject = "merge " + sourceRef + " to " + targetBranch;
|
||||
in.status = status;
|
||||
in.status = ChangeStatus.NEW;
|
||||
MergeInput mergeInput = new MergeInput();
|
||||
mergeInput.source = sourceRef;
|
||||
in.merge = mergeInput;
|
||||
if (!Strings.isNullOrEmpty(strategy)) {
|
||||
in.merge.strategy = strategy;
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
private void changeInTwoBranches(String fileInMaster, String fileInBranchA)
|
||||
throws Exception {
|
||||
private void changeInTwoBranches(String branchA, String fileA, String branchB,
|
||||
String fileB) throws Exception {
|
||||
// create a initial commit in master
|
||||
Result initialCommit = pushFactory
|
||||
.create(db, user.getIdent(), testRepo, "initial commit", "readme.txt",
|
||||
@@ -256,21 +322,21 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
||||
.to("refs/heads/master");
|
||||
initialCommit.assertOkStatus();
|
||||
|
||||
// create a new branch branchA
|
||||
createBranch(new Branch.NameKey(project, "branchA"));
|
||||
|
||||
// create another commit in master
|
||||
Result changeToMaster = pushFactory
|
||||
.create(db, user.getIdent(), testRepo, "change to master", fileInMaster,
|
||||
"master content")
|
||||
.to("refs/heads/master");
|
||||
changeToMaster.assertOkStatus();
|
||||
// create two new branches
|
||||
createBranch(new Branch.NameKey(project, branchA));
|
||||
createBranch(new Branch.NameKey(project, branchB));
|
||||
|
||||
// create a commit in branchA
|
||||
PushOneCommit commit = pushFactory
|
||||
.create(db, user.getIdent(), testRepo, "change to branchA",
|
||||
fileInBranchA, "branchA content");
|
||||
commit.setParent(initialCommit.getCommit());
|
||||
commit.to("refs/heads/branchA");
|
||||
Result changeA = pushFactory
|
||||
.create(db, user.getIdent(), testRepo, "change A", fileA, "A content")
|
||||
.to("refs/heads/" + branchA);
|
||||
changeA.assertOkStatus();
|
||||
|
||||
// create a commit in branchB
|
||||
PushOneCommit commitB = pushFactory
|
||||
.create(db, user.getIdent(), testRepo, "change B", fileB, "B content");
|
||||
commitB.setParent(initialCommit.getCommit());
|
||||
Result changeB = commitB.to("refs/heads/" + branchB);
|
||||
changeB.assertOkStatus();
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,248 @@
|
||||
// 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.acceptance.rest.project;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||
import com.google.gerrit.acceptance.PushOneCommit;
|
||||
import com.google.gerrit.acceptance.RestResponse;
|
||||
import com.google.gerrit.extensions.api.changes.ChangeApi;
|
||||
import com.google.gerrit.extensions.api.changes.CherryPickInput;
|
||||
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||
import com.google.gerrit.extensions.api.projects.BranchInput;
|
||||
import com.google.gerrit.extensions.common.MergeableInfo;
|
||||
import com.google.gerrit.reviewdb.client.Branch;
|
||||
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.transport.RefSpec;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CheckMergeabilityIT extends AbstractDaemonTest {
|
||||
|
||||
private Branch.NameKey branch;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
branch = new Branch.NameKey(project, "test");
|
||||
gApi.projects()
|
||||
.name(branch.getParentKey().get())
|
||||
.branch(branch.get()).create(new BranchInput());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkMergeableCommit() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("some change in a")
|
||||
.add("a.txt", "a contents ")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/master")).call();
|
||||
|
||||
testRepo.reset(initialHead);
|
||||
testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("some change in b")
|
||||
.add("b.txt", "b contents ")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/test")).call();
|
||||
|
||||
assertMergeable("master", "test", "recursive");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkUnMergeableCommit() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("some change in a")
|
||||
.add("a.txt", "a contents ")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/master")).call();
|
||||
|
||||
testRepo.reset(initialHead);
|
||||
testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("some change in a too")
|
||||
.add("a.txt", "a contents too")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/test")).call();
|
||||
|
||||
assertUnMergeable("master", "test", "recursive", "a.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkOursMergeStrategy() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("some change in a")
|
||||
.add("a.txt", "a contents ")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/master")).call();
|
||||
|
||||
testRepo.reset(initialHead);
|
||||
testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("some change in a too")
|
||||
.add("a.txt", "a contents too")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/test")).call();
|
||||
|
||||
assertMergeable("master", "test", "ours");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkAlreadyMergedCommit() throws Exception {
|
||||
ObjectId c0 = testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("first commit")
|
||||
.add("a.txt", "a contents ")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/master")).call();
|
||||
|
||||
testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("second commit")
|
||||
.add("b.txt", "b contents ")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/master")).call();
|
||||
|
||||
assertCommitMerged("master", c0.getName(), "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkContentMergedCommit() throws Exception {
|
||||
testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("first commit")
|
||||
.add("a.txt", "a contents ")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/master")).call();
|
||||
|
||||
// create a change, and cherrypick into master
|
||||
PushOneCommit.Result cId = createChange();
|
||||
RevCommit commitId = cId.getCommit();
|
||||
CherryPickInput cpi = new CherryPickInput();
|
||||
cpi.destination = "master";
|
||||
cpi.message = "cherry pick the commit";
|
||||
ChangeApi orig = gApi.changes()
|
||||
.id(cId.getChangeId());
|
||||
ChangeApi cherry = orig.current().cherryPick(cpi);
|
||||
cherry.current().review(ReviewInput.approve());
|
||||
cherry.current().submit();
|
||||
|
||||
ObjectId remoteId = getRemoteHead();
|
||||
assertThat(remoteId).isNotEqualTo(commitId);
|
||||
assertContentMerged("master", commitId.getName(), "recursive");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInvalidSource() throws Exception {
|
||||
testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("first commit")
|
||||
.add("a.txt", "a contents ")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/master")).call();
|
||||
|
||||
assertBadRequest("master", "fdsafsdf", "recursive",
|
||||
"Cannot resolve 'fdsafsdf' to a commit");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInvalidStrategy() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("first commit")
|
||||
.add("a.txt", "a contents ")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/master")).call();
|
||||
|
||||
testRepo.reset(initialHead);
|
||||
testRepo.branch("HEAD").commit().insertChangeId()
|
||||
.message("some change in a too")
|
||||
.add("a.txt", "a contents too")
|
||||
.create();
|
||||
testRepo.git().push().setRemote("origin").setRefSpecs(
|
||||
new RefSpec("HEAD:refs/heads/test")).call();
|
||||
|
||||
assertBadRequest("master", "test", "octopus",
|
||||
"invalid merge strategy: octopus");
|
||||
}
|
||||
|
||||
private void assertMergeable(String targetBranch, String source,
|
||||
String strategy) throws Exception {
|
||||
MergeableInfo
|
||||
mergeableInfo = getMergeableInfo(targetBranch, source, strategy);
|
||||
assertThat(mergeableInfo.mergeable).isTrue();
|
||||
}
|
||||
|
||||
private void assertUnMergeable(String targetBranch, String source,
|
||||
String strategy, String... conflicts) throws Exception {
|
||||
MergeableInfo mergeableInfo = getMergeableInfo(targetBranch, source, strategy);
|
||||
assertThat(mergeableInfo.mergeable).isFalse();
|
||||
assertThat(mergeableInfo.conflicts).containsExactly((Object[]) conflicts);
|
||||
}
|
||||
|
||||
private void assertCommitMerged(String targetBranch, String source,
|
||||
String strategy) throws Exception {
|
||||
MergeableInfo
|
||||
mergeableInfo = getMergeableInfo(targetBranch, source, strategy);
|
||||
assertThat(mergeableInfo.mergeable).isTrue();
|
||||
assertThat(mergeableInfo.commitMerged).isTrue();
|
||||
}
|
||||
|
||||
private void assertContentMerged(String targetBranch, String source,
|
||||
String strategy) throws Exception {
|
||||
MergeableInfo
|
||||
mergeableInfo = getMergeableInfo(targetBranch, source, strategy);
|
||||
assertThat(mergeableInfo.mergeable).isTrue();
|
||||
assertThat(mergeableInfo.contentMerged).isTrue();
|
||||
}
|
||||
|
||||
private void assertBadRequest(String targetBranch, String source,
|
||||
String strategy, String errMsg) throws Exception {
|
||||
String url = "/projects/" + project.get() + "/branches/" + targetBranch;
|
||||
url += "/mergeable?source=" + source;
|
||||
if (!Strings.isNullOrEmpty(strategy)) {
|
||||
url += "&strategy=" + strategy;
|
||||
}
|
||||
|
||||
RestResponse r = userRestSession.get(url);
|
||||
r.assertBadRequest();
|
||||
assertThat(r.getEntityContent()).isEqualTo(errMsg);
|
||||
}
|
||||
|
||||
private MergeableInfo getMergeableInfo(String targetBranch, String source,
|
||||
String strategy) throws Exception {
|
||||
String url = "/projects/" + project.get() + "/branches/" + targetBranch;
|
||||
url += "/mergeable?source=" + source;
|
||||
if (!Strings.isNullOrEmpty(strategy)) {
|
||||
url += "&strategy=" + strategy;
|
||||
}
|
||||
|
||||
RestResponse r = userRestSession.get(url);
|
||||
r.assertOK();
|
||||
MergeableInfo result = newGson().fromJson(r.getReader(), MergeableInfo.class);
|
||||
r.consume();
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -22,6 +22,8 @@ public class MergeableInfo {
|
||||
public SubmitType submitType;
|
||||
public String strategy;
|
||||
public boolean mergeable;
|
||||
public boolean commitMerged;
|
||||
public boolean contentMerged;
|
||||
public List<String> conflicts;
|
||||
public List<String> mergeableInto;
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ import com.google.gerrit.common.TimeUtil;
|
||||
import com.google.gerrit.common.data.Capable;
|
||||
import com.google.gerrit.extensions.client.ChangeStatus;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.SubmitType;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
import com.google.gerrit.extensions.common.ChangeInput;
|
||||
import com.google.gerrit.extensions.common.MergeInput;
|
||||
@@ -104,6 +105,7 @@ public class CreateChange implements
|
||||
private final PatchSetUtil psUtil;
|
||||
private final boolean allowDrafts;
|
||||
private final MergeUtil.Factory mergeUtilFactory;
|
||||
private final SubmitType submitType;
|
||||
|
||||
@Inject
|
||||
CreateChange(@AnonymousCowardName String anonymousCowardName,
|
||||
@@ -135,6 +137,8 @@ public class CreateChange implements
|
||||
this.updateFactory = updateFactory;
|
||||
this.psUtil = psUtil;
|
||||
this.allowDrafts = config.getBoolean("change", "allowDrafts", true);
|
||||
this.submitType = config
|
||||
.getEnum("project", null, "submitType", SubmitType.MERGE_IF_NECESSARY);
|
||||
this.mergeUtilFactory = mergeUtilFactory;
|
||||
}
|
||||
|
||||
@@ -241,6 +245,11 @@ public class CreateChange implements
|
||||
RevCommit c;
|
||||
if (input.merge != null) {
|
||||
// create a merge commit
|
||||
if (!(submitType.equals(SubmitType.MERGE_ALWAYS) ||
|
||||
submitType.equals(SubmitType.MERGE_IF_NECESSARY))) {
|
||||
throw new BadRequestException(
|
||||
"Submit type: " + submitType + " is not supported");
|
||||
}
|
||||
c = newMergeCommit(git, oi, rw, rsrc.getControl(), mergeTip, input.merge,
|
||||
author, commitMessage);
|
||||
} else {
|
||||
@@ -268,6 +277,8 @@ public class CreateChange implements
|
||||
}
|
||||
ChangeJson json = jsonFactory.create(ChangeJson.NO_OPTIONS);
|
||||
return Response.created(json.format(ins.getChange()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BadRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,9 +319,8 @@ public class CreateChange implements
|
||||
Strings.emptyToNull(merge.strategy),
|
||||
mergeUtil.mergeStrategyName());
|
||||
|
||||
return rw.parseCommit(MergeUtil
|
||||
.createMergeCommit(repo, oi, mergeTip, sourceCommit, mergeStrategy,
|
||||
authorIdent, commitMessage, rw));
|
||||
return MergeUtil.createMergeCommit(repo, oi, mergeTip, sourceCommit,
|
||||
mergeStrategy, authorIdent, commitMessage, rw);
|
||||
}
|
||||
|
||||
private static ObjectId insert(ObjectInserter inserter,
|
||||
|
@@ -0,0 +1,27 @@
|
||||
// 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.git;
|
||||
|
||||
/**
|
||||
* Indicates that the change or commit is already in the source tree.
|
||||
*/
|
||||
public class ChangeAlreadyMergedException extends MergeIdenticalTreeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** @param msg message to return to the client describing the error. */
|
||||
public ChangeAlreadyMergedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@@ -18,13 +18,13 @@ import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
|
||||
/**
|
||||
* Indicates that the commit is already contained in destination branch.
|
||||
*
|
||||
* Either the commit itself is in the source tree, or the content is merged
|
||||
*/
|
||||
public class MergeIdenticalTreeException extends RestApiException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** @param msg message to return to the client describing the error. */
|
||||
public MergeIdenticalTreeException(String msg) {
|
||||
super(msg, null);
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
@@ -208,14 +208,14 @@ public class MergeUtil {
|
||||
throw new MergeConflictException("merge conflict");
|
||||
}
|
||||
|
||||
public static ObjectId createMergeCommit(Repository repo, ObjectInserter inserter,
|
||||
public static RevCommit createMergeCommit(Repository repo, ObjectInserter inserter,
|
||||
RevCommit mergeTip, RevCommit originalCommit, String mergeStrategy,
|
||||
PersonIdent committerIndent, String commitMsg, RevWalk rw)
|
||||
throws IOException, MergeIdenticalTreeException, MergeConflictException {
|
||||
|
||||
if (rw.isMergedInto(originalCommit, mergeTip)) {
|
||||
throw new MergeIdenticalTreeException(
|
||||
"merge identical tree: change(s) has been already merged!");
|
||||
throw new ChangeAlreadyMergedException(
|
||||
"'" + originalCommit.getName() + "' has already been merged");
|
||||
}
|
||||
|
||||
Merger m = newMerger(repo, inserter, mergeStrategy);
|
||||
@@ -228,7 +228,7 @@ public class MergeUtil {
|
||||
mergeCommit.setAuthor(committerIndent);
|
||||
mergeCommit.setCommitter(committerIndent);
|
||||
mergeCommit.setMessage(commitMsg);
|
||||
return inserter.insert(mergeCommit);
|
||||
return rw.parseCommit(inserter.insert(mergeCommit));
|
||||
}
|
||||
List<String> conflicts = ImmutableList.of();
|
||||
if (m instanceof ResolveMerger) {
|
||||
@@ -746,7 +746,12 @@ public class MergeUtil {
|
||||
public static RevCommit resolveCommit(Repository repo, RevWalk rw, String str)
|
||||
throws BadRequestException, ResourceNotFoundException, IOException {
|
||||
try {
|
||||
return rw.parseCommit(repo.resolve(str));
|
||||
ObjectId commitId = repo.resolve(str);
|
||||
if (commitId == null) {
|
||||
throw new BadRequestException(
|
||||
"Cannot resolve '" + str + "' to a commit");
|
||||
}
|
||||
return rw.parseCommit(commitId);
|
||||
} catch (AmbiguousObjectException | IncorrectObjectTypeException |
|
||||
RevisionSyntaxException e) {
|
||||
throw new BadRequestException(e.getMessage());
|
||||
|
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.server.project;
|
||||
|
||||
import com.google.gerrit.extensions.client.SubmitType;
|
||||
import com.google.gerrit.extensions.common.MergeableInfo;
|
||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||
@@ -45,6 +46,7 @@ public class CheckMergeability implements RestReadView<BranchResource> {
|
||||
|
||||
private String source;
|
||||
private String strategy;
|
||||
private SubmitType submitType;
|
||||
private final Provider<ReviewDb> db;
|
||||
|
||||
@Option(name = "--source", metaVar = "COMMIT",
|
||||
@@ -71,13 +73,22 @@ public class CheckMergeability implements RestReadView<BranchResource> {
|
||||
Provider<ReviewDb> db) {
|
||||
this.gitManager = gitManager;
|
||||
this.strategy = MergeUtil.getMergeStrategy(cfg).getName();
|
||||
this.submitType = cfg.getEnum("project", null, "submitType",
|
||||
SubmitType.MERGE_IF_NECESSARY);
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MergeableInfo apply(BranchResource resource)
|
||||
throws IOException, BadRequestException, ResourceNotFoundException {
|
||||
if (!(submitType.equals(SubmitType.MERGE_ALWAYS) ||
|
||||
submitType.equals(SubmitType.MERGE_IF_NECESSARY))) {
|
||||
throw new BadRequestException(
|
||||
"Submit type: " + submitType + " is not supported");
|
||||
}
|
||||
|
||||
MergeableInfo result = new MergeableInfo();
|
||||
result.submitType = submitType;
|
||||
result.strategy = strategy;
|
||||
try (Repository git = gitManager.openRepository(resource.getNameKey());
|
||||
RevWalk rw = new RevWalk(git);
|
||||
@@ -97,10 +108,25 @@ public class CheckMergeability implements RestReadView<BranchResource> {
|
||||
"do not have read permission for: " + source);
|
||||
}
|
||||
|
||||
result.mergeable = m.merge(targetCommit, sourceCommit);
|
||||
if (m instanceof ResolveMerger) {
|
||||
result.conflicts = ((ResolveMerger) m).getUnmergedPaths();
|
||||
if (rw.isMergedInto(sourceCommit, targetCommit)) {
|
||||
result.mergeable = true;
|
||||
result.commitMerged = true;
|
||||
result.contentMerged = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (m.merge(false, targetCommit, sourceCommit)) {
|
||||
result.mergeable = true;
|
||||
result.commitMerged = false;
|
||||
result.contentMerged = m.getResultTreeId().equals(targetCommit.getTree());
|
||||
} else {
|
||||
result.mergeable = false;
|
||||
if (m instanceof ResolveMerger) {
|
||||
result.conflicts = ((ResolveMerger) m).getUnmergedPaths();
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BadRequestException(e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
Reference in New Issue
Block a user