LCOV - code coverage report
Current view: top level - acceptance/testsuite/change - ChangeOperationsImpl.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 206 212 97.2 %
Date: 2022-11-19 15:00:39 Functions: 55 55 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2020 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.acceptance.testsuite.change;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkState;
      18             : import static com.google.common.collect.ImmutableList.toImmutableList;
      19             : 
      20             : import com.google.common.collect.ImmutableList;
      21             : import com.google.common.collect.ImmutableSet;
      22             : import com.google.common.collect.Streams;
      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.entities.Project;
      27             : import com.google.gerrit.entities.RefNames;
      28             : import com.google.gerrit.exceptions.StorageException;
      29             : import com.google.gerrit.extensions.restapi.BadRequestException;
      30             : import com.google.gerrit.extensions.restapi.RestApiException;
      31             : import com.google.gerrit.server.ChangeUtil;
      32             : import com.google.gerrit.server.GerritPersonIdent;
      33             : import com.google.gerrit.server.IdentifiedUser;
      34             : import com.google.gerrit.server.account.AccountResolver;
      35             : import com.google.gerrit.server.change.ChangeFinder;
      36             : import com.google.gerrit.server.change.ChangeInserter;
      37             : import com.google.gerrit.server.change.PatchSetInserter;
      38             : import com.google.gerrit.server.edit.tree.TreeCreator;
      39             : import com.google.gerrit.server.edit.tree.TreeModification;
      40             : import com.google.gerrit.server.git.GitRepositoryManager;
      41             : import com.google.gerrit.server.notedb.ChangeNotes;
      42             : import com.google.gerrit.server.notedb.Sequences;
      43             : import com.google.gerrit.server.project.ProjectCache;
      44             : import com.google.gerrit.server.update.BatchUpdate;
      45             : import com.google.gerrit.server.update.UpdateException;
      46             : import com.google.gerrit.server.util.CommitMessageUtil;
      47             : import com.google.gerrit.server.util.time.TimeUtil;
      48             : import com.google.inject.Inject;
      49             : import java.io.IOException;
      50             : import java.time.Instant;
      51             : import java.util.Arrays;
      52             : import java.util.Objects;
      53             : import java.util.Optional;
      54             : import org.eclipse.jgit.errors.ConfigInvalidException;
      55             : import org.eclipse.jgit.lib.AnyObjectId;
      56             : import org.eclipse.jgit.lib.CommitBuilder;
      57             : import org.eclipse.jgit.lib.Config;
      58             : import org.eclipse.jgit.lib.ObjectId;
      59             : import org.eclipse.jgit.lib.ObjectInserter;
      60             : import org.eclipse.jgit.lib.PersonIdent;
      61             : import org.eclipse.jgit.lib.Ref;
      62             : import org.eclipse.jgit.lib.Repository;
      63             : import org.eclipse.jgit.merge.MergeStrategy;
      64             : import org.eclipse.jgit.merge.Merger;
      65             : import org.eclipse.jgit.revwalk.RevCommit;
      66             : import org.eclipse.jgit.revwalk.RevWalk;
      67             : import org.eclipse.jgit.util.ChangeIdUtil;
      68             : 
      69             : /**
      70             :  * The implementation of {@link ChangeOperations}.
      71             :  *
      72             :  * <p>There is only one implementation of {@link ChangeOperations}. Nevertheless, we keep the
      73             :  * separation between interface and implementation to enhance clarity.
      74             :  */
      75             : public class ChangeOperationsImpl implements ChangeOperations {
      76             :   private final Sequences seq;
      77             :   private final ChangeInserter.Factory changeInserterFactory;
      78             :   private final PatchSetInserter.Factory patchsetInserterFactory;
      79             :   private final GitRepositoryManager repositoryManager;
      80             :   private final AccountResolver resolver;
      81             :   private final IdentifiedUser.GenericFactory userFactory;
      82             :   private final PersonIdent serverIdent;
      83             :   private final BatchUpdate.Factory batchUpdateFactory;
      84             :   private final ProjectCache projectCache;
      85             :   private final ChangeFinder changeFinder;
      86             :   private final PerPatchsetOperationsImpl.Factory perPatchsetOperationsFactory;
      87             :   private final PerCommentOperationsImpl.Factory perCommentOperationsFactory;
      88             :   private final PerDraftCommentOperationsImpl.Factory perDraftCommentOperationsFactory;
      89             :   private final PerRobotCommentOperationsImpl.Factory perRobotCommentOperationsFactory;
      90             : 
      91             :   @Inject
      92             :   public ChangeOperationsImpl(
      93             :       Sequences seq,
      94             :       ChangeInserter.Factory changeInserterFactory,
      95             :       PatchSetInserter.Factory patchsetInserterFactory,
      96             :       GitRepositoryManager repositoryManager,
      97             :       AccountResolver resolver,
      98             :       IdentifiedUser.GenericFactory userFactory,
      99             :       @GerritPersonIdent PersonIdent serverIdent,
     100             :       BatchUpdate.Factory batchUpdateFactory,
     101             :       ProjectCache projectCache,
     102             :       ChangeFinder changeFinder,
     103             :       PerPatchsetOperationsImpl.Factory perPatchsetOperationsFactory,
     104             :       PerCommentOperationsImpl.Factory perCommentOperationsFactory,
     105             :       PerDraftCommentOperationsImpl.Factory perDraftCommentOperationsFactory,
     106          14 :       PerRobotCommentOperationsImpl.Factory perRobotCommentOperationsFactory) {
     107          14 :     this.seq = seq;
     108          14 :     this.changeInserterFactory = changeInserterFactory;
     109          14 :     this.patchsetInserterFactory = patchsetInserterFactory;
     110          14 :     this.repositoryManager = repositoryManager;
     111          14 :     this.resolver = resolver;
     112          14 :     this.userFactory = userFactory;
     113          14 :     this.serverIdent = serverIdent;
     114          14 :     this.batchUpdateFactory = batchUpdateFactory;
     115          14 :     this.projectCache = projectCache;
     116          14 :     this.changeFinder = changeFinder;
     117          14 :     this.perPatchsetOperationsFactory = perPatchsetOperationsFactory;
     118          14 :     this.perCommentOperationsFactory = perCommentOperationsFactory;
     119          14 :     this.perDraftCommentOperationsFactory = perDraftCommentOperationsFactory;
     120          14 :     this.perRobotCommentOperationsFactory = perRobotCommentOperationsFactory;
     121          14 :   }
     122             : 
     123             :   @Override
     124             :   public PerChangeOperations change(Change.Id changeId) {
     125           9 :     return new PerChangeOperationsImpl(changeId);
     126             :   }
     127             : 
     128             :   @Override
     129             :   public TestChangeCreation.Builder newChange() {
     130          14 :     return TestChangeCreation.builder(this::createChange);
     131             :   }
     132             : 
     133             :   private Change.Id createChange(TestChangeCreation changeCreation) throws Exception {
     134          14 :     Change.Id changeId = Change.id(seq.nextChangeId());
     135          14 :     Project.NameKey project = getTargetProject(changeCreation);
     136             : 
     137          14 :     try (Repository repository = repositoryManager.openRepository(project);
     138          14 :         ObjectInserter objectInserter = repository.newObjectInserter();
     139          14 :         RevWalk revWalk = new RevWalk(objectInserter.newReader())) {
     140          14 :       Instant now = TimeUtil.now();
     141          14 :       IdentifiedUser changeOwner = getChangeOwner(changeCreation);
     142          14 :       PersonIdent authorAndCommitter = changeOwner.newCommitterIdent(now, serverIdent.getZoneId());
     143          14 :       ObjectId commitId =
     144          14 :           createCommit(repository, revWalk, objectInserter, changeCreation, authorAndCommitter);
     145             : 
     146          14 :       String refName = RefNames.fullName(changeCreation.branch());
     147          14 :       ChangeInserter inserter = getChangeInserter(changeId, refName, commitId);
     148          14 :       changeCreation.topic().ifPresent(t -> inserter.setTopic(t));
     149          14 :       inserter.setApprovals(changeCreation.approvals());
     150             : 
     151          14 :       try (BatchUpdate batchUpdate = batchUpdateFactory.create(project, changeOwner, now)) {
     152          14 :         batchUpdate.setRepository(repository, revWalk, objectInserter);
     153          14 :         batchUpdate.insertChange(inserter);
     154          14 :         batchUpdate.execute();
     155             :       }
     156          14 :       return changeId;
     157             :     }
     158             :   }
     159             : 
     160             :   private Project.NameKey getTargetProject(TestChangeCreation changeCreation) {
     161          14 :     if (changeCreation.project().isPresent()) {
     162           7 :       return changeCreation.project().get();
     163             :     }
     164             : 
     165           8 :     return getArbitraryProject();
     166             :   }
     167             : 
     168             :   private Project.NameKey getArbitraryProject() {
     169           8 :     Project.NameKey allProjectsName = projectCache.getAllProjects().getNameKey();
     170           8 :     Project.NameKey allUsersName = projectCache.getAllUsers().getNameKey();
     171           8 :     Optional<Project.NameKey> arbitraryProject =
     172           8 :         projectCache.all().stream()
     173           8 :             .filter(
     174             :                 name ->
     175           8 :                     !Objects.equals(name, allProjectsName) && !Objects.equals(name, allUsersName))
     176           8 :             .findFirst();
     177           8 :     checkState(
     178           8 :         arbitraryProject.isPresent(),
     179             :         "At least one repository must be available on the Gerrit server");
     180           8 :     return arbitraryProject.get();
     181             :   }
     182             : 
     183             :   private IdentifiedUser getChangeOwner(TestChangeCreation changeCreation)
     184             :       throws IOException, ConfigInvalidException {
     185          14 :     if (changeCreation.owner().isPresent()) {
     186           2 :       return userFactory.create(changeCreation.owner().get());
     187             :     }
     188             : 
     189          14 :     return getArbitraryUser();
     190             :   }
     191             : 
     192             :   private IdentifiedUser getArbitraryUser() throws ConfigInvalidException, IOException {
     193          14 :     ImmutableSet<Account.Id> foundAccounts = resolver.resolveIgnoreVisibility("").asIdSet();
     194          14 :     checkState(
     195          14 :         !foundAccounts.isEmpty(),
     196             :         "At least one user account must be available on the Gerrit server");
     197          14 :     return userFactory.create(foundAccounts.iterator().next());
     198             :   }
     199             : 
     200             :   private ObjectId createCommit(
     201             :       Repository repository,
     202             :       RevWalk revWalk,
     203             :       ObjectInserter objectInserter,
     204             :       TestChangeCreation changeCreation,
     205             :       PersonIdent authorAndCommitter)
     206             :       throws IOException, BadRequestException {
     207          14 :     ImmutableList<ObjectId> parentCommits = getParentCommits(repository, revWalk, changeCreation);
     208          14 :     TreeCreator treeCreator =
     209          14 :         getTreeCreator(objectInserter, parentCommits, changeCreation.mergeStrategy());
     210          14 :     ObjectId tree = createNewTree(repository, treeCreator, changeCreation.treeModifications());
     211          14 :     String commitMessage = correctCommitMessage(changeCreation.commitMessage());
     212          14 :     return createCommit(
     213             :         objectInserter, tree, parentCommits, authorAndCommitter, authorAndCommitter, commitMessage);
     214             :   }
     215             : 
     216             :   private ImmutableList<ObjectId> getParentCommits(
     217             :       Repository repository, RevWalk revWalk, TestChangeCreation changeCreation) {
     218             : 
     219          14 :     return changeCreation
     220          14 :         .parents()
     221          14 :         .map(parents -> resolveParents(repository, revWalk, parents))
     222          14 :         .orElseGet(() -> asImmutableList(getTip(repository, changeCreation.branch())));
     223             :   }
     224             : 
     225             :   private ImmutableList<ObjectId> resolveParents(
     226             :       Repository repository, RevWalk revWalk, ImmutableList<TestCommitIdentifier> parents) {
     227           5 :     return parents.stream()
     228           5 :         .map(parent -> resolveCommit(repository, revWalk, parent))
     229           5 :         .collect(toImmutableList());
     230             :   }
     231             : 
     232             :   private ObjectId resolveCommit(
     233             :       Repository repository, RevWalk revWalk, TestCommitIdentifier parentCommit) {
     234           5 :     switch (parentCommit.getKind()) {
     235             :       case BRANCH:
     236           2 :         return resolveBranchTip(repository, parentCommit.branch());
     237             :       case CHANGE_ID:
     238           4 :         return resolveChange(parentCommit.changeId());
     239             :       case COMMIT_SHA_1:
     240           1 :         return resolveCommitFromSha1(revWalk, parentCommit.commitSha1());
     241             :       case PATCHSET_ID:
     242           3 :         return resolvePatchset(parentCommit.patchsetId());
     243             :       default:
     244           0 :         throw new IllegalStateException(
     245           0 :             String.format("No parent behavior implemented for %s.", parentCommit.getKind()));
     246             :     }
     247             :   }
     248             : 
     249             :   private static ObjectId resolveBranchTip(Repository repository, String branchName) {
     250           2 :     return getTip(repository, branchName)
     251           2 :         .orElseThrow(
     252             :             () ->
     253           1 :                 new IllegalStateException(
     254           1 :                     String.format(
     255             :                         "Tip of branch %s not found and hence can't be used as parent.",
     256             :                         branchName)));
     257             :   }
     258             : 
     259             :   private static Optional<ObjectId> getTip(Repository repository, String branch) {
     260             :     try {
     261          14 :       Optional<Ref> ref = Optional.ofNullable(repository.findRef(branch));
     262          14 :       return ref.map(Ref::getObjectId);
     263           0 :     } catch (IOException e) {
     264           0 :       throw new StorageException(e);
     265             :     }
     266             :   }
     267             : 
     268             :   private ObjectId resolveChange(Change.Id changeId) {
     269           4 :     Optional<ChangeNotes> changeNotes = changeFinder.findOne(changeId);
     270           4 :     return changeNotes
     271           4 :         .map(ChangeNotes::getCurrentPatchSet)
     272           4 :         .map(PatchSet::commitId)
     273           4 :         .orElseThrow(
     274             :             () ->
     275           1 :                 new IllegalStateException(
     276           1 :                     String.format(
     277             :                         "Change %s not found and hence can't be used as parent.", changeId)));
     278             :   }
     279             : 
     280             :   private static RevCommit resolveCommitFromSha1(RevWalk revWalk, ObjectId commitSha1) {
     281             :     try {
     282           5 :       return revWalk.parseCommit(commitSha1);
     283           1 :     } catch (Exception e) {
     284           1 :       throw new IllegalStateException(
     285           1 :           String.format("Commit %s not found and hence can't be used as parent/base.", commitSha1),
     286             :           e);
     287             :     }
     288             :   }
     289             : 
     290             :   private ObjectId resolvePatchset(PatchSet.Id patchsetId) {
     291           3 :     Optional<ChangeNotes> changeNotes = changeFinder.findOne(patchsetId.changeId());
     292           3 :     return changeNotes
     293           3 :         .map(ChangeNotes::getPatchSets)
     294           3 :         .map(patchsets -> patchsets.get(patchsetId))
     295           3 :         .map(PatchSet::commitId)
     296           3 :         .orElseThrow(
     297             :             () ->
     298           1 :                 new IllegalStateException(
     299           1 :                     String.format(
     300             :                         "Patchset %s not found and hence can't be used as parent.", patchsetId)));
     301             :   }
     302             : 
     303             :   private static <T> ImmutableList<T> asImmutableList(Optional<T> value) {
     304          14 :     return Streams.stream(value).collect(toImmutableList());
     305             :   }
     306             : 
     307             :   private static TreeCreator getTreeCreator(
     308             :       RevWalk revWalk, ObjectId customBaseCommit, ImmutableList<ObjectId> parentCommits) {
     309           5 :     RevCommit commit = resolveCommitFromSha1(revWalk, customBaseCommit);
     310             :     // Use actual parents; relevant for example when a file is restored (->
     311             :     // RestoreFileModification).
     312           5 :     return TreeCreator.basedOnTree(commit.getTree(), parentCommits);
     313             :   }
     314             : 
     315             :   private static TreeCreator getTreeCreator(
     316             :       ObjectInserter objectInserter,
     317             :       ImmutableList<ObjectId> parentCommits,
     318             :       MergeStrategy mergeStrategy) {
     319          14 :     if (parentCommits.isEmpty()) {
     320           2 :       return TreeCreator.basedOnEmptyTree();
     321             :     }
     322          14 :     ObjectId baseTreeId = merge(objectInserter, parentCommits, mergeStrategy);
     323          14 :     return TreeCreator.basedOnTree(baseTreeId, parentCommits);
     324             :   }
     325             : 
     326             :   private static ObjectId merge(
     327             :       ObjectInserter objectInserter,
     328             :       ImmutableList<ObjectId> parentCommits,
     329             :       MergeStrategy mergeStrategy) {
     330             :     try {
     331          14 :       Merger merger = mergeStrategy.newMerger(objectInserter, new Config());
     332          14 :       boolean mergeSuccessful = merger.merge(parentCommits.toArray(new AnyObjectId[0]));
     333          14 :       if (!mergeSuccessful) {
     334           1 :         throw new IllegalStateException(
     335             :             "Conflicts encountered while merging the specified parents. Use"
     336             :                 + " mergeOfButBaseOnFirst() instead to avoid these conflicts and define any"
     337             :                 + " other desired file contents with file().content().");
     338             :       }
     339          14 :       return merger.getResultTreeId();
     340           0 :     } catch (IOException e) {
     341           0 :       throw new IllegalStateException(
     342             :           "Creating the merge commits of the specified parents failed for an unknown reason.", e);
     343             :     }
     344             :   }
     345             : 
     346             :   private static ObjectId createNewTree(
     347             :       Repository repository,
     348             :       TreeCreator treeCreator,
     349             :       ImmutableList<TreeModification> treeModifications)
     350             :       throws IOException {
     351          14 :     treeCreator.addTreeModifications(treeModifications);
     352          14 :     return treeCreator.createNewTreeAndGetId(repository);
     353             :   }
     354             : 
     355             :   private String correctCommitMessage(String desiredCommitMessage) throws BadRequestException {
     356          14 :     String commitMessage = CommitMessageUtil.checkAndSanitizeCommitMessage(desiredCommitMessage);
     357             : 
     358          14 :     if (ChangeIdUtil.indexOfChangeId(commitMessage, "\n") == -1) {
     359          14 :       ObjectId id = CommitMessageUtil.generateChangeId();
     360          14 :       commitMessage = ChangeIdUtil.insertId(commitMessage, id);
     361             :     }
     362             : 
     363          14 :     return commitMessage;
     364             :   }
     365             : 
     366             :   private ObjectId createCommit(
     367             :       ObjectInserter objectInserter,
     368             :       ObjectId tree,
     369             :       ImmutableList<ObjectId> parentCommitIds,
     370             :       PersonIdent author,
     371             :       PersonIdent committer,
     372             :       String commitMessage)
     373             :       throws IOException {
     374          14 :     CommitBuilder builder = new CommitBuilder();
     375          14 :     builder.setTreeId(tree);
     376          14 :     builder.setParentIds(parentCommitIds);
     377          14 :     builder.setAuthor(author);
     378          14 :     builder.setCommitter(committer);
     379          14 :     builder.setMessage(commitMessage);
     380          14 :     ObjectId newCommitId = objectInserter.insert(builder);
     381          14 :     objectInserter.flush();
     382          14 :     return newCommitId;
     383             :   }
     384             : 
     385             :   private ChangeInserter getChangeInserter(Change.Id changeId, String refName, ObjectId commitId) {
     386          14 :     ChangeInserter inserter = changeInserterFactory.create(changeId, commitId, refName);
     387          14 :     inserter.setMessage(String.format("Uploaded patchset %d.", inserter.getPatchSetId().get()));
     388          14 :     return inserter;
     389             :   }
     390             : 
     391             :   private class PerChangeOperationsImpl implements PerChangeOperations {
     392             : 
     393             :     private final Change.Id changeId;
     394             : 
     395           9 :     public PerChangeOperationsImpl(Change.Id changeId) {
     396           9 :       this.changeId = changeId;
     397           9 :     }
     398             : 
     399             :     @Override
     400             :     public boolean exists() {
     401           1 :       return changeFinder.findOne(changeId).isPresent();
     402             :     }
     403             : 
     404             :     @Override
     405             :     public TestChange get() {
     406           2 :       return toTestChange(getChangeNotes().getChange());
     407             :     }
     408             : 
     409             :     private ChangeNotes getChangeNotes() {
     410           9 :       Optional<ChangeNotes> changeNotes = changeFinder.findOne(changeId);
     411           9 :       checkState(changeNotes.isPresent(), "Tried to get non-existing test change.");
     412           9 :       return changeNotes.get();
     413             :     }
     414             : 
     415             :     private TestChange toTestChange(Change change) {
     416           2 :       return TestChange.builder()
     417           2 :           .numericChangeId(change.getId())
     418           2 :           .changeId(change.getKey().get())
     419           2 :           .build();
     420             :     }
     421             : 
     422             :     @Override
     423             :     public TestPatchsetCreation.Builder newPatchset() {
     424           5 :       return TestPatchsetCreation.builder(this::createPatchset);
     425             :     }
     426             : 
     427             :     private PatchSet.Id createPatchset(TestPatchsetCreation patchsetCreation)
     428             :         throws IOException, RestApiException, UpdateException {
     429           5 :       ChangeNotes changeNotes = getChangeNotes();
     430           5 :       Project.NameKey project = changeNotes.getProjectName();
     431           5 :       try (Repository repository = repositoryManager.openRepository(project);
     432           5 :           ObjectInserter objectInserter = repository.newObjectInserter();
     433           5 :           RevWalk revWalk = new RevWalk(objectInserter.newReader())) {
     434           5 :         Instant now = TimeUtil.now();
     435           5 :         ObjectId newPatchsetCommit =
     436           5 :             createPatchsetCommit(
     437             :                 repository, revWalk, objectInserter, changeNotes, patchsetCreation, now);
     438             : 
     439           5 :         PatchSet.Id patchsetId =
     440           5 :             ChangeUtil.nextPatchSetId(repository, changeNotes.getCurrentPatchSet().id());
     441           5 :         PatchSetInserter patchSetInserter =
     442           5 :             getPatchSetInserter(changeNotes, newPatchsetCommit, patchsetId);
     443             : 
     444           5 :         IdentifiedUser changeOwner = userFactory.create(changeNotes.getChange().getOwner());
     445           5 :         try (BatchUpdate batchUpdate = batchUpdateFactory.create(project, changeOwner, now)) {
     446           5 :           batchUpdate.setRepository(repository, revWalk, objectInserter);
     447           5 :           batchUpdate.addOp(changeId, patchSetInserter);
     448           5 :           batchUpdate.execute();
     449             :         }
     450           5 :         return patchsetId;
     451             :       }
     452             :     }
     453             : 
     454             :     private ObjectId createPatchsetCommit(
     455             :         Repository repository,
     456             :         RevWalk revWalk,
     457             :         ObjectInserter objectInserter,
     458             :         ChangeNotes changeNotes,
     459             :         TestPatchsetCreation patchsetCreation,
     460             :         Instant now)
     461             :         throws IOException, BadRequestException {
     462           5 :       ObjectId oldPatchsetCommitId = changeNotes.getCurrentPatchSet().commitId();
     463           5 :       RevCommit oldPatchsetCommit = repository.parseCommit(oldPatchsetCommitId);
     464             : 
     465           5 :       ImmutableList<ObjectId> parentCommitIds =
     466           5 :           getParents(repository, revWalk, patchsetCreation, oldPatchsetCommit);
     467           5 :       TreeCreator treeCreator = getTreeCreator(revWalk, oldPatchsetCommit, parentCommitIds);
     468           5 :       ObjectId tree = createNewTree(repository, treeCreator, patchsetCreation.treeModifications());
     469             : 
     470           5 :       String commitMessage =
     471           5 :           correctCommitMessage(
     472           5 :               changeNotes.getChange().getKey().get(),
     473           5 :               patchsetCreation.commitMessage().orElseGet(oldPatchsetCommit::getFullMessage));
     474             : 
     475           5 :       PersonIdent author = getAuthor(oldPatchsetCommit);
     476           5 :       PersonIdent committer = getCommitter(oldPatchsetCommit, now);
     477           5 :       return createCommit(objectInserter, tree, parentCommitIds, author, committer, commitMessage);
     478             :     }
     479             : 
     480             :     private String correctCommitMessage(String oldChangeId, String desiredCommitMessage)
     481             :         throws BadRequestException {
     482           5 :       String commitMessage = CommitMessageUtil.checkAndSanitizeCommitMessage(desiredCommitMessage);
     483             : 
     484             :       // Remove initial 'I' and treat the rest as ObjectId. This is not the cleanest approach but
     485             :       // unfortunately, we don't seem to have other utility code which takes the string-based
     486             :       // change-id and ensures that it is part of the commit message.
     487           5 :       ObjectId id = ObjectId.fromString(oldChangeId.substring(1));
     488           5 :       commitMessage = ChangeIdUtil.insertId(commitMessage, id, false);
     489             : 
     490           5 :       return commitMessage;
     491             :     }
     492             : 
     493             :     private PersonIdent getAuthor(RevCommit oldPatchsetCommit) {
     494           5 :       return Optional.ofNullable(oldPatchsetCommit.getAuthorIdent()).orElse(serverIdent);
     495             :     }
     496             : 
     497             :     private PersonIdent getCommitter(RevCommit oldPatchsetCommit, Instant now) {
     498           5 :       PersonIdent oldPatchsetCommitter =
     499           5 :           Optional.ofNullable(oldPatchsetCommit.getCommitterIdent()).orElse(serverIdent);
     500           5 :       if (asSeconds(now) == asSeconds(oldPatchsetCommitter.getWhenAsInstant())) {
     501             :         /* We need to ensure that the resulting commit SHA-1 is different from the old patchset.
     502             :          * In real situations, this automatically happens as two patchsets won't have exactly the
     503             :          * same commit timestamp even when the tree and commit message are the same. In tests,
     504             :          * we can easily end up with the same timestamp as Git uses second precision for timestamps.
     505             :          * We could of course require that tests must use TestTimeUtil#setClockStep but
     506             :          * that would be an unnecessary nuisance for test writers. Hence, go with a simple solution
     507             :          * here and simply add a second. */
     508           5 :         now = now.plusSeconds(1);
     509             :       }
     510           5 :       return new PersonIdent(oldPatchsetCommitter, now);
     511             :     }
     512             : 
     513             :     private long asSeconds(Instant date) {
     514           5 :       return date.getEpochSecond();
     515             :     }
     516             : 
     517             :     private ImmutableList<ObjectId> getParents(
     518             :         Repository repository,
     519             :         RevWalk revWalk,
     520             :         TestPatchsetCreation patchsetCreation,
     521             :         RevCommit oldPatchsetCommit) {
     522           5 :       return patchsetCreation
     523           5 :           .parents()
     524           5 :           .map(parents -> resolveParents(repository, revWalk, parents))
     525           5 :           .orElseGet(
     526           5 :               () -> Arrays.stream(oldPatchsetCommit.getParents()).collect(toImmutableList()));
     527             :     }
     528             : 
     529             :     private PatchSetInserter getPatchSetInserter(
     530             :         ChangeNotes changeNotes, ObjectId newPatchsetCommit, PatchSet.Id patchsetId) {
     531           5 :       PatchSetInserter patchSetInserter =
     532           5 :           patchsetInserterFactory.create(changeNotes, patchsetId, newPatchsetCommit);
     533           5 :       patchSetInserter.setCheckAddPatchSetPermission(false);
     534           5 :       patchSetInserter.setMessage(String.format("Uploaded patchset %d.", patchsetId.get()));
     535           5 :       return patchSetInserter;
     536             :     }
     537             : 
     538             :     @Override
     539             :     public PerPatchsetOperations patchset(PatchSet.Id patchsetId) {
     540           3 :       return perPatchsetOperationsFactory.create(getChangeNotes(), patchsetId);
     541             :     }
     542             : 
     543             :     @Override
     544             :     public PerPatchsetOperations currentPatchset() {
     545           6 :       ChangeNotes changeNotes = getChangeNotes();
     546           6 :       return perPatchsetOperationsFactory.create(
     547           6 :           changeNotes, changeNotes.getChange().currentPatchSetId());
     548             :     }
     549             : 
     550             :     @Override
     551             :     public PerCommentOperations comment(String commentUuid) {
     552           1 :       ChangeNotes changeNotes = getChangeNotes();
     553           1 :       return perCommentOperationsFactory.create(changeNotes, commentUuid);
     554             :     }
     555             : 
     556             :     @Override
     557             :     public PerDraftCommentOperations draftComment(String commentUuid) {
     558           2 :       ChangeNotes changeNotes = getChangeNotes();
     559           2 :       return perDraftCommentOperationsFactory.create(changeNotes, commentUuid);
     560             :     }
     561             : 
     562             :     @Override
     563             :     public PerRobotCommentOperations robotComment(String commentUuid) {
     564           1 :       ChangeNotes changeNotes = getChangeNotes();
     565           1 :       return perRobotCommentOperationsFactory.create(changeNotes, commentUuid);
     566             :     }
     567             :   }
     568             : }

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