Fix submittabilty of merge commits that resolve conflicts
Change implementation of 2.12 fix [1] to take into account note-db. [1] https://gerrit-review.googlesource.com/#/c/74461/ Bug: Issue 3811 Change-Id: Iafd5986ce20449dd380f6d274fbd9cb30d1cdd98
This commit is contained in:
committed by
David Pursehouse
parent
44d9c9926a
commit
10f39fb518
@@ -0,0 +1,363 @@
|
|||||||
|
// 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.change;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static com.google.common.truth.TruthJUnit.assume;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
|
import com.google.gerrit.acceptance.NoHttpd;
|
||||||
|
import com.google.gerrit.acceptance.PushOneCommit;
|
||||||
|
import com.google.gerrit.extensions.client.ChangeStatus;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
import com.google.gerrit.server.change.Submit;
|
||||||
|
import com.google.gerrit.server.git.ChangeSet;
|
||||||
|
import com.google.gerrit.server.git.MergeSuperSet;
|
||||||
|
import com.google.gerrit.server.query.change.ChangeData;
|
||||||
|
import com.google.gerrit.testutil.ConfigSuite;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||||
|
import org.eclipse.jgit.errors.MissingObjectException;
|
||||||
|
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
|
||||||
|
import org.eclipse.jgit.junit.TestRepository;
|
||||||
|
import org.eclipse.jgit.lib.Config;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@NoHttpd
|
||||||
|
public class SubmitResolvingMergeCommitIT extends AbstractDaemonTest {
|
||||||
|
@Inject
|
||||||
|
private MergeSuperSet mergeSuperSet;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Submit submit;
|
||||||
|
|
||||||
|
@ConfigSuite.Default
|
||||||
|
public static Config submitWholeTopicEnabled() {
|
||||||
|
return submitWholeTopicEnabledConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolvingMergeCommitAtEndOfChain() throws Exception {
|
||||||
|
/*
|
||||||
|
A <- B <- C <------- D
|
||||||
|
^ ^
|
||||||
|
| |
|
||||||
|
E <- F <- G <- H <-- M*
|
||||||
|
|
||||||
|
G has a conflict with C and is resolved in M which is a merge
|
||||||
|
commit of H and D.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PushOneCommit.Result a = createChange("A");
|
||||||
|
PushOneCommit.Result b = createChange("B", "new.txt", "No conflict line",
|
||||||
|
ImmutableList.of(a.getCommit()));
|
||||||
|
PushOneCommit.Result c = createChange("C", ImmutableList.of(b.getCommit()));
|
||||||
|
PushOneCommit.Result d = createChange("D", ImmutableList.of(c.getCommit()));
|
||||||
|
|
||||||
|
PushOneCommit.Result e = createChange("E", ImmutableList.of(a.getCommit()));
|
||||||
|
PushOneCommit.Result f = createChange("F", ImmutableList.of(e.getCommit()));
|
||||||
|
PushOneCommit.Result g = createChange("G", "new.txt", "Conflicting line",
|
||||||
|
ImmutableList.of(f.getCommit()));
|
||||||
|
PushOneCommit.Result h = createChange("H", ImmutableList.of(g.getCommit()));
|
||||||
|
|
||||||
|
approve(a.getChangeId());
|
||||||
|
approve(b.getChangeId());
|
||||||
|
approve(c.getChangeId());
|
||||||
|
approve(d.getChangeId());
|
||||||
|
submit(d.getChangeId());
|
||||||
|
|
||||||
|
approve(e.getChangeId());
|
||||||
|
approve(f.getChangeId());
|
||||||
|
approve(g.getChangeId());
|
||||||
|
approve(h.getChangeId());
|
||||||
|
|
||||||
|
assertMergeable(e.getChange(), true);
|
||||||
|
assertMergeable(f.getChange(), true);
|
||||||
|
assertMergeable(g.getChange(), false);
|
||||||
|
assertMergeable(h.getChange(), false);
|
||||||
|
|
||||||
|
PushOneCommit.Result m = createChange("M", "new.txt", "Resolved conflict",
|
||||||
|
ImmutableList.of(d.getCommit(), h.getCommit()));
|
||||||
|
approve(m.getChangeId());
|
||||||
|
|
||||||
|
assertChangeSetMergeable(m.getChange(), true);
|
||||||
|
|
||||||
|
assertMergeable(m.getChange(), true);
|
||||||
|
submit(m.getChangeId());
|
||||||
|
|
||||||
|
assertMerged(e.getChangeId());
|
||||||
|
assertMerged(f.getChangeId());
|
||||||
|
assertMerged(g.getChangeId());
|
||||||
|
assertMerged(h.getChangeId());
|
||||||
|
assertMerged(m.getChangeId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolvingMergeCommitComingBeforeConflict() throws Exception {
|
||||||
|
/*
|
||||||
|
A <- B <- C <- D
|
||||||
|
^ ^
|
||||||
|
| |
|
||||||
|
E <- F* <- G
|
||||||
|
|
||||||
|
F is a merge commit of E and B and resolves any conflict.
|
||||||
|
However G is conflicting with C.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PushOneCommit.Result a = createChange("A");
|
||||||
|
PushOneCommit.Result b = createChange("B", "new.txt", "No conflict line",
|
||||||
|
ImmutableList.of(a.getCommit()));
|
||||||
|
PushOneCommit.Result c = createChange("C", "new.txt", "No conflict line #2",
|
||||||
|
ImmutableList.of(b.getCommit()));
|
||||||
|
PushOneCommit.Result d = createChange("D", ImmutableList.of(c.getCommit()));
|
||||||
|
PushOneCommit.Result e = createChange("E", "new.txt", "Conflicting line",
|
||||||
|
ImmutableList.of(a.getCommit()));
|
||||||
|
PushOneCommit.Result f = createChange("F", "new.txt", "Resolved conflict",
|
||||||
|
ImmutableList.of(b.getCommit(), e.getCommit()));
|
||||||
|
PushOneCommit.Result g = createChange("G", "new.txt", "Conflicting line #2",
|
||||||
|
ImmutableList.of(f.getCommit()));
|
||||||
|
|
||||||
|
assertMergeable(e.getChange(), true);
|
||||||
|
|
||||||
|
approve(a.getChangeId());
|
||||||
|
approve(b.getChangeId());
|
||||||
|
submit(b.getChangeId());
|
||||||
|
|
||||||
|
assertMergeable(e.getChange(), false);
|
||||||
|
assertMergeable(f.getChange(), true);
|
||||||
|
assertMergeable(g.getChange(), true);
|
||||||
|
|
||||||
|
approve(c.getChangeId());
|
||||||
|
approve(d.getChangeId());
|
||||||
|
submit(d.getChangeId());
|
||||||
|
|
||||||
|
approve(e.getChangeId());
|
||||||
|
approve(f.getChangeId());
|
||||||
|
approve(g.getChangeId());
|
||||||
|
|
||||||
|
assertMergeable(g.getChange(), false);
|
||||||
|
assertChangeSetMergeable(g.getChange(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolvingMergeCommitWithTopics() throws Exception {
|
||||||
|
/*
|
||||||
|
Project1:
|
||||||
|
A <- B <-- C <---
|
||||||
|
^ ^ |
|
||||||
|
| | |
|
||||||
|
E <- F* <- G <- L*
|
||||||
|
|
||||||
|
G clashes with C, and F resolves the clashes between E and B.
|
||||||
|
Later, L resolves the clashes between C and G.
|
||||||
|
|
||||||
|
Project2:
|
||||||
|
H <- I
|
||||||
|
^ ^
|
||||||
|
| |
|
||||||
|
J <- K*
|
||||||
|
|
||||||
|
J clashes with I, and K resolves all problems.
|
||||||
|
G, K and L are in the same topic.
|
||||||
|
*/
|
||||||
|
assume().that(isSubmitWholeTopicEnabled()).isTrue();
|
||||||
|
|
||||||
|
String project1Name = name("Project1");
|
||||||
|
String project2Name = name("Project2");
|
||||||
|
gApi.projects().create(project1Name);
|
||||||
|
gApi.projects().create(project2Name);
|
||||||
|
TestRepository<InMemoryRepository> project1 =
|
||||||
|
cloneProject(new Project.NameKey(project1Name));
|
||||||
|
TestRepository<InMemoryRepository> project2 =
|
||||||
|
cloneProject(new Project.NameKey(project2Name));
|
||||||
|
|
||||||
|
PushOneCommit.Result a = createChange(project1, "A");
|
||||||
|
PushOneCommit.Result b = createChange(project1, "B", "new.txt",
|
||||||
|
"No conflict line", ImmutableList.of(a.getCommit()));
|
||||||
|
PushOneCommit.Result c = createChange(project1, "C", "new.txt",
|
||||||
|
"No conflict line #2", ImmutableList.of(b.getCommit()));
|
||||||
|
|
||||||
|
approve(a.getChangeId());
|
||||||
|
approve(b.getChangeId());
|
||||||
|
approve(c.getChangeId());
|
||||||
|
submit(c.getChangeId());
|
||||||
|
|
||||||
|
PushOneCommit.Result e = createChange(project1, "E", "new.txt",
|
||||||
|
"Conflicting line", ImmutableList.of(a.getCommit()));
|
||||||
|
PushOneCommit.Result f = createChange(project1, "F", "new.txt",
|
||||||
|
"Resolved conflict", ImmutableList.of(b.getCommit(), e.getCommit()));
|
||||||
|
PushOneCommit.Result g = createChange(project1, "G", "new.txt",
|
||||||
|
"Conflicting line #2", ImmutableList.of(f.getCommit()),
|
||||||
|
"refs/for/master/" + name("topic1"));
|
||||||
|
|
||||||
|
PushOneCommit.Result h = createChange(project2, "H");
|
||||||
|
PushOneCommit.Result i = createChange(project2, "I", "new.txt",
|
||||||
|
"No conflict line", ImmutableList.of(h.getCommit()));
|
||||||
|
PushOneCommit.Result j = createChange(project2, "J", "new.txt",
|
||||||
|
"Conflicting line", ImmutableList.of(h.getCommit()));
|
||||||
|
PushOneCommit.Result k =
|
||||||
|
createChange(project2, "K", "new.txt", "Sadly conflicting topic-wise",
|
||||||
|
ImmutableList.of(i.getCommit(), j.getCommit()),
|
||||||
|
"refs/for/master/" + name("topic1"));
|
||||||
|
|
||||||
|
approve(h.getChangeId());
|
||||||
|
approve(i.getChangeId());
|
||||||
|
submit(i.getChangeId());
|
||||||
|
|
||||||
|
approve(e.getChangeId());
|
||||||
|
approve(f.getChangeId());
|
||||||
|
approve(g.getChangeId());
|
||||||
|
approve(j.getChangeId());
|
||||||
|
approve(k.getChangeId());
|
||||||
|
|
||||||
|
assertChangeSetMergeable(g.getChange(), false);
|
||||||
|
assertChangeSetMergeable(k.getChange(), false);
|
||||||
|
|
||||||
|
PushOneCommit.Result l =
|
||||||
|
createChange(project1, "L", "new.txt", "Resolving conflicts again",
|
||||||
|
ImmutableList.of(c.getCommit(), g.getCommit()),
|
||||||
|
"refs/for/master/" + name("topic1"));
|
||||||
|
|
||||||
|
approve(l.getChangeId());
|
||||||
|
assertChangeSetMergeable(l.getChange(), true);
|
||||||
|
|
||||||
|
submit(l.getChangeId());
|
||||||
|
assertMerged(c.getChangeId());
|
||||||
|
assertMerged(g.getChangeId());
|
||||||
|
assertMerged(k.getChangeId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolvingMergeCommitAtEndOfChainAndNotUpToDate() throws Exception {
|
||||||
|
/*
|
||||||
|
A <-- B
|
||||||
|
\
|
||||||
|
C <- D
|
||||||
|
\ /
|
||||||
|
E
|
||||||
|
|
||||||
|
B is the target branch, and D should be merged with B, but one
|
||||||
|
of C conflicts with B
|
||||||
|
*/
|
||||||
|
|
||||||
|
PushOneCommit.Result a = createChange("A");
|
||||||
|
PushOneCommit.Result b = createChange("B", "new.txt", "No conflict line",
|
||||||
|
ImmutableList.of(a.getCommit()));
|
||||||
|
|
||||||
|
approve(a.getChangeId());
|
||||||
|
approve(b.getChangeId());
|
||||||
|
submit(b.getChangeId());
|
||||||
|
|
||||||
|
PushOneCommit.Result c = createChange("C", "new.txt", "Create conflicts",
|
||||||
|
ImmutableList.of(a.getCommit()));
|
||||||
|
PushOneCommit.Result e = createChange("E", ImmutableList.of(c.getCommit()));
|
||||||
|
PushOneCommit.Result d = createChange("D", "new.txt", "Resolves conflicts",
|
||||||
|
ImmutableList.of(c.getCommit(), e.getCommit()));
|
||||||
|
|
||||||
|
approve(c.getChangeId());
|
||||||
|
approve(e.getChangeId());
|
||||||
|
approve(d.getChangeId());
|
||||||
|
assertMergeable(d.getChange(), false);
|
||||||
|
assertChangeSetMergeable(d.getChange(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void submit(String changeId) throws Exception {
|
||||||
|
gApi.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.current()
|
||||||
|
.submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertChangeSetMergeable(ChangeData change, boolean expected)
|
||||||
|
throws MissingObjectException, IncorrectObjectTypeException, IOException,
|
||||||
|
OrmException {
|
||||||
|
ChangeSet cs =
|
||||||
|
mergeSuperSet.completeChangeSet(db, change.change(), user(admin));
|
||||||
|
assertThat(submit.isPatchSetMergeable(cs)).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMergeable(ChangeData change, boolean expected)
|
||||||
|
throws Exception {
|
||||||
|
change.setMergeable(null);
|
||||||
|
assertThat(change.isMergeable()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMerged(String changeId) throws Exception {
|
||||||
|
assertThat(gApi
|
||||||
|
.changes()
|
||||||
|
.id(changeId)
|
||||||
|
.get()
|
||||||
|
.status).isEqualTo(ChangeStatus.MERGED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PushOneCommit.Result createChange(TestRepository<?> repo,
|
||||||
|
String subject, String fileName, String content, List<RevCommit> parents,
|
||||||
|
String ref) throws Exception {
|
||||||
|
PushOneCommit push = pushFactory.create(db, admin.getIdent(), repo,
|
||||||
|
subject, fileName, content);
|
||||||
|
|
||||||
|
if (!parents.isEmpty()) {
|
||||||
|
push.setParents(parents);
|
||||||
|
}
|
||||||
|
|
||||||
|
PushOneCommit.Result result;
|
||||||
|
if (fileName.isEmpty()) {
|
||||||
|
result = push.execute(ref);
|
||||||
|
} else {
|
||||||
|
result = push.to(ref);
|
||||||
|
}
|
||||||
|
result.assertOkStatus();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PushOneCommit.Result createChange(TestRepository<?> repo,
|
||||||
|
String subject) throws Exception {
|
||||||
|
return createChange(repo, subject, "x", "x", new ArrayList<RevCommit>(),
|
||||||
|
"refs/for/master");
|
||||||
|
}
|
||||||
|
|
||||||
|
private PushOneCommit.Result createChange(TestRepository<?> repo,
|
||||||
|
String subject, String fileName, String content, List<RevCommit> parents)
|
||||||
|
throws Exception {
|
||||||
|
return createChange(repo, subject, fileName, content, parents,
|
||||||
|
"refs/for/master");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PushOneCommit.Result createChange(String subject) throws Exception {
|
||||||
|
return createChange(testRepo, subject, "", "",
|
||||||
|
Collections.<RevCommit> emptyList(), "refs/for/master");
|
||||||
|
}
|
||||||
|
|
||||||
|
private PushOneCommit.Result createChange(String subject,
|
||||||
|
List<RevCommit> parents) throws Exception {
|
||||||
|
return createChange(testRepo, subject, "", "", parents, "refs/for/master");
|
||||||
|
}
|
||||||
|
|
||||||
|
private PushOneCommit.Result createChange(String subject, String fileName,
|
||||||
|
String content, List<RevCommit> parents) throws Exception {
|
||||||
|
return createChange(testRepo, subject, fileName, content, parents,
|
||||||
|
"refs/for/master");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,8 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.FluentIterable;
|
import com.google.common.collect.FluentIterable;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import com.google.gerrit.common.data.ParameterizedString;
|
import com.google.gerrit.common.data.ParameterizedString;
|
||||||
import com.google.gerrit.extensions.api.changes.SubmitInput;
|
import com.google.gerrit.extensions.api.changes.SubmitInput;
|
||||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
@@ -29,9 +31,11 @@ import com.google.gerrit.extensions.restapi.RestApiException;
|
|||||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||||
import com.google.gerrit.extensions.webui.UiAction;
|
import com.google.gerrit.extensions.webui.UiAction;
|
||||||
|
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.ChangeMessage;
|
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.client.RevId;
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||||
@@ -58,12 +62,18 @@ import com.google.inject.Singleton;
|
|||||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||||
import org.eclipse.jgit.lib.Config;
|
import org.eclipse.jgit.lib.Config;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
|
public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
|
||||||
@@ -129,6 +139,7 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
|
|||||||
private final ParameterizedString submitTopicTooltip;
|
private final ParameterizedString submitTopicTooltip;
|
||||||
private final boolean submitWholeTopic;
|
private final boolean submitWholeTopic;
|
||||||
private final Provider<InternalChangeQuery> queryProvider;
|
private final Provider<InternalChangeQuery> queryProvider;
|
||||||
|
private final PatchSetUtil psUtil;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Submit(Provider<ReviewDb> dbProvider,
|
Submit(Provider<ReviewDb> dbProvider,
|
||||||
@@ -141,7 +152,8 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
|
|||||||
AccountsCollection accounts,
|
AccountsCollection accounts,
|
||||||
ChangesCollection changes,
|
ChangesCollection changes,
|
||||||
@GerritServerConfig Config cfg,
|
@GerritServerConfig Config cfg,
|
||||||
Provider<InternalChangeQuery> queryProvider) {
|
Provider<InternalChangeQuery> queryProvider,
|
||||||
|
PatchSetUtil psUtil) {
|
||||||
this.dbProvider = dbProvider;
|
this.dbProvider = dbProvider;
|
||||||
this.repoManager = repoManager;
|
this.repoManager = repoManager;
|
||||||
this.changeDataFactory = changeDataFactory;
|
this.changeDataFactory = changeDataFactory;
|
||||||
@@ -173,6 +185,7 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
|
|||||||
cfg.getString("change", null, "submitTopicTooltip"),
|
cfg.getString("change", null, "submitTopicTooltip"),
|
||||||
DEFAULT_TOPIC_TOOLTIP));
|
DEFAULT_TOPIC_TOOLTIP));
|
||||||
this.queryProvider = queryProvider;
|
this.queryProvider = queryProvider;
|
||||||
|
this.psUtil = psUtil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -247,24 +260,18 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
|
|||||||
if (!changeControl.canSubmit()) {
|
if (!changeControl.canSubmit()) {
|
||||||
return BLOCKED_SUBMIT_TOOLTIP;
|
return BLOCKED_SUBMIT_TOOLTIP;
|
||||||
}
|
}
|
||||||
// Recheck mergeability rather than using value stored in the index,
|
|
||||||
// which may be stale.
|
|
||||||
// TODO(dborowitz): This is ugly; consider providing a way to not read
|
|
||||||
// stored fields from the index in the first place.
|
|
||||||
c.setMergeable(null);
|
|
||||||
Boolean mergeable = c.isMergeable();
|
|
||||||
if (mergeable == null) {
|
|
||||||
log.error("Ephemeral error checking if change is submittable");
|
|
||||||
return CLICK_FAILURE_TOOLTIP;
|
|
||||||
}
|
|
||||||
if (!mergeable) {
|
|
||||||
return CHANGES_NOT_MERGEABLE;
|
|
||||||
}
|
|
||||||
MergeOp.checkSubmitRule(c);
|
MergeOp.checkSubmitRule(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Boolean csIsMergeable = isPatchSetMergeable(cs);
|
||||||
|
if (csIsMergeable == null) {
|
||||||
|
return CLICK_FAILURE_TOOLTIP;
|
||||||
|
} else if (!csIsMergeable) {
|
||||||
|
return CHANGES_NOT_MERGEABLE;
|
||||||
|
}
|
||||||
} catch (ResourceConflictException e) {
|
} catch (ResourceConflictException e) {
|
||||||
return BLOCKED_SUBMIT_TOOLTIP;
|
return BLOCKED_SUBMIT_TOOLTIP;
|
||||||
} catch (OrmException e) {
|
} catch (OrmException | IOException e) {
|
||||||
log.error("Error checking if change is submittable", e);
|
log.error("Error checking if change is submittable", e);
|
||||||
throw new OrmRuntimeException("Could not determine problems for the change", e);
|
throw new OrmRuntimeException("Could not determine problems for the change", e);
|
||||||
}
|
}
|
||||||
@@ -400,6 +407,69 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
|
|||||||
return change != null ? change.getStatus().name().toLowerCase() : "deleted";
|
return change != null ? change.getStatus().name().toLowerCase() : "deleted";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean isPatchSetMergeable(ChangeSet cs)
|
||||||
|
throws OrmException, IOException {
|
||||||
|
Map<ChangeData, Boolean> mergeabilityMap = new HashMap<>();
|
||||||
|
for (ChangeData change : cs.changes()) {
|
||||||
|
mergeabilityMap.put(change, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Multimap<Branch.NameKey, ChangeData> cbb = cs.changesByBranch();
|
||||||
|
for (Branch.NameKey branch : cbb.keySet()) {
|
||||||
|
Collection<ChangeData> targetBranch = cbb.get(branch);
|
||||||
|
HashMap<Change.Id, RevCommit> commits =
|
||||||
|
findCommits(targetBranch, branch.getParentKey());
|
||||||
|
|
||||||
|
Set<ObjectId> allParents = Sets.newHashSetWithExpectedSize(cs.size());
|
||||||
|
for (RevCommit commit : commits.values()) {
|
||||||
|
for (RevCommit parent : commit.getParents()) {
|
||||||
|
allParents.add(parent.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ChangeData change : targetBranch) {
|
||||||
|
RevCommit commit = commits.get(change.getId());
|
||||||
|
boolean isMergeCommit = commit.getParentCount() > 1;
|
||||||
|
boolean isLastInChain = !allParents.contains(commit.getId());
|
||||||
|
|
||||||
|
// Recheck mergeability rather than using value stored in the index,
|
||||||
|
// which may be stale.
|
||||||
|
// TODO(dborowitz): This is ugly; consider providing a way to not read
|
||||||
|
// stored fields from the index in the first place.
|
||||||
|
change.setMergeable(null);
|
||||||
|
Boolean mergeable = change.isMergeable();
|
||||||
|
if (mergeable == null) {
|
||||||
|
// Skip whole check, cannot determine if mergeable
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
mergeabilityMap.put(change, mergeable);
|
||||||
|
|
||||||
|
if (isLastInChain && isMergeCommit && mergeable) {
|
||||||
|
for (ChangeData c : targetBranch) {
|
||||||
|
mergeabilityMap.put(c, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !mergeabilityMap.values().contains(Boolean.FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashMap<Change.Id, RevCommit> findCommits(
|
||||||
|
Collection<ChangeData> changes, Project.NameKey project)
|
||||||
|
throws IOException, OrmException {
|
||||||
|
HashMap<Change.Id, RevCommit> commits = new HashMap<>();
|
||||||
|
try (Repository repo = repoManager.openRepository(project);
|
||||||
|
RevWalk walk = new RevWalk(repo)) {
|
||||||
|
for (ChangeData change : changes) {
|
||||||
|
RevCommit commit = walk.parseCommit(ObjectId.fromString(
|
||||||
|
psUtil.current(dbProvider.get(), change.notes()).getRevision().get()));
|
||||||
|
commits.put(change.getId(), commit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commits;
|
||||||
|
}
|
||||||
|
|
||||||
private RevisionResource onBehalfOf(RevisionResource rsrc, SubmitInput in)
|
private RevisionResource onBehalfOf(RevisionResource rsrc, SubmitInput in)
|
||||||
throws AuthException, UnprocessableEntityException, OrmException {
|
throws AuthException, UnprocessableEntityException, OrmException {
|
||||||
ChangeControl caller = rsrc.getControl();
|
ChangeControl caller = rsrc.getControl();
|
||||||
|
|||||||
Reference in New Issue
Block a user