LCOV - code coverage report
Current view: top level - server/change - ChangeInserter.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 252 270 93.3 %
Date: 2022-11-19 15:00:39 Functions: 36 42 85.7 %

          Line data    Source code
       1             : // Copyright (C) 2013 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.change;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkState;
      18             : import static com.google.common.collect.ImmutableList.toImmutableList;
      19             : import static com.google.common.collect.ImmutableSet.toImmutableSet;
      20             : import static com.google.gerrit.entities.Change.INITIAL_PATCH_SET_ID;
      21             : import static com.google.gerrit.server.change.ReviewerModifier.newReviewerInputFromCommitIdentity;
      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.Objects.requireNonNull;
      25             : 
      26             : import com.google.common.base.MoreObjects;
      27             : import com.google.common.collect.ImmutableList;
      28             : import com.google.common.collect.ImmutableListMultimap;
      29             : import com.google.common.collect.Iterables;
      30             : import com.google.common.collect.Streams;
      31             : import com.google.common.flogger.FluentLogger;
      32             : import com.google.errorprone.annotations.CanIgnoreReturnValue;
      33             : import com.google.gerrit.common.Nullable;
      34             : import com.google.gerrit.entities.Account;
      35             : import com.google.gerrit.entities.BranchNameKey;
      36             : import com.google.gerrit.entities.Change;
      37             : import com.google.gerrit.entities.LabelType;
      38             : import com.google.gerrit.entities.LabelTypes;
      39             : import com.google.gerrit.entities.PatchSet;
      40             : import com.google.gerrit.entities.PatchSetApproval;
      41             : import com.google.gerrit.entities.PatchSetInfo;
      42             : import com.google.gerrit.entities.SubmissionId;
      43             : import com.google.gerrit.extensions.api.changes.NotifyHandling;
      44             : import com.google.gerrit.extensions.client.ReviewerState;
      45             : import com.google.gerrit.extensions.registration.DynamicItem;
      46             : import com.google.gerrit.extensions.restapi.BadRequestException;
      47             : import com.google.gerrit.extensions.restapi.ResourceConflictException;
      48             : import com.google.gerrit.extensions.restapi.RestApiException;
      49             : import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
      50             : import com.google.gerrit.server.ChangeMessagesUtil;
      51             : import com.google.gerrit.server.ChangeUtil;
      52             : import com.google.gerrit.server.PatchSetUtil;
      53             : import com.google.gerrit.server.approval.ApprovalsUtil;
      54             : import com.google.gerrit.server.change.ReviewerModifier.InternalReviewerInput;
      55             : import com.google.gerrit.server.change.ReviewerModifier.ReviewerModification;
      56             : import com.google.gerrit.server.change.ReviewerModifier.ReviewerModificationList;
      57             : import com.google.gerrit.server.config.SendEmailExecutor;
      58             : import com.google.gerrit.server.config.UrlFormatter;
      59             : import com.google.gerrit.server.events.CommitReceivedEvent;
      60             : import com.google.gerrit.server.extensions.events.CommentAdded;
      61             : import com.google.gerrit.server.extensions.events.RevisionCreated;
      62             : import com.google.gerrit.server.git.GroupCollector;
      63             : import com.google.gerrit.server.git.validators.CommitValidationException;
      64             : import com.google.gerrit.server.git.validators.CommitValidators;
      65             : import com.google.gerrit.server.mail.send.CreateChangeSender;
      66             : import com.google.gerrit.server.mail.send.MessageIdGenerator;
      67             : import com.google.gerrit.server.notedb.ChangeUpdate;
      68             : import com.google.gerrit.server.patch.AutoMerger;
      69             : import com.google.gerrit.server.patch.PatchSetInfoFactory;
      70             : import com.google.gerrit.server.permissions.PermissionBackend;
      71             : import com.google.gerrit.server.permissions.PermissionBackendException;
      72             : import com.google.gerrit.server.project.ProjectCache;
      73             : import com.google.gerrit.server.project.ProjectState;
      74             : import com.google.gerrit.server.ssh.NoSshInfo;
      75             : import com.google.gerrit.server.update.ChangeContext;
      76             : import com.google.gerrit.server.update.Context;
      77             : import com.google.gerrit.server.update.InsertChangeOp;
      78             : import com.google.gerrit.server.update.PostUpdateContext;
      79             : import com.google.gerrit.server.update.RepoContext;
      80             : import com.google.gerrit.server.util.CommitMessageUtil;
      81             : import com.google.gerrit.server.util.RequestScopePropagator;
      82             : import com.google.gerrit.server.validators.ValidationException;
      83             : import com.google.inject.Inject;
      84             : import com.google.inject.assistedinject.Assisted;
      85             : import java.io.IOException;
      86             : import java.util.Collections;
      87             : import java.util.HashMap;
      88             : import java.util.List;
      89             : import java.util.Map;
      90             : import java.util.Optional;
      91             : import java.util.concurrent.ExecutorService;
      92             : import java.util.concurrent.Future;
      93             : import org.eclipse.jgit.errors.ConfigInvalidException;
      94             : import org.eclipse.jgit.lib.ObjectId;
      95             : import org.eclipse.jgit.revwalk.RevCommit;
      96             : import org.eclipse.jgit.revwalk.RevWalk;
      97             : import org.eclipse.jgit.transport.ReceiveCommand;
      98             : 
      99             : public class ChangeInserter implements InsertChangeOp {
     100         103 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
     101             : 
     102             :   public interface Factory {
     103             :     ChangeInserter create(Change.Id cid, ObjectId commitId, String refName);
     104             :   }
     105             : 
     106             :   private final PermissionBackend permissionBackend;
     107             :   private final ProjectCache projectCache;
     108             :   private final PatchSetInfoFactory patchSetInfoFactory;
     109             :   private final PatchSetUtil psUtil;
     110             :   private final ApprovalsUtil approvalsUtil;
     111             :   private final ChangeMessagesUtil cmUtil;
     112             :   private final CreateChangeSender.Factory createChangeSenderFactory;
     113             :   private final ExecutorService sendEmailExecutor;
     114             :   private final CommitValidators.Factory commitValidatorsFactory;
     115             :   private final RevisionCreated revisionCreated;
     116             :   private final CommentAdded commentAdded;
     117             :   private final ReviewerModifier reviewerModifier;
     118             :   private final MessageIdGenerator messageIdGenerator;
     119             :   private final DynamicItem<UrlFormatter> urlFormatter;
     120             :   private final AutoMerger autoMerger;
     121             : 
     122             :   private final Change.Id changeId;
     123             :   private final PatchSet.Id psId;
     124             :   private final ObjectId commitId;
     125             :   private final String refName;
     126             : 
     127             :   // Fields exposed as setters.
     128             :   private PatchSet.Id cherryPickOf;
     129             :   private Change.Status status;
     130             :   private String topic;
     131             :   private String message;
     132             :   private String patchSetDescription;
     133             :   private boolean isPrivate;
     134             :   private boolean workInProgress;
     135         103 :   private List<String> groups = Collections.emptyList();
     136         103 :   private ImmutableListMultimap<String, String> validationOptions = ImmutableListMultimap.of();
     137         103 :   private boolean validate = true;
     138             :   private Map<String, Short> approvals;
     139             :   private RequestScopePropagator requestScopePropagator;
     140             :   private boolean fireRevisionCreated;
     141             :   private boolean sendMail;
     142             :   private boolean updateRef;
     143             :   private Change.Id revertOf;
     144             :   private ImmutableList<InternalReviewerInput> reviewerInputs;
     145             : 
     146             :   // Fields set during the insertion process.
     147             :   private ReceiveCommand cmd;
     148             :   private Change change;
     149             :   private String changeMessage;
     150             :   private PatchSetInfo patchSetInfo;
     151             :   private PatchSet patchSet;
     152             :   private String pushCert;
     153             :   private ProjectState projectState;
     154             :   private ReviewerModificationList reviewerAdditions;
     155             : 
     156             :   @Inject
     157             :   ChangeInserter(
     158             :       PermissionBackend permissionBackend,
     159             :       ProjectCache projectCache,
     160             :       PatchSetInfoFactory patchSetInfoFactory,
     161             :       PatchSetUtil psUtil,
     162             :       ApprovalsUtil approvalsUtil,
     163             :       ChangeMessagesUtil cmUtil,
     164             :       CreateChangeSender.Factory createChangeSenderFactory,
     165             :       @SendEmailExecutor ExecutorService sendEmailExecutor,
     166             :       CommitValidators.Factory commitValidatorsFactory,
     167             :       CommentAdded commentAdded,
     168             :       RevisionCreated revisionCreated,
     169             :       ReviewerModifier reviewerModifier,
     170             :       MessageIdGenerator messageIdGenerator,
     171             :       DynamicItem<UrlFormatter> urlFormatter,
     172             :       AutoMerger autoMerger,
     173             :       @Assisted Change.Id changeId,
     174             :       @Assisted ObjectId commitId,
     175         103 :       @Assisted String refName) {
     176         103 :     this.permissionBackend = permissionBackend;
     177         103 :     this.projectCache = projectCache;
     178         103 :     this.patchSetInfoFactory = patchSetInfoFactory;
     179         103 :     this.psUtil = psUtil;
     180         103 :     this.approvalsUtil = approvalsUtil;
     181         103 :     this.cmUtil = cmUtil;
     182         103 :     this.createChangeSenderFactory = createChangeSenderFactory;
     183         103 :     this.sendEmailExecutor = sendEmailExecutor;
     184         103 :     this.commitValidatorsFactory = commitValidatorsFactory;
     185         103 :     this.revisionCreated = revisionCreated;
     186         103 :     this.commentAdded = commentAdded;
     187         103 :     this.reviewerModifier = reviewerModifier;
     188         103 :     this.messageIdGenerator = messageIdGenerator;
     189         103 :     this.urlFormatter = urlFormatter;
     190         103 :     this.autoMerger = autoMerger;
     191             : 
     192         103 :     this.changeId = changeId;
     193         103 :     this.psId = PatchSet.id(changeId, INITIAL_PATCH_SET_ID);
     194         103 :     this.commitId = commitId.copy();
     195         103 :     this.refName = refName;
     196         103 :     this.reviewerInputs = ImmutableList.of();
     197         103 :     this.approvals = Collections.emptyMap();
     198         103 :     this.fireRevisionCreated = true;
     199         103 :     this.sendMail = true;
     200         103 :     this.updateRef = true;
     201         103 :   }
     202             : 
     203             :   @Override
     204             :   public Change createChange(Context ctx) throws IOException {
     205         103 :     change =
     206             :         new Change(
     207         103 :             getChangeKey(ctx.getRevWalk()),
     208             :             changeId,
     209         103 :             ctx.getAccountId(),
     210         103 :             BranchNameKey.create(ctx.getProject(), refName),
     211         103 :             ctx.getWhen());
     212         103 :     change.setStatus(MoreObjects.firstNonNull(status, Change.Status.NEW));
     213         103 :     change.setTopic(topic);
     214         103 :     change.setCherryPickOf(cherryPickOf);
     215         103 :     change.setPrivate(isPrivate);
     216         103 :     change.setWorkInProgress(workInProgress);
     217         103 :     change.setReviewStarted(!workInProgress);
     218         103 :     change.setRevertOf(revertOf);
     219         103 :     return change;
     220             :   }
     221             : 
     222             :   private Change.Key getChangeKey(RevWalk rw) throws IOException {
     223         103 :     RevCommit commit = rw.parseCommit(commitId);
     224         103 :     rw.parseBody(commit);
     225         103 :     List<String> idList = ChangeUtil.getChangeIdsFromFooter(commit, urlFormatter.get());
     226         103 :     if (!idList.isEmpty()) {
     227         103 :       return Change.key(idList.get(idList.size() - 1).trim());
     228             :     }
     229             :     // A Change-Id is generated for the review, but not appended to the commit message.
     230             :     // This can happen if requireChangeId is false.
     231           9 :     return CommitMessageUtil.generateKey();
     232             :   }
     233             : 
     234             :   public PatchSet.Id getPatchSetId() {
     235         102 :     return psId;
     236             :   }
     237             : 
     238             :   public ObjectId getCommitId() {
     239           4 :     return commitId;
     240             :   }
     241             : 
     242             :   public Change getChange() {
     243          32 :     checkState(change != null, "getChange() only valid after creating change");
     244          32 :     return change;
     245             :   }
     246             : 
     247             :   @CanIgnoreReturnValue
     248             :   public ChangeInserter setTopic(String topic) {
     249         101 :     checkState(change == null, "setTopic(String) only valid before creating change");
     250         101 :     this.topic = topic;
     251         101 :     return this;
     252             :   }
     253             : 
     254             :   @CanIgnoreReturnValue
     255             :   public ChangeInserter setCherryPickOf(PatchSet.Id cherryPickOf) {
     256          10 :     this.cherryPickOf = cherryPickOf;
     257          10 :     return this;
     258             :   }
     259             : 
     260             :   @CanIgnoreReturnValue
     261             :   public ChangeInserter setMessage(String message) {
     262         102 :     this.message = message;
     263         102 :     return this;
     264             :   }
     265             : 
     266             :   @CanIgnoreReturnValue
     267             :   public ChangeInserter setPatchSetDescription(String patchSetDescription) {
     268          88 :     this.patchSetDescription = patchSetDescription;
     269          88 :     return this;
     270             :   }
     271             : 
     272             :   @CanIgnoreReturnValue
     273             :   public ChangeInserter setValidate(boolean validate) {
     274          92 :     this.validate = validate;
     275          92 :     return this;
     276             :   }
     277             : 
     278             :   @CanIgnoreReturnValue
     279             :   public ChangeInserter setReviewersAndCcs(
     280             :       Iterable<Account.Id> reviewers, Iterable<Account.Id> ccs) {
     281           0 :     return setReviewersAndCcsAsStrings(
     282           0 :         Iterables.transform(reviewers, Account.Id::toString),
     283           0 :         Iterables.transform(ccs, Account.Id::toString));
     284             :   }
     285             : 
     286             :   @CanIgnoreReturnValue
     287             :   public ChangeInserter setReviewersAndCcsIgnoreVisibility(
     288             :       Iterable<Account.Id> reviewers, Iterable<Account.Id> ccs) {
     289          15 :     return setReviewersAndCcsAsStrings(
     290          15 :         Iterables.transform(reviewers, Account.Id::toString),
     291          15 :         Iterables.transform(ccs, Account.Id::toString),
     292             :         /* skipVisibilityCheck= */ true);
     293             :   }
     294             : 
     295             :   @CanIgnoreReturnValue
     296             :   public ChangeInserter setReviewersAndCcsAsStrings(
     297             :       Iterable<String> reviewers, Iterable<String> ccs) {
     298          88 :     return setReviewersAndCcsAsStrings(reviewers, ccs, /* skipVisibilityCheck= */ false);
     299             :   }
     300             : 
     301             :   @CanIgnoreReturnValue
     302             :   private ChangeInserter setReviewersAndCcsAsStrings(
     303             :       Iterable<String> reviewers, Iterable<String> ccs, boolean skipVisibilityCheck) {
     304          92 :     reviewerInputs =
     305          92 :         Streams.concat(
     306          92 :                 Streams.stream(reviewers)
     307          92 :                     .distinct()
     308          92 :                     .map(id -> newReviewerInput(id, ReviewerState.REVIEWER, skipVisibilityCheck)),
     309          92 :                 Streams.stream(ccs)
     310          92 :                     .distinct()
     311          92 :                     .map(id -> newReviewerInput(id, ReviewerState.CC, skipVisibilityCheck)))
     312          92 :             .collect(toImmutableList());
     313          92 :     return this;
     314             :   }
     315             : 
     316             :   @CanIgnoreReturnValue
     317             :   public ChangeInserter setPrivate(boolean isPrivate) {
     318         101 :     checkState(change == null, "setPrivate(boolean) only valid before creating change");
     319         101 :     this.isPrivate = isPrivate;
     320         101 :     return this;
     321             :   }
     322             : 
     323             :   @CanIgnoreReturnValue
     324             :   public ChangeInserter setWorkInProgress(boolean workInProgress) {
     325         101 :     this.workInProgress = workInProgress;
     326         101 :     return this;
     327             :   }
     328             : 
     329             :   @CanIgnoreReturnValue
     330             :   public ChangeInserter setStatus(Change.Status status) {
     331           7 :     checkState(change == null, "setStatus(Change.Status) only valid before creating change");
     332           7 :     this.status = status;
     333           7 :     return this;
     334             :   }
     335             : 
     336             :   @CanIgnoreReturnValue
     337             :   public ChangeInserter setGroups(List<String> groups) {
     338         101 :     requireNonNull(groups, "groups may not be empty");
     339         101 :     checkState(patchSet == null, "setGroups(List<String>) only valid before creating change");
     340         101 :     this.groups = groups;
     341         101 :     return this;
     342             :   }
     343             : 
     344             :   @CanIgnoreReturnValue
     345             :   public ChangeInserter setValidationOptions(
     346             :       ImmutableListMultimap<String, String> validationOptions) {
     347          19 :     requireNonNull(validationOptions, "validationOptions may not be null");
     348          19 :     checkState(
     349             :         patchSet == null,
     350             :         "setValidationOptions(ImmutableListMultimap<String, String>) only valid before creating a"
     351             :             + " change");
     352          19 :     this.validationOptions = validationOptions;
     353          19 :     return this;
     354             :   }
     355             : 
     356             :   @CanIgnoreReturnValue
     357             :   public ChangeInserter setFireRevisionCreated(boolean fireRevisionCreated) {
     358           1 :     this.fireRevisionCreated = fireRevisionCreated;
     359           1 :     return this;
     360             :   }
     361             : 
     362             :   @CanIgnoreReturnValue
     363             :   public ChangeInserter setSendMail(boolean sendMail) {
     364          88 :     this.sendMail = sendMail;
     365          88 :     return this;
     366             :   }
     367             : 
     368             :   @CanIgnoreReturnValue
     369             :   public ChangeInserter setRequestScopePropagator(RequestScopePropagator r) {
     370          88 :     this.requestScopePropagator = r;
     371          88 :     return this;
     372             :   }
     373             : 
     374             :   @CanIgnoreReturnValue
     375             :   public ChangeInserter setRevertOf(Change.Id revertOf) {
     376          19 :     this.revertOf = revertOf;
     377          19 :     return this;
     378             :   }
     379             : 
     380             :   public void setPushCertificate(String cert) {
     381           0 :     pushCert = cert;
     382           0 :   }
     383             : 
     384             :   public PatchSet getPatchSet() {
     385           0 :     checkState(patchSet != null, "getPatchSet() only valid after creating change");
     386           0 :     return patchSet;
     387             :   }
     388             : 
     389             :   @CanIgnoreReturnValue
     390             :   public ChangeInserter setApprovals(Map<String, Short> approvals) {
     391          89 :     this.approvals = approvals;
     392          89 :     return this;
     393             :   }
     394             : 
     395             :   /**
     396             :    * Set whether to include the new patch set ref update in this update.
     397             :    *
     398             :    * <p>If false, the caller is responsible for creating the patch set ref <strong>before</strong>
     399             :    * executing the containing {@code BatchUpdate}.
     400             :    *
     401             :    * <p>Should not be used in new code, as it doesn't result in a single atomic batch ref update for
     402             :    * code and NoteDb meta refs.
     403             :    *
     404             :    * @param updateRef whether to update the ref during {@link #updateRepo(RepoContext)}.
     405             :    */
     406             :   @Deprecated
     407             :   @CanIgnoreReturnValue
     408             :   public ChangeInserter setUpdateRef(boolean updateRef) {
     409           1 :     this.updateRef = updateRef;
     410           1 :     return this;
     411             :   }
     412             : 
     413             :   @Nullable
     414             :   public String getChangeMessage() {
     415           0 :     if (message == null) {
     416           0 :       return null;
     417             :     }
     418           0 :     checkState(changeMessage != null, "getChangeMessage() only valid after inserting change");
     419           0 :     return changeMessage;
     420             :   }
     421             : 
     422             :   public ReceiveCommand getCommand() {
     423           0 :     return cmd;
     424             :   }
     425             : 
     426             :   @Override
     427             :   public void updateRepo(RepoContext ctx) throws ResourceConflictException, IOException {
     428         103 :     cmd = new ReceiveCommand(ObjectId.zeroId(), commitId, psId.toRefName());
     429         103 :     projectState = projectCache.get(ctx.getProject()).orElseThrow(illegalState(ctx.getProject()));
     430         103 :     validate(ctx);
     431         103 :     if (!updateRef) {
     432           1 :       return;
     433             :     }
     434         103 :     ctx.addRefUpdate(cmd);
     435         103 :     Optional<ReceiveCommand> autoMerge =
     436         103 :         autoMerger.createAutoMergeCommitIfNecessary(
     437         103 :             ctx.getRepoView(),
     438         103 :             ctx.getRevWalk(),
     439         103 :             ctx.getInserter(),
     440         103 :             ctx.getRevWalk().parseCommit(commitId));
     441         103 :     if (autoMerge.isPresent()) {
     442          29 :       ctx.addRefUpdate(autoMerge.get());
     443             :     }
     444         103 :   }
     445             : 
     446             :   @Override
     447             :   public boolean updateChange(ChangeContext ctx)
     448             :       throws RestApiException, IOException, PermissionBackendException, ConfigInvalidException {
     449         103 :     change = ctx.getChange(); // Use defensive copy created by ChangeControl.
     450         103 :     patchSetInfo =
     451         103 :         patchSetInfoFactory.get(ctx.getRevWalk(), ctx.getRevWalk().parseCommit(commitId), psId);
     452         103 :     ctx.getChange().setCurrentPatchSet(patchSetInfo);
     453             : 
     454         103 :     ChangeUpdate update = ctx.getUpdate(psId);
     455         103 :     update.setChangeId(change.getKey().get());
     456         103 :     update.setSubjectForCommit("Create change");
     457         103 :     update.setBranch(change.getDest().branch());
     458             :     try {
     459         103 :       update.setTopic(change.getTopic());
     460           0 :     } catch (ValidationException ex) {
     461           0 :       throw new BadRequestException(ex.getMessage());
     462         103 :     }
     463         103 :     update.setPsDescription(patchSetDescription);
     464         103 :     update.setPrivate(isPrivate);
     465         103 :     update.setWorkInProgress(workInProgress);
     466         103 :     if (revertOf != null) {
     467          14 :       update.setRevertOf(revertOf.get());
     468             :     }
     469         103 :     if (cherryPickOf != null) {
     470           8 :       update.setCherryPickOf(cherryPickOf.getCommaSeparatedChangeAndPatchSetId());
     471             :     }
     472             : 
     473         103 :     List<String> newGroups = groups;
     474         103 :     if (newGroups.isEmpty()) {
     475          45 :       newGroups = GroupCollector.getDefaultGroups(commitId);
     476             :     }
     477         103 :     patchSet =
     478         103 :         psUtil.insert(
     479         103 :             ctx.getRevWalk(), update, psId, commitId, newGroups, pushCert, patchSetDescription);
     480             : 
     481             :     /* TODO: fixStatusToMerged is used here because the tests
     482             :      * (byStatusClosed() in AbstractQueryChangesTest)
     483             :      * insert changes that are already merged,
     484             :      * and setStatus may not be used to set the Status to merged
     485             :      *
     486             :      * is it possible to make the tests use the merge code path,
     487             :      * instead of setting the status directly?
     488             :      */
     489         103 :     if (change.getStatus() == Change.Status.MERGED) {
     490           7 :       update.fixStatusToMerged(new SubmissionId(change));
     491             :     } else {
     492         103 :       update.setStatus(change.getStatus());
     493             :     }
     494             : 
     495         103 :     reviewerAdditions =
     496         103 :         reviewerModifier.prepare(ctx.getNotes(), ctx.getUser(), getReviewerInputs(), true);
     497         103 :     Optional<ReviewerModification> reviewerError =
     498         103 :         reviewerAdditions.getFailures().stream().findFirst();
     499         103 :     if (reviewerError.isPresent()) {
     500           3 :       throw new UnprocessableEntityException(reviewerError.get().result.error);
     501             :     }
     502         103 :     reviewerAdditions.updateChange(ctx, patchSet);
     503             : 
     504         103 :     LabelTypes labelTypes = projectState.getLabelTypes();
     505         103 :     approvalsUtil.addApprovalsForNewPatchSet(
     506         103 :         update, labelTypes, patchSet, ctx.getUser(), approvals);
     507             : 
     508             :     // Check if approvals are changing with this update. If so, add the current user (aka the
     509             :     // approver) as a reviewers because all approvers must also be reviewers.
     510             :     // Note that this is done separately as addReviewers is filtering out the change owner as a
     511             :     // reviewer which is needed in several other code paths.
     512         103 :     if (!approvals.isEmpty()) {
     513           4 :       update.putReviewer(ctx.getAccountId(), REVIEWER);
     514             :     }
     515         103 :     if (message != null) {
     516         102 :       changeMessage =
     517         102 :           cmUtil.setChangeMessage(
     518         102 :               update, message, ChangeMessagesUtil.uploadedPatchSetTag(workInProgress));
     519             :     }
     520         103 :     return true;
     521             :   }
     522             : 
     523             :   @Override
     524             :   public void postUpdate(PostUpdateContext ctx) throws Exception {
     525         103 :     reviewerAdditions.postUpdate(ctx);
     526         103 :     NotifyResolver.Result notify = ctx.getNotify(change.getId());
     527         103 :     if (sendMail && notify.shouldNotify()) {
     528         103 :       Runnable sender =
     529         103 :           new Runnable() {
     530             :             @Override
     531             :             public void run() {
     532             :               try {
     533         103 :                 CreateChangeSender emailSender =
     534         103 :                     createChangeSenderFactory.create(change.getProject(), change.getId());
     535         103 :                 emailSender.setFrom(change.getOwner());
     536         103 :                 emailSender.setPatchSet(patchSet, patchSetInfo);
     537         103 :                 emailSender.setNotify(notify);
     538         103 :                 emailSender.addReviewers(
     539         103 :                     reviewerAdditions.flattenResults(ReviewerOp.Result::addedReviewers).stream()
     540         103 :                         .map(PatchSetApproval::accountId)
     541         103 :                         .collect(toImmutableSet()));
     542         103 :                 emailSender.addReviewersByEmail(
     543         103 :                     reviewerAdditions.flattenResults(ReviewerOp.Result::addedReviewersByEmail));
     544         103 :                 emailSender.addExtraCC(
     545         103 :                     reviewerAdditions.flattenResults(ReviewerOp.Result::addedCCs));
     546         103 :                 emailSender.addExtraCCByEmail(
     547         103 :                     reviewerAdditions.flattenResults(ReviewerOp.Result::addedCCsByEmail));
     548         103 :                 emailSender.setMessageId(
     549         103 :                     messageIdGenerator.fromChangeUpdate(ctx.getRepoView(), patchSet.id()));
     550         103 :                 emailSender.send();
     551           0 :               } catch (Exception e) {
     552           0 :                 logger.atSevere().withCause(e).log(
     553           0 :                     "Cannot send email for new change %s", change.getId());
     554         103 :               }
     555         103 :             }
     556             : 
     557             :             @Override
     558             :             public String toString() {
     559           0 :               return "send-email newchange";
     560             :             }
     561             :           };
     562         103 :       if (requestScopePropagator != null) {
     563             :         @SuppressWarnings("unused")
     564          88 :         Future<?> possiblyIgnoredError =
     565          88 :             sendEmailExecutor.submit(requestScopePropagator.wrap(sender));
     566          88 :       } else {
     567          48 :         sender.run();
     568             :       }
     569             :     }
     570             : 
     571             :     /* For labels that are not set in this operation, show the "current" value
     572             :      * of 0, and no oldValue as the value was not modified by this operation.
     573             :      * For labels that are set in this operation, the value was modified, so
     574             :      * show a transition from an oldValue of 0 to the new value.
     575             :      */
     576         103 :     if (fireRevisionCreated) {
     577         103 :       revisionCreated.fire(
     578         103 :           ctx.getChangeData(change), patchSet, ctx.getAccount(), ctx.getWhen(), notify);
     579         103 :       if (approvals != null && !approvals.isEmpty()) {
     580           4 :         List<LabelType> labels = projectState.getLabelTypes(change.getDest()).getLabelTypes();
     581           4 :         Map<String, Short> allApprovals = new HashMap<>();
     582           4 :         Map<String, Short> oldApprovals = new HashMap<>();
     583           4 :         for (LabelType lt : labels) {
     584           4 :           allApprovals.put(lt.getName(), (short) 0);
     585           4 :           oldApprovals.put(lt.getName(), null);
     586           4 :         }
     587           4 :         for (Map.Entry<String, Short> entry : approvals.entrySet()) {
     588           4 :           if (entry.getValue() != 0) {
     589           4 :             allApprovals.put(entry.getKey(), entry.getValue());
     590           4 :             oldApprovals.put(entry.getKey(), (short) 0);
     591             :           }
     592           4 :         }
     593           4 :         commentAdded.fire(
     594           4 :             ctx.getChangeData(change),
     595             :             patchSet,
     596           4 :             ctx.getAccount(),
     597             :             null,
     598             :             allApprovals,
     599             :             oldApprovals,
     600           4 :             ctx.getWhen());
     601             :       }
     602             :     }
     603         103 :   }
     604             : 
     605             :   private void validate(RepoContext ctx) throws IOException, ResourceConflictException {
     606         103 :     if (!validate) {
     607          92 :       return;
     608             :     }
     609             : 
     610             :     try {
     611          48 :       try (CommitReceivedEvent event =
     612             :           new CommitReceivedEvent(
     613             :               cmd,
     614          48 :               projectState.getProject(),
     615          48 :               change.getDest().branch(),
     616             :               validationOptions,
     617          48 :               ctx.getRepoView().getConfig(),
     618          48 :               ctx.getRevWalk().getObjectReader(),
     619             :               commitId,
     620          48 :               ctx.getIdentifiedUser())) {
     621          48 :         commitValidatorsFactory
     622          48 :             .forGerritCommits(
     623          48 :                 permissionBackend.user(ctx.getUser()).project(ctx.getProject()),
     624          48 :                 BranchNameKey.create(ctx.getProject(), refName),
     625          48 :                 ctx.getIdentifiedUser(),
     626             :                 new NoSshInfo(),
     627          48 :                 ctx.getRevWalk(),
     628             :                 change)
     629          48 :             .validate(event);
     630             :       }
     631           1 :     } catch (CommitValidationException e) {
     632           1 :       throw new ResourceConflictException(e.getFullMessage());
     633          48 :     }
     634          48 :   }
     635             : 
     636             :   private static InternalReviewerInput newReviewerInput(
     637             :       String reviewer, ReviewerState state, boolean skipVisibilityCheck) {
     638             :     // Disable individual emails when adding reviewers, as all reviewers will receive the single
     639             :     // bulk new change email.
     640          10 :     InternalReviewerInput input =
     641          10 :         ReviewerModifier.newReviewerInput(reviewer, state, NotifyHandling.NONE);
     642             : 
     643             :     // Ignore failures for reasons like the reviewer being inactive or being unable to see the
     644             :     // change. This is required for the push path, where it automatically sets reviewers from
     645             :     // certain commit footers: putting a nonexistent user in a footer should not cause an error. In
     646             :     // theory we could provide finer control to do this for some reviewers and not others, but it's
     647             :     // not worth complicating the ChangeInserter interface further at this time.
     648          10 :     input.otherFailureBehavior = ReviewerModifier.FailureBehavior.IGNORE_EXCEPT_NOT_FOUND;
     649             : 
     650          10 :     input.skipVisibilityCheck = skipVisibilityCheck;
     651             : 
     652          10 :     return input;
     653             :   }
     654             : 
     655             :   private ImmutableList<InternalReviewerInput> getReviewerInputs() {
     656         103 :     return Streams.concat(
     657         103 :             reviewerInputs.stream(),
     658         103 :             Streams.stream(
     659         103 :                 newReviewerInputFromCommitIdentity(
     660             :                     change,
     661         103 :                     patchSetInfo.getCommitId(),
     662         103 :                     patchSetInfo.getAuthor().getAccount(),
     663             :                     NotifyHandling.NONE,
     664         103 :                     change.getOwner())),
     665         103 :             Streams.stream(
     666         103 :                 newReviewerInputFromCommitIdentity(
     667             :                     change,
     668         103 :                     patchSetInfo.getCommitId(),
     669         103 :                     patchSetInfo.getCommitter().getAccount(),
     670             :                     NotifyHandling.NONE,
     671         103 :                     change.getOwner())))
     672         103 :         .collect(toImmutableList());
     673             :   }
     674             : }

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