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:
Zhen Chen
2016-07-26 16:54:59 -07:00
parent 93a4c8e42d
commit 8f00d55d11
10 changed files with 522 additions and 75 deletions

View File

@@ -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|

View File

@@ -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
}
----

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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());

View File

@@ -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;
}