LCOV - code coverage report
Current view: top level - server/git - CommitUtil.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 167 174 96.0 %
Date: 2022-11-19 15:00:39 Functions: 17 17 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2017 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;
      16             : 
      17             : import static com.google.common.base.MoreObjects.firstNonNull;
      18             : 
      19             : import com.google.common.base.Strings;
      20             : import com.google.common.collect.ImmutableListMultimap;
      21             : import com.google.common.flogger.FluentLogger;
      22             : import com.google.gerrit.common.Nullable;
      23             : import com.google.gerrit.entities.Account;
      24             : import com.google.gerrit.entities.Change;
      25             : import com.google.gerrit.entities.PatchSet;
      26             : import com.google.gerrit.extensions.api.changes.NotifyHandling;
      27             : import com.google.gerrit.extensions.api.changes.RevertInput;
      28             : import com.google.gerrit.extensions.common.CommitInfo;
      29             : import com.google.gerrit.extensions.restapi.BadRequestException;
      30             : import com.google.gerrit.extensions.restapi.ResourceConflictException;
      31             : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
      32             : import com.google.gerrit.extensions.restapi.RestApiException;
      33             : import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
      34             : import com.google.gerrit.server.ChangeMessagesUtil;
      35             : import com.google.gerrit.server.ChangeUtil;
      36             : import com.google.gerrit.server.CommonConverters;
      37             : import com.google.gerrit.server.CurrentUser;
      38             : import com.google.gerrit.server.GerritPersonIdent;
      39             : import com.google.gerrit.server.ReviewerSet;
      40             : import com.google.gerrit.server.approval.ApprovalsUtil;
      41             : import com.google.gerrit.server.change.ChangeInserter;
      42             : import com.google.gerrit.server.change.ChangeMessages;
      43             : import com.google.gerrit.server.change.NotifyResolver;
      44             : import com.google.gerrit.server.extensions.events.ChangeReverted;
      45             : import com.google.gerrit.server.mail.send.MessageIdGenerator;
      46             : import com.google.gerrit.server.mail.send.RevertedSender;
      47             : import com.google.gerrit.server.notedb.ChangeNotes;
      48             : import com.google.gerrit.server.notedb.ReviewerStateInternal;
      49             : import com.google.gerrit.server.notedb.Sequences;
      50             : import com.google.gerrit.server.query.change.ChangeData;
      51             : import com.google.gerrit.server.query.change.InternalChangeQuery;
      52             : import com.google.gerrit.server.update.BatchUpdate;
      53             : import com.google.gerrit.server.update.BatchUpdateOp;
      54             : import com.google.gerrit.server.update.ChangeContext;
      55             : import com.google.gerrit.server.update.PostUpdateContext;
      56             : import com.google.gerrit.server.update.UpdateException;
      57             : import com.google.gerrit.server.util.CommitMessageUtil;
      58             : import com.google.inject.Inject;
      59             : import com.google.inject.Provider;
      60             : import com.google.inject.Singleton;
      61             : import java.io.IOException;
      62             : import java.text.MessageFormat;
      63             : import java.time.Instant;
      64             : import java.util.ArrayList;
      65             : import java.util.HashSet;
      66             : import java.util.List;
      67             : import java.util.Map;
      68             : import java.util.Set;
      69             : import org.eclipse.jgit.errors.ConfigInvalidException;
      70             : import org.eclipse.jgit.errors.InvalidObjectIdException;
      71             : import org.eclipse.jgit.errors.MissingObjectException;
      72             : import org.eclipse.jgit.errors.RepositoryNotFoundException;
      73             : import org.eclipse.jgit.lib.CommitBuilder;
      74             : import org.eclipse.jgit.lib.ObjectId;
      75             : import org.eclipse.jgit.lib.ObjectInserter;
      76             : import org.eclipse.jgit.lib.ObjectReader;
      77             : import org.eclipse.jgit.lib.PersonIdent;
      78             : import org.eclipse.jgit.lib.Ref;
      79             : import org.eclipse.jgit.lib.Repository;
      80             : import org.eclipse.jgit.revwalk.RevCommit;
      81             : import org.eclipse.jgit.revwalk.RevWalk;
      82             : import org.eclipse.jgit.util.ChangeIdUtil;
      83             : 
      84             : /** Static utilities for working with {@link RevCommit}s. */
      85             : @Singleton
      86             : public class CommitUtil {
      87         146 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      88             : 
      89             :   private final GitRepositoryManager repoManager;
      90             :   private final Provider<PersonIdent> serverIdent;
      91             :   private final Sequences seq;
      92             :   private final ApprovalsUtil approvalsUtil;
      93             :   private final ChangeInserter.Factory changeInserterFactory;
      94             :   private final NotifyResolver notifyResolver;
      95             :   private final RevertedSender.Factory revertedSenderFactory;
      96             :   private final ChangeMessagesUtil cmUtil;
      97             :   private final ChangeNotes.Factory changeNotesFactory;
      98             :   private final ChangeReverted changeReverted;
      99             :   private final BatchUpdate.Factory updateFactory;
     100             :   private final MessageIdGenerator messageIdGenerator;
     101             : 
     102             :   @Inject
     103             :   CommitUtil(
     104             :       GitRepositoryManager repoManager,
     105             :       @GerritPersonIdent Provider<PersonIdent> serverIdent,
     106             :       Sequences seq,
     107             :       ApprovalsUtil approvalsUtil,
     108             :       ChangeInserter.Factory changeInserterFactory,
     109             :       NotifyResolver notifyResolver,
     110             :       RevertedSender.Factory revertedSenderFactory,
     111             :       ChangeMessagesUtil cmUtil,
     112             :       ChangeNotes.Factory changeNotesFactory,
     113             :       ChangeReverted changeReverted,
     114             :       BatchUpdate.Factory updateFactory,
     115         145 :       MessageIdGenerator messageIdGenerator) {
     116         145 :     this.repoManager = repoManager;
     117         145 :     this.serverIdent = serverIdent;
     118         145 :     this.seq = seq;
     119         145 :     this.approvalsUtil = approvalsUtil;
     120         145 :     this.changeInserterFactory = changeInserterFactory;
     121         145 :     this.notifyResolver = notifyResolver;
     122         145 :     this.revertedSenderFactory = revertedSenderFactory;
     123         145 :     this.cmUtil = cmUtil;
     124         145 :     this.changeNotesFactory = changeNotesFactory;
     125         145 :     this.changeReverted = changeReverted;
     126         145 :     this.updateFactory = updateFactory;
     127         145 :     this.messageIdGenerator = messageIdGenerator;
     128         145 :   }
     129             : 
     130             :   public static CommitInfo toCommitInfo(RevCommit commit) throws IOException {
     131           4 :     return toCommitInfo(commit, null);
     132             :   }
     133             : 
     134             :   public static CommitInfo toCommitInfo(RevCommit commit, @Nullable RevWalk walk)
     135             :       throws IOException {
     136           4 :     CommitInfo info = new CommitInfo();
     137           4 :     info.commit = commit.getName();
     138           4 :     info.author = CommonConverters.toGitPerson(commit.getAuthorIdent());
     139           4 :     info.committer = CommonConverters.toGitPerson(commit.getCommitterIdent());
     140           4 :     info.subject = commit.getShortMessage();
     141           4 :     info.message = commit.getFullMessage();
     142           4 :     info.parents = new ArrayList<>(commit.getParentCount());
     143           4 :     for (int i = 0; i < commit.getParentCount(); i++) {
     144           4 :       RevCommit p = walk == null ? commit.getParent(i) : walk.parseCommit(commit.getParent(i));
     145           4 :       CommitInfo parentInfo = new CommitInfo();
     146           4 :       parentInfo.commit = p.getName();
     147           4 :       parentInfo.subject = p.getShortMessage();
     148           4 :       info.parents.add(parentInfo);
     149             :     }
     150           4 :     return info;
     151             :   }
     152             : 
     153             :   /**
     154             :    * Allows creating a revert change.
     155             :    *
     156             :    * @param notes ChangeNotes of the change being reverted.
     157             :    * @param user Current User performing the revert.
     158             :    * @param input the RevertInput entity for conducting the revert.
     159             :    * @param timestamp timestamp for the created change.
     160             :    * @return ObjectId that represents the newly created commit.
     161             :    */
     162             :   public Change.Id createRevertChange(
     163             :       ChangeNotes notes, CurrentUser user, RevertInput input, Instant timestamp)
     164             :       throws RestApiException, UpdateException, ConfigInvalidException, IOException {
     165          14 :     String message = Strings.emptyToNull(input.message);
     166             : 
     167          14 :     try (Repository git = repoManager.openRepository(notes.getProjectName());
     168          14 :         ObjectInserter oi = git.newObjectInserter();
     169          14 :         ObjectReader reader = oi.newReader();
     170          14 :         RevWalk revWalk = new RevWalk(reader)) {
     171          14 :       ObjectId generatedChangeId = CommitMessageUtil.generateChangeId();
     172          14 :       ObjectId revCommit =
     173          14 :           createRevertCommit(message, notes, user, timestamp, oi, revWalk, generatedChangeId);
     174          14 :       return createRevertChangeFromCommit(
     175             :           revCommit, input, notes, user, generatedChangeId, timestamp, oi, revWalk, git);
     176           0 :     } catch (RepositoryNotFoundException e) {
     177           0 :       throw new ResourceNotFoundException(notes.getChangeId().toString(), e);
     178             :     }
     179             :   }
     180             : 
     181             :   /**
     182             :    * Wrapper function for creating a revert Commit.
     183             :    *
     184             :    * @param message Commit message for the revert commit.
     185             :    * @param notes ChangeNotes of the change being reverted.
     186             :    * @param user Current User performing the revert.
     187             :    * @param ts Timestamp of creation for the commit.
     188             :    * @return ObjectId that represents the newly created commit.
     189             :    */
     190             :   public ObjectId createRevertCommit(
     191             :       String message, ChangeNotes notes, CurrentUser user, Instant ts)
     192             :       throws RestApiException, IOException {
     193             : 
     194           1 :     try (Repository git = repoManager.openRepository(notes.getProjectName());
     195           1 :         ObjectInserter oi = git.newObjectInserter();
     196           1 :         ObjectReader reader = oi.newReader();
     197           1 :         RevWalk revWalk = new RevWalk(reader)) {
     198           1 :       return createRevertCommit(message, notes, user, ts, oi, revWalk, null);
     199           0 :     } catch (RepositoryNotFoundException e) {
     200           0 :       throw new ResourceNotFoundException(notes.getProjectName().toString(), e);
     201             :     }
     202             :   }
     203             : 
     204             :   /**
     205             :    * Creates a commit with the specified tree ID.
     206             :    *
     207             :    * @param oi ObjectInserter for inserting the newly created commit.
     208             :    * @param authorIdent of the new commit
     209             :    * @param committerIdent of the new commit
     210             :    * @param parentCommit of the new commit. Can be null.
     211             :    * @param commitMessage for the new commit.
     212             :    * @param treeId of the content for the new commit.
     213             :    * @return the newly created commit.
     214             :    * @throws IOException if fails to insert the commit.
     215             :    */
     216             :   public static ObjectId createCommitWithTree(
     217             :       ObjectInserter oi,
     218             :       PersonIdent authorIdent,
     219             :       PersonIdent committerIdent,
     220             :       @Nullable RevCommit parentCommit,
     221             :       String commitMessage,
     222             :       ObjectId treeId)
     223             :       throws IOException {
     224          33 :     logger.atFine().log("Creating commit with tree: %s", treeId.getName());
     225          33 :     CommitBuilder commit = new CommitBuilder();
     226          33 :     commit.setTreeId(treeId);
     227          33 :     if (parentCommit != null) {
     228          28 :       commit.setParentId(parentCommit);
     229             :     }
     230          33 :     commit.setAuthor(authorIdent);
     231          33 :     commit.setCommitter(committerIdent);
     232          33 :     commit.setMessage(commitMessage);
     233             : 
     234          33 :     ObjectId id = oi.insert(commit);
     235          33 :     oi.flush();
     236          33 :     return id;
     237             :   }
     238             : 
     239             :   /**
     240             :    * Creates a revert commit.
     241             :    *
     242             :    * @param message Commit message for the revert commit.
     243             :    * @param notes ChangeNotes of the change being reverted.
     244             :    * @param user Current User performing the revert.
     245             :    * @param ts Timestamp of creation for the commit.
     246             :    * @param oi ObjectInserter for inserting the newly created commit.
     247             :    * @param revWalk Used for parsing the original commit.
     248             :    * @param generatedChangeId The changeId for the commit message, can be null since it is not
     249             :    *     needed for commits, only for changes.
     250             :    * @return ObjectId that represents the newly created commit.
     251             :    * @throws ResourceConflictException Can't revert the initial commit.
     252             :    * @throws IOException Thrown in case of I/O errors.
     253             :    */
     254             :   private ObjectId createRevertCommit(
     255             :       String message,
     256             :       ChangeNotes notes,
     257             :       CurrentUser user,
     258             :       Instant ts,
     259             :       ObjectInserter oi,
     260             :       RevWalk revWalk,
     261             :       @Nullable ObjectId generatedChangeId)
     262             :       throws ResourceConflictException, IOException {
     263             : 
     264          14 :     PatchSet patch = notes.getCurrentPatchSet();
     265          14 :     RevCommit commitToRevert = revWalk.parseCommit(patch.commitId());
     266          14 :     if (commitToRevert.getParentCount() == 0) {
     267           1 :       throw new ResourceConflictException("Cannot revert initial commit");
     268             :     }
     269             : 
     270          14 :     PersonIdent committerIdent = serverIdent.get();
     271          14 :     PersonIdent authorIdent =
     272          14 :         user.asIdentifiedUser().newCommitterIdent(ts, committerIdent.getZoneId());
     273             : 
     274          14 :     RevCommit parentToCommitToRevert = commitToRevert.getParent(0);
     275          14 :     revWalk.parseHeaders(parentToCommitToRevert);
     276             : 
     277          14 :     Change changeToRevert = notes.getChange();
     278          14 :     String subject = changeToRevert.getSubject();
     279          14 :     if (subject.length() > 63) {
     280           1 :       subject = subject.substring(0, 59) + "...";
     281             :     }
     282          14 :     if (message == null) {
     283             :       message =
     284          14 :           MessageFormat.format(
     285          14 :               ChangeMessages.get().revertChangeDefaultMessage, subject, patch.commitId().name());
     286             :     }
     287          14 :     if (generatedChangeId != null) {
     288          14 :       message = ChangeIdUtil.insertId(message, generatedChangeId, true);
     289             :     }
     290             : 
     291          14 :     return createCommitWithTree(
     292          14 :         oi, authorIdent, committerIdent, commitToRevert, message, parentToCommitToRevert.getTree());
     293             :   }
     294             : 
     295             :   private Change.Id createRevertChangeFromCommit(
     296             :       ObjectId revertCommitId,
     297             :       RevertInput input,
     298             :       ChangeNotes notes,
     299             :       CurrentUser user,
     300             :       @Nullable ObjectId generatedChangeId,
     301             :       Instant ts,
     302             :       ObjectInserter oi,
     303             :       RevWalk revWalk,
     304             :       Repository git)
     305             :       throws IOException, RestApiException, UpdateException, ConfigInvalidException {
     306          14 :     RevCommit revertCommit = revWalk.parseCommit(revertCommitId);
     307          14 :     Change.Id changeId = Change.id(seq.nextChangeId());
     308          14 :     if (input.workInProgress) {
     309           2 :       input.notify = firstNonNull(input.notify, NotifyHandling.NONE);
     310             :     }
     311          14 :     NotifyResolver.Result notify =
     312          14 :         notifyResolver.resolve(firstNonNull(input.notify, NotifyHandling.ALL), input.notifyDetails);
     313             : 
     314          14 :     Change changeToRevert = notes.getChange();
     315          14 :     ChangeInserter ins =
     316             :         changeInserterFactory
     317          14 :             .create(changeId, revertCommit, changeToRevert.getDest().branch())
     318          14 :             .setTopic(input.topic == null ? changeToRevert.getTopic() : input.topic.trim());
     319          14 :     ins.setMessage("Uploaded patch set 1.");
     320          14 :     ins.setValidationOptions(getValidateOptionsAsMultimap(input.validationOptions));
     321             : 
     322          14 :     ReviewerSet reviewerSet = approvalsUtil.getReviewers(notes);
     323             : 
     324          14 :     Set<Account.Id> reviewers = new HashSet<>();
     325          14 :     reviewers.add(changeToRevert.getOwner());
     326          14 :     reviewers.addAll(reviewerSet.byState(ReviewerStateInternal.REVIEWER));
     327          14 :     reviewers.remove(user.getAccountId());
     328          14 :     Set<Account.Id> ccs = new HashSet<>(reviewerSet.byState(ReviewerStateInternal.CC));
     329          14 :     ccs.remove(user.getAccountId());
     330          14 :     ins.setReviewersAndCcsIgnoreVisibility(reviewers, ccs);
     331          14 :     ins.setRevertOf(notes.getChangeId());
     332          14 :     ins.setWorkInProgress(input.workInProgress);
     333             : 
     334          14 :     try (BatchUpdate bu = updateFactory.create(notes.getProjectName(), user, ts)) {
     335          14 :       bu.setRepository(git, revWalk, oi);
     336          14 :       bu.setNotify(notify);
     337          14 :       bu.insertChange(ins);
     338          14 :       if (!input.workInProgress) {
     339          13 :         addChangeRevertedNotificationOps(
     340          13 :             bu, changeToRevert.getId(), changeId, generatedChangeId.name());
     341             :       }
     342          14 :       bu.execute();
     343             :     }
     344          14 :     return changeId;
     345             :   }
     346             : 
     347             :   private static ImmutableListMultimap<String, String> getValidateOptionsAsMultimap(
     348             :       @Nullable Map<String, String> validationOptions) {
     349          14 :     if (validationOptions == null) {
     350          14 :       return ImmutableListMultimap.of();
     351             :     }
     352             : 
     353             :     ImmutableListMultimap.Builder<String, String> validationOptionsBuilder =
     354           1 :         ImmutableListMultimap.builder();
     355           1 :     validationOptions
     356           1 :         .entrySet()
     357           1 :         .forEach(e -> validationOptionsBuilder.put(e.getKey(), e.getValue()));
     358           1 :     return validationOptionsBuilder.build();
     359             :   }
     360             : 
     361             :   /**
     362             :    * Notify the owners of a change that their change is being reverted.
     363             :    *
     364             :    * @param bu to append the notification actions to.
     365             :    * @param revertedChangeId to be notified.
     366             :    * @param revertingChangeId to notify about.
     367             :    * @param revertingChangeKey to notify about.
     368             :    */
     369             :   public void addChangeRevertedNotificationOps(
     370             :       BatchUpdate bu,
     371             :       Change.Id revertedChangeId,
     372             :       Change.Id revertingChangeId,
     373             :       String revertingChangeKey) {
     374          14 :     bu.addOp(revertingChangeId, new ChangeRevertedNotifyOp(revertedChangeId, revertingChangeId));
     375          14 :     bu.addOp(revertedChangeId, new PostRevertedMessageOp(revertingChangeKey));
     376          14 :   }
     377             : 
     378             :   private class ChangeRevertedNotifyOp implements BatchUpdateOp {
     379             :     private final Change.Id revertedChangeId;
     380             :     private final Change.Id revertingChangeId;
     381             : 
     382          14 :     ChangeRevertedNotifyOp(Change.Id revertedChangeId, Change.Id revertingChangeId) {
     383          14 :       this.revertedChangeId = revertedChangeId;
     384          14 :       this.revertingChangeId = revertingChangeId;
     385          14 :     }
     386             : 
     387             :     @Override
     388             :     public void postUpdate(PostUpdateContext ctx) throws Exception {
     389          14 :       ChangeData revertedChange =
     390          14 :           ctx.getChangeData(changeNotesFactory.createChecked(ctx.getProject(), revertedChangeId));
     391          14 :       ChangeData revertingChange =
     392          14 :           ctx.getChangeData(changeNotesFactory.createChecked(ctx.getProject(), revertingChangeId));
     393          14 :       changeReverted.fire(revertedChange, revertingChange, ctx.getWhen());
     394             :       try {
     395          14 :         RevertedSender emailSender =
     396          14 :             revertedSenderFactory.create(ctx.getProject(), revertedChange.getId());
     397          14 :         emailSender.setFrom(ctx.getAccountId());
     398          14 :         emailSender.setNotify(ctx.getNotify(revertedChangeId));
     399          14 :         emailSender.setMessageId(
     400          14 :             messageIdGenerator.fromChangeUpdate(
     401          14 :                 ctx.getRepoView(), revertedChange.currentPatchSet().id()));
     402          14 :         emailSender.send();
     403           0 :       } catch (Exception err) {
     404           0 :         logger.atSevere().withCause(err).log(
     405             :             "Cannot send email for revert change %s", revertedChangeId);
     406          14 :       }
     407          14 :     }
     408             :   }
     409             : 
     410             :   private class PostRevertedMessageOp implements BatchUpdateOp {
     411             :     private final String revertingChangeKey;
     412             : 
     413          14 :     PostRevertedMessageOp(String revertingChangeKey) {
     414          14 :       this.revertingChangeKey = revertingChangeKey;
     415          14 :     }
     416             : 
     417             :     @Override
     418             :     public boolean updateChange(ChangeContext ctx) {
     419          14 :       cmUtil.setChangeMessage(
     420             :           ctx,
     421             :           "Created a revert of this change as I" + revertingChangeKey,
     422             :           ChangeMessagesUtil.TAG_REVERT);
     423          14 :       return true;
     424             :     }
     425             :   }
     426             : 
     427             :   /**
     428             :    * Returns the parent commit for a new commit.
     429             :    *
     430             :    * <p>If {@code baseSha1} is provided, the method verifies it can be used as a base. If {@code
     431             :    * baseSha1} is not provided the tip of the {@code destRef} is returned.
     432             :    *
     433             :    * @param project The name of the project.
     434             :    * @param changeQuery Used for looking up the base commit.
     435             :    * @param revWalk Used for parsing the base commit.
     436             :    * @param destRef The destination branch.
     437             :    * @param baseSha1 The hash of the base commit. Nullable.
     438             :    * @return the base commit. Either the commit matching the provided hash, or the direct parent if
     439             :    *     a hash was not provided.
     440             :    * @throws IOException if the branch reference cannot be parsed.
     441             :    * @throws RestApiException if the base commit cannot be fetched.
     442             :    */
     443             :   public static RevCommit getBaseCommit(
     444             :       String project,
     445             :       InternalChangeQuery changeQuery,
     446             :       RevWalk revWalk,
     447             :       Ref destRef,
     448             :       @Nullable String baseSha1)
     449             :       throws IOException, RestApiException {
     450           9 :     RevCommit destRefTip = revWalk.parseCommit(destRef.getObjectId());
     451             :     // The tip commit of the destination ref is the default base for the newly created change.
     452           9 :     if (Strings.isNullOrEmpty(baseSha1)) {
     453           8 :       return destRefTip;
     454             :     }
     455             : 
     456             :     ObjectId baseObjectId;
     457             :     try {
     458           4 :       baseObjectId = ObjectId.fromString(baseSha1);
     459           1 :     } catch (InvalidObjectIdException e) {
     460           1 :       throw new BadRequestException(
     461           1 :           String.format("Base %s doesn't represent a valid SHA-1", baseSha1), e);
     462           4 :     }
     463             : 
     464             :     RevCommit baseCommit;
     465             :     try {
     466           4 :       baseCommit = revWalk.parseCommit(baseObjectId);
     467           1 :     } catch (MissingObjectException e) {
     468           1 :       throw new UnprocessableEntityException(
     469           1 :           String.format("Base %s doesn't exist", baseObjectId.name()), e);
     470           4 :     }
     471             : 
     472           4 :     changeQuery.enforceVisibility(true);
     473           4 :     List<ChangeData> changeDatas = changeQuery.byBranchCommit(project, destRef.getName(), baseSha1);
     474             : 
     475           4 :     if (changeDatas.isEmpty()) {
     476           3 :       if (revWalk.isMergedInto(baseCommit, destRefTip)) {
     477             :         // The base commit is a merged commit with no change associated.
     478           3 :         return baseCommit;
     479             :       }
     480           1 :       throw new UnprocessableEntityException(
     481           1 :           String.format("Commit %s does not exist on branch %s", baseSha1, destRef.getName()));
     482           3 :     } else if (changeDatas.size() != 1) {
     483           0 :       throw new ResourceConflictException("Multiple changes found for commit " + baseSha1);
     484             :     }
     485             : 
     486           3 :     Change change = changeDatas.get(0).change();
     487           3 :     if (!change.isAbandoned()) {
     488             :       // The base commit is a valid change revision.
     489           3 :       return baseCommit;
     490             :     }
     491             : 
     492           1 :     throw new ResourceConflictException(
     493           1 :         String.format(
     494             :             "Change %s with commit %s is %s",
     495           1 :             change.getChangeId(), baseSha1, ChangeUtil.status(change)));
     496             :   }
     497             : }

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