// Copyright (C) 2012 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.git.strategy; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.ListMultimap; import com.google.common.collect.Sets; import com.google.gerrit.extensions.api.changes.NotifyHandling; import com.google.gerrit.extensions.api.changes.RecipientType; import com.google.gerrit.extensions.client.SubmitType; import com.google.gerrit.extensions.config.FactoryModule; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.ApprovalsUtil; import com.google.gerrit.server.ChangeMessagesUtil; import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.PatchSetUtil; import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.change.RebaseChangeOp; import com.google.gerrit.server.extensions.events.ChangeMerged; import com.google.gerrit.server.git.CodeReviewCommit; import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk; import com.google.gerrit.server.git.EmailMerge; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.IntegrationException; import com.google.gerrit.server.git.LabelNormalizer; import com.google.gerrit.server.git.MergeOp.CommitStatus; import com.google.gerrit.server.git.MergeSorter; import com.google.gerrit.server.git.MergeTip; import com.google.gerrit.server.git.MergeUtil; import com.google.gerrit.server.git.SubmoduleOp; import com.google.gerrit.server.git.TagCache; import com.google.gerrit.server.git.validators.OnSubmitValidators; import com.google.gerrit.server.patch.PatchSetInfoFactory; import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.query.change.InternalChangeQuery; import com.google.gerrit.server.update.BatchUpdate; import com.google.gerrit.server.util.RequestId; import com.google.inject.Inject; import com.google.inject.Module; import com.google.inject.assistedinject.Assisted; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; /** * Base class that submit strategies must extend. * * <p>A submit strategy for a certain {@link SubmitType} defines how the submitted commits should be * merged. */ public abstract class SubmitStrategy { public static Module module() { return new FactoryModule() { @Override protected void configure() { factory(SubmitStrategy.Arguments.Factory.class); } }; } static class Arguments { interface Factory { Arguments create( SubmitType submitType, Branch.NameKey destBranch, CommitStatus commitStatus, CodeReviewRevWalk rw, IdentifiedUser caller, MergeTip mergeTip, RevFlag canMergeFlag, ReviewDb db, Set<RevCommit> alreadyAccepted, Set<CodeReviewCommit> incoming, RequestId submissionId, NotifyHandling notifyHandling, ListMultimap<RecipientType, Account.Id> accountsToNotify, SubmoduleOp submoduleOp, boolean dryrun); } final AccountCache accountCache; final ApprovalsUtil approvalsUtil; final BatchUpdate.Factory batchUpdateFactory; final ChangeControl.GenericFactory changeControlFactory; final ChangeMerged changeMerged; final ChangeMessagesUtil cmUtil; final EmailMerge.Factory mergedSenderFactory; final GitRepositoryManager repoManager; final LabelNormalizer labelNormalizer; final PatchSetInfoFactory patchSetInfoFactory; final PatchSetUtil psUtil; final ProjectCache projectCache; final PersonIdent serverIdent; final RebaseChangeOp.Factory rebaseFactory; final OnSubmitValidators.Factory onSubmitValidatorsFactory; final TagCache tagCache; final InternalChangeQuery internalChangeQuery; final Branch.NameKey destBranch; final CodeReviewRevWalk rw; final CommitStatus commitStatus; final IdentifiedUser caller; final MergeTip mergeTip; final RevFlag canMergeFlag; final ReviewDb db; final Set<RevCommit> alreadyAccepted; final Set<CodeReviewCommit> incoming; final RequestId submissionId; final SubmitType submitType; final NotifyHandling notifyHandling; final ListMultimap<RecipientType, Account.Id> accountsToNotify; final SubmoduleOp submoduleOp; final ProjectState project; final MergeSorter mergeSorter; final MergeUtil mergeUtil; final boolean dryrun; @Inject Arguments( AccountCache accountCache, ApprovalsUtil approvalsUtil, BatchUpdate.Factory batchUpdateFactory, ChangeControl.GenericFactory changeControlFactory, ChangeMerged changeMerged, ChangeMessagesUtil cmUtil, EmailMerge.Factory mergedSenderFactory, GitRepositoryManager repoManager, LabelNormalizer labelNormalizer, MergeUtil.Factory mergeUtilFactory, PatchSetInfoFactory patchSetInfoFactory, PatchSetUtil psUtil, @GerritPersonIdent PersonIdent serverIdent, ProjectCache projectCache, RebaseChangeOp.Factory rebaseFactory, OnSubmitValidators.Factory onSubmitValidatorsFactory, TagCache tagCache, InternalChangeQuery internalChangeQuery, @Assisted Branch.NameKey destBranch, @Assisted CommitStatus commitStatus, @Assisted CodeReviewRevWalk rw, @Assisted IdentifiedUser caller, @Assisted MergeTip mergeTip, @Assisted RevFlag canMergeFlag, @Assisted ReviewDb db, @Assisted Set<RevCommit> alreadyAccepted, @Assisted Set<CodeReviewCommit> incoming, @Assisted RequestId submissionId, @Assisted SubmitType submitType, @Assisted NotifyHandling notifyHandling, @Assisted ListMultimap<RecipientType, Account.Id> accountsToNotify, @Assisted SubmoduleOp submoduleOp, @Assisted boolean dryrun) { this.accountCache = accountCache; this.approvalsUtil = approvalsUtil; this.batchUpdateFactory = batchUpdateFactory; this.changeControlFactory = changeControlFactory; this.changeMerged = changeMerged; this.mergedSenderFactory = mergedSenderFactory; this.repoManager = repoManager; this.cmUtil = cmUtil; this.labelNormalizer = labelNormalizer; this.patchSetInfoFactory = patchSetInfoFactory; this.psUtil = psUtil; this.projectCache = projectCache; this.rebaseFactory = rebaseFactory; this.tagCache = tagCache; this.internalChangeQuery = internalChangeQuery; this.serverIdent = serverIdent; this.destBranch = destBranch; this.commitStatus = commitStatus; this.rw = rw; this.caller = caller; this.mergeTip = mergeTip; this.canMergeFlag = canMergeFlag; this.db = db; this.alreadyAccepted = alreadyAccepted; this.incoming = incoming; this.submissionId = submissionId; this.submitType = submitType; this.notifyHandling = notifyHandling; this.accountsToNotify = accountsToNotify; this.submoduleOp = submoduleOp; this.dryrun = dryrun; this.project = checkNotNull( projectCache.get(destBranch.getParentKey()), "project not found: %s", destBranch.getParentKey()); this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag); this.mergeUtil = mergeUtilFactory.create(project); this.onSubmitValidatorsFactory = onSubmitValidatorsFactory; } } final Arguments args; SubmitStrategy(Arguments args) { this.args = checkNotNull(args); } /** * Add operations to a batch update that execute this submit strategy. * * <p>Guarantees exactly one op is added to the update for each change in the input set. * * @param bu batch update to add operations to. * @param toMerge the set of submitted commits that should be merged using this submit strategy. * Implementations are responsible for ordering of commits, and will not modify the input in * place. * @throws IntegrationException if an error occurred initializing the operations (as opposed to an * error during execution, which will be reported only when the batch update executes the * operations). */ public final void addOps(BatchUpdate bu, Set<CodeReviewCommit> toMerge) throws IntegrationException { List<SubmitStrategyOp> ops = buildOps(toMerge); Set<CodeReviewCommit> added = Sets.newHashSetWithExpectedSize(ops.size()); for (SubmitStrategyOp op : ops) { added.add(op.getCommit()); } // First add ops for any implicitly merged changes. List<CodeReviewCommit> difference = new ArrayList<>(Sets.difference(toMerge, added)); Collections.reverse(difference); for (CodeReviewCommit c : difference) { bu.addOp(c.change().getId(), new ImplicitIntegrateOp(args, c)); } // Then ops for explicitly merged changes for (SubmitStrategyOp op : ops) { bu.addOp(op.getId(), op); } } protected abstract List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) throws IntegrationException; }