LCOV - code coverage report
Current view: top level - server/git/receive - ReplaceOp.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 243 278 87.4 %
Date: 2022-11-19 15:00:39 Functions: 20 24 83.3 %

          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.git.receive;
      16             : 
      17             : import static com.google.common.collect.ImmutableList.toImmutableList;
      18             : import static com.google.common.collect.ImmutableSet.toImmutableSet;
      19             : import static com.google.gerrit.server.change.ReviewerModifier.newReviewerInputFromCommitIdentity;
      20             : import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
      21             : import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromReviewers;
      22             : import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
      23             : import static com.google.gerrit.server.project.ProjectCache.illegalState;
      24             : import static java.util.stream.Collectors.joining;
      25             : import static org.eclipse.jgit.lib.Constants.R_HEADS;
      26             : 
      27             : import com.google.common.base.Strings;
      28             : import com.google.common.collect.ImmutableList;
      29             : import com.google.common.collect.Streams;
      30             : import com.google.common.flogger.FluentLogger;
      31             : import com.google.gerrit.common.Nullable;
      32             : import com.google.gerrit.entities.Account;
      33             : import com.google.gerrit.entities.Change;
      34             : import com.google.gerrit.entities.LabelType;
      35             : import com.google.gerrit.entities.PatchSet;
      36             : import com.google.gerrit.entities.PatchSetApproval;
      37             : import com.google.gerrit.entities.PatchSetInfo;
      38             : import com.google.gerrit.entities.SubmissionId;
      39             : import com.google.gerrit.extensions.api.changes.NotifyHandling;
      40             : import com.google.gerrit.extensions.api.changes.ReviewerInput;
      41             : import com.google.gerrit.extensions.client.ChangeKind;
      42             : import com.google.gerrit.extensions.client.ReviewerState;
      43             : import com.google.gerrit.extensions.registration.DynamicItem;
      44             : import com.google.gerrit.extensions.restapi.BadRequestException;
      45             : import com.google.gerrit.extensions.restapi.RestApiException;
      46             : import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
      47             : import com.google.gerrit.server.ChangeMessagesUtil;
      48             : import com.google.gerrit.server.ChangeUtil;
      49             : import com.google.gerrit.server.PatchSetUtil;
      50             : import com.google.gerrit.server.account.AccountCache;
      51             : import com.google.gerrit.server.account.AccountResolver;
      52             : import com.google.gerrit.server.account.AccountState;
      53             : import com.google.gerrit.server.approval.ApprovalCopier;
      54             : import com.google.gerrit.server.approval.ApprovalsUtil;
      55             : import com.google.gerrit.server.change.ChangeKindCache;
      56             : import com.google.gerrit.server.change.EmailNewPatchSet;
      57             : import com.google.gerrit.server.change.NotifyResolver;
      58             : import com.google.gerrit.server.change.ReviewerModifier;
      59             : import com.google.gerrit.server.change.ReviewerModifier.InternalReviewerInput;
      60             : import com.google.gerrit.server.change.ReviewerModifier.ReviewerModification;
      61             : import com.google.gerrit.server.change.ReviewerModifier.ReviewerModificationList;
      62             : import com.google.gerrit.server.change.ReviewerOp;
      63             : import com.google.gerrit.server.config.AnonymousCowardName;
      64             : import com.google.gerrit.server.config.UrlFormatter;
      65             : import com.google.gerrit.server.extensions.events.CommentAdded;
      66             : import com.google.gerrit.server.extensions.events.RevisionCreated;
      67             : import com.google.gerrit.server.git.MergedByPushOp;
      68             : import com.google.gerrit.server.git.receive.ReceiveCommits.MagicBranchInput;
      69             : import com.google.gerrit.server.mail.MailUtil.MailRecipients;
      70             : import com.google.gerrit.server.notedb.ChangeNotes;
      71             : import com.google.gerrit.server.notedb.ChangeUpdate;
      72             : import com.google.gerrit.server.permissions.PermissionBackendException;
      73             : import com.google.gerrit.server.project.ProjectCache;
      74             : import com.google.gerrit.server.project.ProjectState;
      75             : import com.google.gerrit.server.query.change.ChangeData;
      76             : import com.google.gerrit.server.update.BatchUpdateOp;
      77             : import com.google.gerrit.server.update.ChangeContext;
      78             : import com.google.gerrit.server.update.Context;
      79             : import com.google.gerrit.server.update.PostUpdateContext;
      80             : import com.google.gerrit.server.update.RepoContext;
      81             : import com.google.gerrit.server.util.LabelVote;
      82             : import com.google.gerrit.server.util.RequestScopePropagator;
      83             : import com.google.gerrit.server.validators.ValidationException;
      84             : import com.google.inject.Inject;
      85             : import com.google.inject.assistedinject.Assisted;
      86             : import com.google.inject.util.Providers;
      87             : import java.io.IOException;
      88             : import java.util.HashMap;
      89             : import java.util.List;
      90             : import java.util.Map;
      91             : import java.util.Optional;
      92             : import java.util.Set;
      93             : import java.util.stream.Stream;
      94             : import org.eclipse.jgit.errors.ConfigInvalidException;
      95             : import org.eclipse.jgit.lib.ObjectId;
      96             : import org.eclipse.jgit.revwalk.RevCommit;
      97             : import org.eclipse.jgit.revwalk.RevWalk;
      98             : import org.eclipse.jgit.transport.PushCertificate;
      99             : import org.eclipse.jgit.transport.ReceiveCommand;
     100             : 
     101             : public class ReplaceOp implements BatchUpdateOp {
     102          37 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
     103             : 
     104             :   public interface Factory {
     105             :     ReplaceOp create(
     106             :         ProjectState projectState,
     107             :         Change change,
     108             :         boolean checkMergedInto,
     109             :         @Nullable String mergeResultRevId,
     110             :         @Assisted("priorPatchSetId") PatchSet.Id priorPatchSetId,
     111             :         @Assisted("priorCommitId") ObjectId priorCommit,
     112             :         @Assisted("patchSetId") PatchSet.Id patchSetId,
     113             :         @Assisted("commitId") ObjectId commitId,
     114             :         PatchSetInfo info,
     115             :         List<String> groups,
     116             :         @Nullable MagicBranchInput magicBranch,
     117             :         @Nullable PushCertificate pushCertificate,
     118             :         RequestScopePropagator requestScopePropagator);
     119             :   }
     120             : 
     121             :   private static final String CHANGE_IS_CLOSED = "change is closed";
     122             : 
     123             :   private final AccountCache accountCache;
     124             :   private final AccountResolver accountResolver;
     125             :   private final String anonymousCowardName;
     126             :   private final ApprovalsUtil approvalsUtil;
     127             :   private final ChangeData.Factory changeDataFactory;
     128             :   private final ChangeKindCache changeKindCache;
     129             :   private final ChangeMessagesUtil cmUtil;
     130             :   private final EmailNewPatchSet.Factory emailNewPatchSetFactory;
     131             :   private final RevisionCreated revisionCreated;
     132             :   private final CommentAdded commentAdded;
     133             :   private final MergedByPushOp.Factory mergedByPushOpFactory;
     134             :   private final PatchSetUtil psUtil;
     135             :   private final ProjectCache projectCache;
     136             :   private final ReviewerModifier reviewerModifier;
     137             :   private final DynamicItem<UrlFormatter> urlFormatter;
     138             : 
     139             :   private final ProjectState projectState;
     140             :   private final Change change;
     141             :   private final boolean checkMergedInto;
     142             :   private final String mergeResultRevId;
     143             :   private final PatchSet.Id priorPatchSetId;
     144             :   private final ObjectId priorCommitId;
     145             :   private final PatchSet.Id patchSetId;
     146             :   private final ObjectId commitId;
     147             :   private final PatchSetInfo info;
     148             :   private final MagicBranchInput magicBranch;
     149             :   private final PushCertificate pushCertificate;
     150             :   private final RequestScopePropagator requestScopePropagator;
     151             :   private List<String> groups;
     152             : 
     153          37 :   private final Map<String, Short> approvals = new HashMap<>();
     154             :   private RevCommit commit;
     155             :   private ReceiveCommand cmd;
     156             :   private ChangeNotes notes;
     157             :   private PatchSet newPatchSet;
     158             :   private ChangeKind changeKind;
     159             :   private String mailMessage;
     160             :   private ApprovalCopier.Result approvalCopierResult;
     161             :   private String rejectMessage;
     162             :   private MergedByPushOp mergedByPushOp;
     163             :   private ReviewerModificationList reviewerAdditions;
     164             :   private MailRecipients oldRecipients;
     165             : 
     166             :   @Inject
     167             :   ReplaceOp(
     168             :       AccountCache accountCache,
     169             :       AccountResolver accountResolver,
     170             :       @AnonymousCowardName String anonymousCowardName,
     171             :       ApprovalsUtil approvalsUtil,
     172             :       ChangeData.Factory changeDataFactory,
     173             :       ChangeKindCache changeKindCache,
     174             :       ChangeMessagesUtil cmUtil,
     175             :       RevisionCreated revisionCreated,
     176             :       CommentAdded commentAdded,
     177             :       MergedByPushOp.Factory mergedByPushOpFactory,
     178             :       PatchSetUtil psUtil,
     179             :       ProjectCache projectCache,
     180             :       EmailNewPatchSet.Factory emailNewPatchSetFactory,
     181             :       ReviewerModifier reviewerModifier,
     182             :       DynamicItem<UrlFormatter> urlFormatter,
     183             :       @Assisted ProjectState projectState,
     184             :       @Assisted Change change,
     185             :       @Assisted boolean checkMergedInto,
     186             :       @Assisted @Nullable String mergeResultRevId,
     187             :       @Assisted("priorPatchSetId") PatchSet.Id priorPatchSetId,
     188             :       @Assisted("priorCommitId") ObjectId priorCommitId,
     189             :       @Assisted("patchSetId") PatchSet.Id patchSetId,
     190             :       @Assisted("commitId") ObjectId commitId,
     191             :       @Assisted PatchSetInfo info,
     192             :       @Assisted List<String> groups,
     193             :       @Assisted @Nullable MagicBranchInput magicBranch,
     194             :       @Assisted @Nullable PushCertificate pushCertificate,
     195          37 :       @Assisted RequestScopePropagator requestScopePropagator) {
     196          37 :     this.accountCache = accountCache;
     197          37 :     this.accountResolver = accountResolver;
     198          37 :     this.anonymousCowardName = anonymousCowardName;
     199          37 :     this.approvalsUtil = approvalsUtil;
     200          37 :     this.changeDataFactory = changeDataFactory;
     201          37 :     this.changeKindCache = changeKindCache;
     202          37 :     this.cmUtil = cmUtil;
     203          37 :     this.revisionCreated = revisionCreated;
     204          37 :     this.commentAdded = commentAdded;
     205          37 :     this.mergedByPushOpFactory = mergedByPushOpFactory;
     206          37 :     this.psUtil = psUtil;
     207          37 :     this.projectCache = projectCache;
     208          37 :     this.emailNewPatchSetFactory = emailNewPatchSetFactory;
     209          37 :     this.reviewerModifier = reviewerModifier;
     210          37 :     this.urlFormatter = urlFormatter;
     211             : 
     212          37 :     this.projectState = projectState;
     213          37 :     this.change = change;
     214          37 :     this.checkMergedInto = checkMergedInto;
     215          37 :     this.mergeResultRevId = mergeResultRevId;
     216          37 :     this.priorPatchSetId = priorPatchSetId;
     217          37 :     this.priorCommitId = priorCommitId.copy();
     218          37 :     this.patchSetId = patchSetId;
     219          37 :     this.commitId = commitId.copy();
     220          37 :     this.info = info;
     221          37 :     this.groups = groups;
     222          37 :     this.magicBranch = magicBranch;
     223          37 :     this.pushCertificate = pushCertificate;
     224          37 :     this.requestScopePropagator = requestScopePropagator;
     225          37 :   }
     226             : 
     227             :   @Override
     228             :   public void updateRepo(RepoContext ctx) throws Exception {
     229          37 :     commit = ctx.getRevWalk().parseCommit(commitId);
     230          37 :     ctx.getRevWalk().parseBody(commit);
     231          37 :     changeKind =
     232          37 :         changeKindCache.getChangeKind(
     233          37 :             projectState.getNameKey(),
     234          37 :             ctx.getRevWalk(),
     235          37 :             ctx.getRepoView().getConfig(),
     236             :             priorCommitId,
     237             :             commitId);
     238             : 
     239          37 :     if (checkMergedInto) {
     240           0 :       String mergedInto = findMergedInto(ctx, change.getDest().branch(), commit);
     241           0 :       if (mergedInto != null) {
     242           0 :         mergedByPushOp =
     243           0 :             mergedByPushOpFactory.create(
     244             :                 requestScopePropagator,
     245             :                 patchSetId,
     246             :                 new SubmissionId(change),
     247             :                 mergedInto,
     248             :                 mergeResultRevId);
     249             :       }
     250             :     }
     251             : 
     252          37 :     cmd = new ReceiveCommand(ObjectId.zeroId(), commitId, patchSetId.toRefName());
     253          37 :     ctx.addRefUpdate(cmd);
     254          37 :   }
     255             : 
     256             :   @Override
     257             :   public boolean updateChange(ChangeContext ctx)
     258             :       throws RestApiException, IOException, PermissionBackendException, ConfigInvalidException,
     259             :           ValidationException {
     260          37 :     notes = ctx.getNotes();
     261          37 :     Change change = notes.getChange();
     262          37 :     if (change == null || change.isClosed()) {
     263           0 :       rejectMessage = CHANGE_IS_CLOSED;
     264           0 :       return false;
     265             :     }
     266          37 :     if (groups.isEmpty()) {
     267           6 :       PatchSet prevPs = psUtil.current(notes);
     268           6 :       groups = prevPs != null ? prevPs.groups() : ImmutableList.of();
     269             :     }
     270             : 
     271          37 :     ChangeData cd = changeDataFactory.create(ctx.getNotes());
     272          37 :     oldRecipients = getRecipientsFromReviewers(cd.reviewers());
     273             : 
     274          37 :     ChangeUpdate update = ctx.getUpdate(patchSetId);
     275          37 :     update.setSubjectForCommit("Create patch set " + patchSetId.get());
     276             : 
     277          37 :     String reviewMessage = null;
     278          37 :     String psDescription = null;
     279          37 :     if (magicBranch != null) {
     280          37 :       reviewMessage = magicBranch.message;
     281          37 :       psDescription = magicBranch.message;
     282          37 :       approvals.putAll(magicBranch.labels);
     283          37 :       Set<String> hashtags = magicBranch.hashtags;
     284          37 :       if (hashtags != null && !hashtags.isEmpty()) {
     285           3 :         hashtags.addAll(notes.getHashtags());
     286           3 :         update.setHashtags(hashtags);
     287             :       }
     288          37 :       if (magicBranch.topic != null && !magicBranch.topic.equals(ctx.getChange().getTopic())) {
     289             :         try {
     290           0 :           update.setTopic(magicBranch.topic);
     291           0 :         } catch (ValidationException ex) {
     292           0 :           throw new BadRequestException(ex.getMessage());
     293           0 :         }
     294             :       }
     295          37 :       if (magicBranch.removePrivate) {
     296           3 :         change.setPrivate(false);
     297           3 :         update.setPrivate(false);
     298          37 :       } else if (magicBranch.isPrivate) {
     299           1 :         change.setPrivate(true);
     300           1 :         update.setPrivate(true);
     301             :       }
     302          37 :       if (magicBranch.ready) {
     303           4 :         change.setWorkInProgress(false);
     304           4 :         change.setReviewStarted(true);
     305           4 :         update.setWorkInProgress(false);
     306          37 :       } else if (magicBranch.workInProgress) {
     307           5 :         change.setWorkInProgress(true);
     308           5 :         update.setWorkInProgress(true);
     309             :       }
     310          37 :       if (magicBranch.ignoreAttentionSet) {
     311           0 :         update.ignoreFurtherAttentionSetUpdates();
     312             :       }
     313             :     }
     314             : 
     315          37 :     newPatchSet =
     316          37 :         psUtil.insert(
     317          37 :             ctx.getRevWalk(),
     318             :             update,
     319             :             patchSetId,
     320             :             commitId,
     321             :             groups,
     322          37 :             pushCertificate != null ? pushCertificate.toTextWithSignature() : null,
     323             :             psDescription);
     324             : 
     325          37 :     update.setPsDescription(psDescription);
     326          37 :     MailRecipients fromFooters = getRecipientsFromFooters(accountResolver, commit.getFooterLines());
     327          37 :     approvalsUtil.addApprovalsForNewPatchSet(
     328          37 :         update, projectState.getLabelTypes(), newPatchSet, ctx.getUser(), approvals);
     329             : 
     330          37 :     reviewerAdditions =
     331          37 :         reviewerModifier.prepare(
     332          37 :             ctx.getNotes(),
     333          37 :             ctx.getUser(),
     334          37 :             getReviewerInputs(magicBranch, fromFooters, ctx.getChange(), info),
     335             :             true);
     336          37 :     Optional<ReviewerModification> reviewerError =
     337          37 :         reviewerAdditions.getFailures().stream().findFirst();
     338          37 :     if (reviewerError.isPresent()) {
     339           0 :       throw new UnprocessableEntityException(reviewerError.get().result.error);
     340             :     }
     341          37 :     reviewerAdditions.updateChange(ctx, newPatchSet);
     342             : 
     343             :     // Check if approvals are changing with this update. If so, add the current user (aka the
     344             :     // approver) as a reviewers because all approvers must also be reviewers.
     345             :     // Note that this is done separately as addReviewers is filtering out the change owner as a
     346             :     // reviewer which is needed in several other code paths.
     347          37 :     if (magicBranch != null && !magicBranch.labels.isEmpty()) {
     348           4 :       update.putReviewer(ctx.getAccountId(), REVIEWER);
     349             :     }
     350             : 
     351          37 :     approvalCopierResult =
     352          37 :         approvalsUtil.copyApprovalsToNewPatchSet(
     353          37 :             ctx.getNotes(), newPatchSet, ctx.getRevWalk(), ctx.getRepoView().getConfig(), update);
     354             : 
     355          37 :     mailMessage = insertChangeMessage(update, ctx, reviewMessage);
     356          37 :     if (mergedByPushOp == null) {
     357          37 :       resetChange(ctx);
     358             :     } else {
     359           0 :       mergedByPushOp.setPatchSetProvider(Providers.of(newPatchSet)).updateChange(ctx);
     360             :     }
     361             : 
     362          37 :     return true;
     363             :   }
     364             : 
     365             :   private ImmutableList<ReviewerInput> getReviewerInputs(
     366             :       @Nullable MagicBranchInput magicBranch,
     367             :       MailRecipients fromFooters,
     368             :       Change change,
     369             :       PatchSetInfo psInfo) {
     370             :     // Disable individual emails when adding reviewers, as all reviewers will receive the single
     371             :     // bulk new change email.
     372          37 :     Stream<ReviewerInput> inputs =
     373          37 :         Streams.concat(
     374          37 :             Streams.stream(
     375          37 :                 newReviewerInputFromCommitIdentity(
     376             :                     change,
     377          37 :                     psInfo.getCommitId(),
     378          37 :                     psInfo.getAuthor().getAccount(),
     379             :                     NotifyHandling.NONE,
     380          37 :                     newPatchSet.uploader())),
     381          37 :             Streams.stream(
     382          37 :                 newReviewerInputFromCommitIdentity(
     383             :                     change,
     384          37 :                     psInfo.getCommitId(),
     385          37 :                     psInfo.getCommitter().getAccount(),
     386             :                     NotifyHandling.NONE,
     387          37 :                     newPatchSet.uploader())));
     388          37 :     if (magicBranch != null) {
     389          37 :       inputs =
     390          37 :           Streams.concat(
     391             :               inputs,
     392          37 :               magicBranch.getCombinedReviewers(fromFooters).stream()
     393          37 :                   .map(r -> newReviewerInput(r, ReviewerState.REVIEWER)),
     394          37 :               magicBranch.getCombinedCcs(fromFooters).stream()
     395          37 :                   .map(r -> newReviewerInput(r, ReviewerState.CC)));
     396             :     }
     397          37 :     return inputs.collect(toImmutableList());
     398             :   }
     399             : 
     400             :   private static InternalReviewerInput newReviewerInput(String reviewer, ReviewerState state) {
     401             :     // Disable individual emails when adding reviewers, as all reviewers will receive the single
     402             :     // bulk new patch set email.
     403           4 :     InternalReviewerInput input =
     404           4 :         ReviewerModifier.newReviewerInput(reviewer, state, NotifyHandling.NONE);
     405             : 
     406             :     // Ignore failures for reasons like the reviewer being inactive or being unable to see the
     407             :     // change. See discussion in ChangeInserter.
     408           4 :     input.otherFailureBehavior = ReviewerModifier.FailureBehavior.IGNORE_EXCEPT_NOT_FOUND;
     409             : 
     410           4 :     return input;
     411             :   }
     412             : 
     413             :   private String insertChangeMessage(ChangeUpdate update, ChangeContext ctx, String reviewMessage) {
     414          37 :     String approvalMessage =
     415          37 :         ApprovalsUtil.renderMessageWithApprovals(
     416          37 :             patchSetId.get(), approvals, scanLabels(ctx, approvals));
     417          37 :     String kindMessage = changeKindMessage(changeKind);
     418          37 :     StringBuilder message = new StringBuilder(approvalMessage);
     419          37 :     if (!Strings.isNullOrEmpty(kindMessage)) {
     420          14 :       message.append(kindMessage);
     421             :     } else {
     422          36 :       message.append('.');
     423             :     }
     424          37 :     if (!Strings.isNullOrEmpty(reviewMessage)) {
     425           4 :       message.append("\n\n").append(reviewMessage);
     426             :     }
     427          37 :     approvalsUtil
     428          37 :         .formatApprovalCopierResult(approvalCopierResult, projectState.getLabelTypes())
     429          37 :         .ifPresent(
     430             :             msg -> {
     431          12 :               if (Strings.isNullOrEmpty(reviewMessage) || !reviewMessage.endsWith("\n")) {
     432          12 :                 message.append("\n");
     433             :               }
     434          12 :               message.append("\n").append(msg);
     435          12 :             });
     436          37 :     boolean workInProgress = ctx.getChange().isWorkInProgress();
     437          37 :     if (magicBranch != null && magicBranch.workInProgress) {
     438           5 :       workInProgress = true;
     439             :     }
     440          37 :     return cmUtil.setChangeMessage(
     441          37 :         update, message.toString(), ChangeMessagesUtil.uploadedPatchSetTag(workInProgress));
     442             :   }
     443             : 
     444             :   @Nullable
     445             :   private String changeKindMessage(ChangeKind changeKind) {
     446          37 :     switch (changeKind) {
     447             :       case MERGE_FIRST_PARENT_UPDATE:
     448           1 :         return ": New merge patch set was added with a new first parent relative to Patch Set "
     449           1 :             + priorPatchSetId.get()
     450             :             + ".";
     451             :       case TRIVIAL_REBASE:
     452           5 :         return ": Patch Set " + priorPatchSetId.get() + " was rebased.";
     453             :       case NO_CHANGE:
     454           8 :         return ": New patch set was added with same tree, parent "
     455           8 :             + (commit.getParentCount() != 1 ? "trees" : "tree")
     456             :             + ", and commit message as Patch Set "
     457           8 :             + priorPatchSetId.get()
     458             :             + ".";
     459             :       case NO_CODE_CHANGE:
     460           6 :         return ": Commit message was updated.";
     461             :       case REWORK:
     462             :       default:
     463          36 :         return null;
     464             :     }
     465             :   }
     466             : 
     467             :   private Map<String, PatchSetApproval> scanLabels(
     468             :       ChangeContext ctx, Map<String, Short> approvals) {
     469          37 :     Map<String, PatchSetApproval> current = new HashMap<>();
     470             :     // We optimize here and only retrieve current when approvals provided
     471          37 :     if (!approvals.isEmpty()) {
     472             :       for (PatchSetApproval a :
     473           4 :           approvalsUtil.byPatchSetUser(ctx.getNotes(), priorPatchSetId, ctx.getAccountId())) {
     474           4 :         if (a.isLegacySubmit()) {
     475           0 :           continue;
     476             :         }
     477             : 
     478           4 :         projectState
     479           4 :             .getLabelTypes()
     480           4 :             .byLabel(a.labelId())
     481           4 :             .ifPresent(l -> current.put(l.getName(), a));
     482           4 :       }
     483             :     }
     484          37 :     return current;
     485             :   }
     486             : 
     487             :   private void resetChange(ChangeContext ctx) {
     488          37 :     Change change = ctx.getChange();
     489          37 :     if (!change.currentPatchSetId().equals(priorPatchSetId)) {
     490           0 :       return;
     491             :     }
     492             : 
     493          37 :     if (magicBranch != null && magicBranch.topic != null) {
     494           0 :       change.setTopic(magicBranch.topic);
     495             :     }
     496          37 :     change.setStatus(Change.Status.NEW);
     497          37 :     change.setCurrentPatchSet(info);
     498             : 
     499          37 :     List<String> idList = ChangeUtil.getChangeIdsFromFooter(commit, urlFormatter.get());
     500          37 :     change.setKey(Change.key(idList.get(idList.size() - 1).trim()));
     501          37 :   }
     502             : 
     503             :   @Override
     504             :   public void postUpdate(PostUpdateContext ctx) throws Exception {
     505          37 :     reviewerAdditions.postUpdate(ctx);
     506             : 
     507             :     // TODO(dborowitz): Merge email templates so we only have to send one.
     508          37 :     emailNewPatchSetFactory
     509          37 :         .create(
     510             :             ctx,
     511             :             newPatchSet,
     512             :             mailMessage,
     513          37 :             approvalCopierResult.outdatedApprovals(),
     514          37 :             Streams.concat(
     515          37 :                     oldRecipients.getReviewers().stream(),
     516          37 :                     reviewerAdditions.flattenResults(ReviewerOp.Result::addedReviewers).stream()
     517          37 :                         .map(PatchSetApproval::accountId))
     518          37 :                 .collect(toImmutableSet()),
     519          37 :             Streams.concat(
     520          37 :                     oldRecipients.getCcOnly().stream(),
     521          37 :                     reviewerAdditions.flattenResults(ReviewerOp.Result::addedCCs).stream())
     522          37 :                 .collect(toImmutableSet()),
     523             :             changeKind,
     524          37 :             notes.getMetaId())
     525          37 :         .setRequestScopePropagator(requestScopePropagator)
     526          37 :         .sendAsync();
     527             : 
     528          37 :     NotifyResolver.Result notify = ctx.getNotify(notes.getChangeId());
     529          37 :     revisionCreated.fire(
     530          37 :         ctx.getChangeData(notes), newPatchSet, ctx.getAccount(), ctx.getWhen(), notify);
     531             :     try {
     532          37 :       fireApprovalsEvent(ctx);
     533           0 :     } catch (Exception e) {
     534           0 :       logger.atWarning().withCause(e).log("comment-added event invocation failed");
     535          37 :     }
     536          37 :     if (mergedByPushOp != null) {
     537           0 :       mergedByPushOp.postUpdate(ctx);
     538             :     }
     539          37 :   }
     540             : 
     541             :   private void fireApprovalsEvent(PostUpdateContext ctx) {
     542          37 :     if (approvals.isEmpty()) {
     543          37 :       return;
     544             :     }
     545             :     /* For labels that are not set in this operation, show the "current" value
     546             :      * of 0, and no oldValue as the value was not modified by this operation.
     547             :      * For labels that are set in this operation, the value was modified, so
     548             :      * show a transition from an oldValue of 0 to the new value.
     549             :      */
     550           4 :     List<LabelType> labels =
     551             :         projectCache
     552           4 :             .get(ctx.getProject())
     553           4 :             .orElseThrow(illegalState(ctx.getProject()))
     554           4 :             .getLabelTypes(notes)
     555           4 :             .getLabelTypes();
     556           4 :     Map<String, Short> allApprovals = new HashMap<>();
     557           4 :     Map<String, Short> oldApprovals = new HashMap<>();
     558           4 :     for (LabelType lt : labels) {
     559           4 :       allApprovals.put(lt.getName(), (short) 0);
     560           4 :       oldApprovals.put(lt.getName(), null);
     561           4 :     }
     562           4 :     for (Map.Entry<String, Short> entry : approvals.entrySet()) {
     563           4 :       if (entry.getValue() != 0) {
     564           4 :         allApprovals.put(entry.getKey(), entry.getValue());
     565           4 :         oldApprovals.put(entry.getKey(), (short) 0);
     566             :       }
     567           4 :     }
     568           4 :     commentAdded.fire(
     569           4 :         ctx.getChangeData(notes),
     570             :         newPatchSet,
     571           4 :         ctx.getAccount(),
     572             :         null,
     573             :         allApprovals,
     574             :         oldApprovals,
     575           4 :         ctx.getWhen());
     576           4 :   }
     577             : 
     578             :   public PatchSet getPatchSet() {
     579           6 :     return newPatchSet;
     580             :   }
     581             : 
     582             :   public Change getChange() {
     583           0 :     return notes.getChange();
     584             :   }
     585             : 
     586             :   public String getRejectMessage() {
     587          37 :     return rejectMessage;
     588             :   }
     589             : 
     590             :   public Optional<String> getOutdatedApprovalsMessage() {
     591          37 :     if (approvalCopierResult == null || approvalCopierResult.outdatedApprovals().isEmpty()) {
     592          35 :       return Optional.empty();
     593             :     }
     594             : 
     595          11 :     return Optional.of(
     596             :         "The following approvals got outdated and were removed:\n"
     597          11 :             + approvalCopierResult.outdatedApprovals().stream()
     598          11 :                 .map(
     599             :                     outdatedApproval ->
     600          11 :                         String.format(
     601             :                             "* %s by %s",
     602          11 :                             LabelVote.create(outdatedApproval.label(), outdatedApproval.value())
     603          11 :                                 .format(),
     604          11 :                             getNameFor(outdatedApproval.accountId())))
     605          11 :                 .sorted()
     606          11 :                 .collect(joining("\n")));
     607             :   }
     608             : 
     609             :   private String getNameFor(Account.Id accountId) {
     610          11 :     Optional<Account> account = accountCache.get(accountId).map(AccountState::account);
     611          11 :     String name = null;
     612          11 :     if (account.isPresent()) {
     613          11 :       name = account.get().fullName();
     614          11 :       if (name == null) {
     615           0 :         name = account.get().preferredEmail();
     616             :       }
     617             :     }
     618          11 :     if (name == null) {
     619           0 :       name = anonymousCowardName + " #" + accountId;
     620             :     }
     621          11 :     return name;
     622             :   }
     623             : 
     624             :   public ReceiveCommand getCommand() {
     625           0 :     return cmd;
     626             :   }
     627             : 
     628             :   @Nullable
     629             :   private static String findMergedInto(Context ctx, String first, RevCommit commit) {
     630             :     try {
     631           0 :       RevWalk rw = ctx.getRevWalk();
     632           0 :       Optional<ObjectId> firstId = ctx.getRepoView().getRef(first);
     633           0 :       if (firstId.isPresent() && rw.isMergedInto(commit, rw.parseCommit(firstId.get()))) {
     634           0 :         return first;
     635             :       }
     636             : 
     637           0 :       for (Map.Entry<String, ObjectId> e : ctx.getRepoView().getRefs(R_HEADS).entrySet()) {
     638           0 :         if (rw.isMergedInto(commit, rw.parseCommit(e.getValue()))) {
     639           0 :           return R_HEADS + e.getKey();
     640             :         }
     641           0 :       }
     642           0 :       return null;
     643           0 :     } catch (IOException e) {
     644           0 :       logger.atWarning().withCause(e).log("Can't check for already submitted change");
     645           0 :       return null;
     646             :     }
     647             :   }
     648             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750