diff options
Diffstat (limited to 'java/com/google/gerrit/server/submit/SubmitStrategyListener.java')
-rw-r--r-- | java/com/google/gerrit/server/submit/SubmitStrategyListener.java | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyListener.java b/java/com/google/gerrit/server/submit/SubmitStrategyListener.java new file mode 100644 index 0000000000..d1f847bbd7 --- /dev/null +++ b/java/com/google/gerrit/server/submit/SubmitStrategyListener.java @@ -0,0 +1,165 @@ +// 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.submit; + +import com.google.common.base.CharMatcher; +import com.google.common.collect.ImmutableSet; +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.extensions.api.changes.SubmitInput; +import com.google.gerrit.extensions.restapi.ResourceConflictException; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.server.change.TestSubmitInput; +import com.google.gerrit.server.git.CodeReviewCommit; +import com.google.gerrit.server.submit.MergeOp.CommitStatus; +import com.google.gerrit.server.update.BatchUpdateListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import org.eclipse.jgit.revwalk.RevCommit; + +public class SubmitStrategyListener implements BatchUpdateListener { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + private final Collection<SubmitStrategy> strategies; + private final CommitStatus commitStatus; + private final boolean failAfterRefUpdates; + + public SubmitStrategyListener( + SubmitInput input, Collection<SubmitStrategy> strategies, CommitStatus commitStatus) { + this.strategies = strategies; + this.commitStatus = commitStatus; + if (input instanceof TestSubmitInput) { + failAfterRefUpdates = ((TestSubmitInput) input).failAfterRefUpdates; + } else { + failAfterRefUpdates = false; + } + } + + @Override + public void afterUpdateRepos() throws ResourceConflictException { + try { + markCleanMerges(); + List<Change.Id> alreadyMerged = checkCommitStatus(); + findUnmergedChanges(alreadyMerged); + } catch (IntegrationException e) { + throw new ResourceConflictException(e.getMessage(), e); + } + } + + @Override + public void afterUpdateRefs() throws ResourceConflictException { + if (failAfterRefUpdates) { + throw new ResourceConflictException("Failing after ref updates"); + } + } + + private void findUnmergedChanges(List<Change.Id> alreadyMerged) + throws ResourceConflictException, IntegrationException { + for (SubmitStrategy strategy : strategies) { + if (strategy instanceof CherryPick) { + // Can't do this sanity check for CherryPick since: + // * CherryPick might have picked a subset of changes + // * CherryPick might have status SKIPPED_IDENTICAL_TREE + continue; + } + SubmitStrategy.Arguments args = strategy.args; + Set<Change.Id> unmerged = + args.mergeUtil.findUnmergedChanges( + args.commitStatus.getChangeIds(args.destBranch), + args.rw, + args.canMergeFlag, + args.mergeTip.getInitialTip(), + args.mergeTip.getCurrentTip(), + alreadyMerged); + for (Change.Id id : unmerged) { + commitStatus.problem(id, "internal error: change not reachable from new branch tip"); + } + } + commitStatus.maybeFailVerbose(); + } + + private void markCleanMerges() throws IntegrationException { + for (SubmitStrategy strategy : strategies) { + SubmitStrategy.Arguments args = strategy.args; + RevCommit initialTip = args.mergeTip.getInitialTip(); + args.mergeUtil.markCleanMerges( + args.rw, + args.canMergeFlag, + args.mergeTip.getCurrentTip(), + initialTip == null ? ImmutableSet.<RevCommit>of() : ImmutableSet.of(initialTip)); + } + } + + private List<Change.Id> checkCommitStatus() throws ResourceConflictException { + List<Change.Id> alreadyMerged = new ArrayList<>(commitStatus.getChangeIds().size()); + for (Change.Id id : commitStatus.getChangeIds()) { + CodeReviewCommit commit = commitStatus.get(id); + CommitMergeStatus s = commit != null ? commit.getStatusCode() : null; + if (s == null) { + logger.atSevere().log("change %d: change not processed by merge strategy", id.get()); + commitStatus.problem(id, "internal error: change not processed by merge strategy"); + continue; + } + if (commit.getStatusMessage().isPresent()) { + logger.atFine().log( + "change %d: Status for commit %s is %s. %s", + id.get(), commit.name(), s, commit.getStatusMessage().get()); + } else { + logger.atFine().log("change %d: Status for commit %s is %s.", id.get(), commit.name(), s); + } + switch (s) { + case CLEAN_MERGE: + case CLEAN_REBASE: + case CLEAN_PICK: + case SKIPPED_IDENTICAL_TREE: + break; // Merge strategy accepted this change. + + case ALREADY_MERGED: + // Already an ancestor of tip. + alreadyMerged.add(commit.getPatchsetId().getParentKey()); + break; + + case PATH_CONFLICT: + case REBASE_MERGE_CONFLICT: + case MANUAL_RECURSIVE_MERGE: + case CANNOT_CHERRY_PICK_ROOT: + case CANNOT_REBASE_ROOT: + case NOT_FAST_FORWARD: + case EMPTY_COMMIT: + case MISSING_DEPENDENCY: + // TODO(dborowitz): Reformat these messages to be more appropriate for + // short problem descriptions. + String message = s.getDescription(); + if (commit.getStatusMessage().isPresent()) { + message += " " + commit.getStatusMessage().get(); + } + commitStatus.problem(id, CharMatcher.is('\n').collapseFrom(message, ' ')); + break; + + default: + commitStatus.problem(id, "unspecified merge failure: " + s); + break; + } + } + commitStatus.maybeFailVerbose(); + return alreadyMerged; + } + + @Override + public void afterUpdateChanges() throws ResourceConflictException { + commitStatus.maybeFail("Error updating status"); + } +} |