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:
		 Alexandre Philbert
					Alexandre Philbert
				
			
				
					committed by
					
						 David Pursehouse
						David Pursehouse
					
				
			
			
				
	
			
			
			 David Pursehouse
						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.collect.FluentIterable; | ||||
| 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.extensions.api.changes.SubmitInput; | ||||
| 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.UnprocessableEntityException; | ||||
| 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.ChangeMessage; | ||||
| 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.server.ReviewDb; | ||||
| 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.lib.Config; | ||||
| 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.LoggerFactory; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.util.Collection; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
|  | ||||
| @Singleton | ||||
| public class Submit implements RestModifyView<RevisionResource, SubmitInput>, | ||||
| @@ -129,6 +139,7 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>, | ||||
|   private final ParameterizedString submitTopicTooltip; | ||||
|   private final boolean submitWholeTopic; | ||||
|   private final Provider<InternalChangeQuery> queryProvider; | ||||
|   private final PatchSetUtil psUtil; | ||||
|  | ||||
|   @Inject | ||||
|   Submit(Provider<ReviewDb> dbProvider, | ||||
| @@ -141,7 +152,8 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>, | ||||
|       AccountsCollection accounts, | ||||
|       ChangesCollection changes, | ||||
|       @GerritServerConfig Config cfg, | ||||
|       Provider<InternalChangeQuery> queryProvider) { | ||||
|       Provider<InternalChangeQuery> queryProvider, | ||||
|       PatchSetUtil psUtil) { | ||||
|     this.dbProvider = dbProvider; | ||||
|     this.repoManager = repoManager; | ||||
|     this.changeDataFactory = changeDataFactory; | ||||
| @@ -173,6 +185,7 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>, | ||||
|         cfg.getString("change", null, "submitTopicTooltip"), | ||||
|         DEFAULT_TOPIC_TOOLTIP)); | ||||
|     this.queryProvider = queryProvider; | ||||
|     this.psUtil = psUtil; | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
| @@ -247,24 +260,18 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>, | ||||
|         if (!changeControl.canSubmit()) { | ||||
|           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); | ||||
|       } | ||||
|  | ||||
|       Boolean csIsMergeable = isPatchSetMergeable(cs); | ||||
|       if (csIsMergeable == null) { | ||||
|         return CLICK_FAILURE_TOOLTIP; | ||||
|       } else if (!csIsMergeable) { | ||||
|         return CHANGES_NOT_MERGEABLE; | ||||
|       } | ||||
|     } catch (ResourceConflictException e) { | ||||
|       return BLOCKED_SUBMIT_TOOLTIP; | ||||
|     } catch (OrmException e) { | ||||
|     } catch (OrmException | IOException e) { | ||||
|       log.error("Error checking if change is submittable", 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"; | ||||
|   } | ||||
|  | ||||
|   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) | ||||
|       throws AuthException, UnprocessableEntityException, OrmException { | ||||
|     ChangeControl caller = rsrc.getControl(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user