Line data Source code
1 : // Copyright (C) 2012 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.submit; 16 : 17 : import com.google.common.flogger.FluentLogger; 18 : import com.google.gerrit.entities.BranchNameKey; 19 : import com.google.gerrit.exceptions.StorageException; 20 : import com.google.gerrit.server.CurrentUser; 21 : import com.google.gerrit.server.git.CodeReviewCommit; 22 : import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk; 23 : import com.google.gerrit.server.query.change.ChangeData; 24 : import com.google.gerrit.server.query.change.InternalChangeQuery; 25 : import com.google.inject.Provider; 26 : import java.io.IOException; 27 : import java.util.ArrayList; 28 : import java.util.Collection; 29 : import java.util.Collections; 30 : import java.util.HashSet; 31 : import java.util.Iterator; 32 : import java.util.List; 33 : import java.util.Set; 34 : import org.eclipse.jgit.revwalk.RevCommit; 35 : import org.eclipse.jgit.revwalk.RevFlag; 36 : 37 : public class RebaseSorter { 38 53 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 39 : 40 : private final CurrentUser caller; 41 : private final CodeReviewRevWalk rw; 42 : private final RevFlag canMergeFlag; 43 : private final RevCommit initialTip; 44 : private final Set<RevCommit> alreadyAccepted; 45 : private final Provider<InternalChangeQuery> queryProvider; 46 : private final Set<CodeReviewCommit> incoming; 47 : 48 : public RebaseSorter( 49 : CurrentUser caller, 50 : CodeReviewRevWalk rw, 51 : RevCommit initialTip, 52 : Set<RevCommit> alreadyAccepted, 53 : RevFlag canMergeFlag, 54 : Provider<InternalChangeQuery> queryProvider, 55 53 : Set<CodeReviewCommit> incoming) { 56 53 : this.caller = caller; 57 53 : this.rw = rw; 58 53 : this.canMergeFlag = canMergeFlag; 59 53 : this.initialTip = initialTip; 60 53 : this.alreadyAccepted = alreadyAccepted; 61 53 : this.queryProvider = queryProvider; 62 53 : this.incoming = incoming; 63 53 : } 64 : 65 : public List<CodeReviewCommit> sort(Collection<CodeReviewCommit> toSort) throws IOException { 66 5 : final List<CodeReviewCommit> sorted = new ArrayList<>(); 67 5 : final Set<CodeReviewCommit> sort = new HashSet<>(toSort); 68 5 : while (!sort.isEmpty()) { 69 5 : final CodeReviewCommit n = removeOne(sort); 70 : 71 5 : rw.resetRetain(canMergeFlag); 72 5 : rw.markStart(n); 73 5 : if (initialTip != null) { 74 5 : rw.markUninteresting(initialTip); 75 : } 76 : 77 : CodeReviewCommit c; 78 5 : final List<CodeReviewCommit> contents = new ArrayList<>(); 79 5 : while ((c = rw.next()) != null) { 80 5 : if (!c.has(canMergeFlag) || !incoming.contains(c)) { 81 3 : if (isAlreadyMerged(c, n.change().getDest())) { 82 3 : rw.markUninteresting(c); 83 : } else { 84 : // We cannot merge n as it would bring something we 85 : // aren't permitted to merge at this time. Drop n. 86 : // 87 2 : n.setStatusCode(CommitMergeStatus.MISSING_DEPENDENCY); 88 2 : n.setStatusMessage( 89 2 : CommitMergeStatus.createMissingDependencyMessage( 90 2 : caller, queryProvider, n.name(), c.name())); 91 : } 92 : // Stop RevWalk because c is either a merged commit or a missing 93 : // dependency. Not need to walk further. 94 2 : break; 95 : } 96 5 : contents.add(c); 97 : } 98 : 99 5 : if (n.getStatusCode() == CommitMergeStatus.MISSING_DEPENDENCY) { 100 2 : continue; 101 : } 102 : 103 5 : sort.removeAll(contents); 104 5 : Collections.reverse(contents); 105 5 : sorted.removeAll(contents); 106 5 : sorted.addAll(contents); 107 5 : } 108 5 : return sorted; 109 : } 110 : 111 : private boolean isAlreadyMerged(CodeReviewCommit commit, BranchNameKey dest) throws IOException { 112 3 : try (CodeReviewRevWalk mirw = CodeReviewCommit.newRevWalk(rw.getObjectReader())) { 113 3 : mirw.reset(); 114 3 : mirw.markStart(commit); 115 : // check if the commit is merged in other branches 116 3 : for (RevCommit accepted : alreadyAccepted) { 117 3 : if (mirw.isMergedInto(mirw.parseCommit(commit), mirw.parseCommit(accepted))) { 118 3 : logger.atFine().log( 119 3 : "Dependency %s merged into branch head %s.", commit.getName(), accepted.getName()); 120 3 : return true; 121 : } 122 3 : } 123 : 124 : // check if the commit associated change is merged in the same branch 125 2 : List<ChangeData> changes = queryProvider.get().byCommit(commit); 126 2 : for (ChangeData change : changes) { 127 2 : if (change.change().isMerged() && change.change().getDest().equals(dest)) { 128 2 : logger.atFine().log( 129 2 : "Dependency %s associated with merged change %s.", commit.getName(), change.getId()); 130 2 : return true; 131 : } 132 2 : } 133 2 : return false; 134 3 : } catch (StorageException e) { 135 0 : throw new IOException(e); 136 : } 137 : } 138 : 139 : private static <T> T removeOne(Collection<T> c) { 140 5 : final Iterator<T> i = c.iterator(); 141 5 : final T r = i.next(); 142 5 : i.remove(); 143 5 : return r; 144 : } 145 : }