Line data Source code
1 : // Copyright (C) 2016 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 static com.google.common.base.Preconditions.checkState; 18 : import static java.util.Objects.requireNonNull; 19 : 20 : import com.google.common.base.CharMatcher; 21 : import com.google.common.collect.ImmutableSet; 22 : import com.google.common.flogger.FluentLogger; 23 : import com.google.gerrit.entities.Change; 24 : import com.google.gerrit.extensions.api.changes.SubmitInput; 25 : import com.google.gerrit.extensions.restapi.ResourceConflictException; 26 : import com.google.gerrit.server.change.TestSubmitInput; 27 : import com.google.gerrit.server.git.CodeReviewCommit; 28 : import com.google.gerrit.server.submit.MergeOp.CommitStatus; 29 : import com.google.gerrit.server.update.BatchUpdateListener; 30 : import java.util.ArrayList; 31 : import java.util.Collection; 32 : import java.util.List; 33 : import java.util.Set; 34 : import org.eclipse.jgit.revwalk.RevCommit; 35 : 36 : public class SubmitStrategyListener implements BatchUpdateListener { 37 53 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 38 : 39 : private final Collection<SubmitStrategy> strategies; 40 : private final CommitStatus commitStatus; 41 : private final boolean failAfterRefUpdates; 42 : 43 : public SubmitStrategyListener( 44 53 : SubmitInput input, Collection<SubmitStrategy> strategies, CommitStatus commitStatus) { 45 53 : this.strategies = strategies; 46 53 : this.commitStatus = commitStatus; 47 53 : if (input instanceof TestSubmitInput) { 48 8 : failAfterRefUpdates = ((TestSubmitInput) input).failAfterRefUpdates; 49 : } else { 50 53 : failAfterRefUpdates = false; 51 : } 52 53 : } 53 : 54 : @Override 55 : public void afterUpdateRepos() throws ResourceConflictException { 56 53 : markCleanMerges(); 57 53 : List<Change.Id> alreadyMerged = checkCommitStatus(); 58 53 : findUnmergedChanges(alreadyMerged); 59 53 : } 60 : 61 : @Override 62 : public void afterUpdateRefs() throws ResourceConflictException { 63 53 : if (failAfterRefUpdates) { 64 0 : throw new ResourceConflictException("Failing after ref updates"); 65 : } 66 53 : } 67 : 68 : private void findUnmergedChanges(List<Change.Id> alreadyMerged) throws ResourceConflictException { 69 53 : for (SubmitStrategy strategy : strategies) { 70 53 : if (strategy instanceof CherryPick) { 71 : // Can't do this sanity check for CherryPick since: 72 : // * CherryPick might have picked a subset of changes 73 : // * CherryPick might have status SKIPPED_IDENTICAL_TREE 74 7 : continue; 75 : } 76 53 : SubmitStrategy.Arguments args = strategy.args; 77 53 : Set<Change.Id> unmerged = 78 53 : args.mergeUtil.findUnmergedChanges( 79 53 : args.commitStatus.getChangeIds(args.destBranch), 80 : args.rw, 81 : args.canMergeFlag, 82 53 : args.mergeTip.getInitialTip(), 83 53 : args.mergeTip.getCurrentTip(), 84 : alreadyMerged); 85 53 : checkState(unmerged.isEmpty(), "changes not reachable from new branch tip: %s", unmerged); 86 53 : } 87 53 : commitStatus.maybeFailVerbose(); 88 53 : } 89 : 90 : private void markCleanMerges() { 91 53 : for (SubmitStrategy strategy : strategies) { 92 53 : SubmitStrategy.Arguments args = strategy.args; 93 53 : RevCommit initialTip = args.mergeTip.getInitialTip(); 94 53 : args.mergeUtil.markCleanMerges( 95 : args.rw, 96 : args.canMergeFlag, 97 53 : args.mergeTip.getCurrentTip(), 98 53 : initialTip == null ? ImmutableSet.of() : ImmutableSet.of(initialTip)); 99 53 : } 100 53 : } 101 : 102 : private List<Change.Id> checkCommitStatus() throws ResourceConflictException { 103 53 : List<Change.Id> alreadyMerged = new ArrayList<>(commitStatus.getChangeIds().size()); 104 53 : for (Change.Id id : commitStatus.getChangeIds()) { 105 53 : CodeReviewCommit commit = commitStatus.get(id); 106 53 : CommitMergeStatus s = commit != null ? commit.getStatusCode() : null; 107 53 : requireNonNull( 108 53 : s, String.format("change %d: change not processed by merge strategy", id.get())); 109 : 110 53 : if (commit.getStatusMessage().isPresent()) { 111 5 : logger.atFine().log( 112 : "change %d: Status for commit %s is %s. %s", 113 5 : id.get(), commit.name(), s, commit.getStatusMessage().get()); 114 : } else { 115 53 : logger.atFine().log("change %d: Status for commit %s is %s.", id.get(), commit.name(), s); 116 : } 117 53 : switch (s) { 118 : case CLEAN_MERGE: 119 : case CLEAN_REBASE: 120 : case CLEAN_PICK: 121 : case SKIPPED_IDENTICAL_TREE: 122 53 : break; // Merge strategy accepted this change. 123 : 124 : case ALREADY_MERGED: 125 : // Already an ancestor of tip. 126 7 : alreadyMerged.add(commit.getPatchsetId().changeId()); 127 7 : break; 128 : 129 : case PATH_CONFLICT: 130 : case REBASE_MERGE_CONFLICT: 131 : case MANUAL_RECURSIVE_MERGE: 132 : case CANNOT_CHERRY_PICK_ROOT: 133 : case CANNOT_REBASE_ROOT: 134 : case NOT_FAST_FORWARD: 135 : case EMPTY_COMMIT: 136 : case MISSING_DEPENDENCY: 137 : case FAST_FORWARD_INDEPENDENT_CHANGES: 138 : // TODO(dborowitz): Reformat these messages to be more appropriate for 139 : // short problem descriptions. 140 8 : String message = s.getDescription(); 141 8 : if (commit.getStatusMessage().isPresent()) { 142 5 : message += " " + commit.getStatusMessage().get(); 143 : } 144 8 : commitStatus.problem(id, CharMatcher.is('\n').collapseFrom(message, ' ')); 145 8 : break; 146 : 147 : default: 148 0 : commitStatus.problem(id, "unspecified merge failure: " + s); 149 : break; 150 : } 151 53 : } 152 53 : commitStatus.maybeFailVerbose(); 153 53 : return alreadyMerged; 154 : } 155 : 156 : @Override 157 : public void afterUpdateChanges() throws ResourceConflictException { 158 53 : commitStatus.maybeFail("Error updating status"); 159 53 : } 160 : }