LCOV - code coverage report
Current view: top level - server/restapi/change - ApplyPatch.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 55 67 82.1 %
Date: 2022-11-19 15:00:39 Functions: 4 4 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2022 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.restapi.change;
      16             : 
      17             : import com.google.gerrit.entities.BranchNameKey;
      18             : import com.google.gerrit.entities.Change;
      19             : import com.google.gerrit.entities.PatchSet;
      20             : import com.google.gerrit.entities.Project.NameKey;
      21             : import com.google.gerrit.extensions.api.changes.ApplyPatchPatchSetInput;
      22             : import com.google.gerrit.extensions.client.ListChangesOption;
      23             : import com.google.gerrit.extensions.common.ChangeInfo;
      24             : import com.google.gerrit.extensions.restapi.PreconditionFailedException;
      25             : import com.google.gerrit.extensions.restapi.ResourceConflictException;
      26             : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
      27             : import com.google.gerrit.extensions.restapi.Response;
      28             : import com.google.gerrit.extensions.restapi.RestApiException;
      29             : import com.google.gerrit.extensions.restapi.RestModifyView;
      30             : import com.google.gerrit.server.ChangeUtil;
      31             : import com.google.gerrit.server.GerritPersonIdent;
      32             : import com.google.gerrit.server.IdentifiedUser;
      33             : import com.google.gerrit.server.change.ChangeJson;
      34             : import com.google.gerrit.server.change.ChangeResource;
      35             : import com.google.gerrit.server.change.PatchSetInserter;
      36             : import com.google.gerrit.server.git.CodeReviewCommit;
      37             : import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
      38             : import com.google.gerrit.server.git.CommitUtil;
      39             : import com.google.gerrit.server.git.GitRepositoryManager;
      40             : import com.google.gerrit.server.notedb.ChangeNotes;
      41             : import com.google.gerrit.server.permissions.PermissionBackendException;
      42             : import com.google.gerrit.server.project.ContributorAgreementsChecker;
      43             : import com.google.gerrit.server.project.InvalidChangeOperationException;
      44             : import com.google.gerrit.server.project.NoSuchChangeException;
      45             : import com.google.gerrit.server.project.NoSuchProjectException;
      46             : import com.google.gerrit.server.query.change.ChangeData;
      47             : import com.google.gerrit.server.query.change.InternalChangeQuery;
      48             : import com.google.gerrit.server.update.BatchUpdate;
      49             : import com.google.gerrit.server.update.UpdateException;
      50             : import com.google.gerrit.server.util.CommitMessageUtil;
      51             : import com.google.gerrit.server.util.time.TimeUtil;
      52             : import com.google.inject.Inject;
      53             : import com.google.inject.Provider;
      54             : import com.google.inject.Singleton;
      55             : import java.io.IOException;
      56             : import java.time.Instant;
      57             : import java.time.ZoneId;
      58             : import org.eclipse.jgit.errors.ConfigInvalidException;
      59             : import org.eclipse.jgit.errors.RepositoryNotFoundException;
      60             : import org.eclipse.jgit.lib.ObjectId;
      61             : import org.eclipse.jgit.lib.ObjectInserter;
      62             : import org.eclipse.jgit.lib.ObjectReader;
      63             : import org.eclipse.jgit.lib.PersonIdent;
      64             : import org.eclipse.jgit.lib.Ref;
      65             : import org.eclipse.jgit.lib.Repository;
      66             : import org.eclipse.jgit.revwalk.RevCommit;
      67             : 
      68             : @Singleton
      69             : public class ApplyPatch implements RestModifyView<ChangeResource, ApplyPatchPatchSetInput> {
      70             :   private final ChangeJson.Factory jsonFactory;
      71             :   private final ContributorAgreementsChecker contributorAgreements;
      72             :   private final Provider<IdentifiedUser> user;
      73             :   private final GitRepositoryManager gitManager;
      74             :   private final BatchUpdate.Factory batchUpdateFactory;
      75             :   private final PatchSetInserter.Factory patchSetInserterFactory;
      76             :   private final Provider<InternalChangeQuery> queryProvider;
      77             :   private final ZoneId serverZoneId;
      78             : 
      79             :   @Inject
      80             :   ApplyPatch(
      81             :       ChangeJson.Factory jsonFactory,
      82             :       ContributorAgreementsChecker contributorAgreements,
      83             :       Provider<IdentifiedUser> user,
      84             :       GitRepositoryManager gitManager,
      85             :       BatchUpdate.Factory batchUpdateFactory,
      86             :       PatchSetInserter.Factory patchSetInserterFactory,
      87             :       Provider<InternalChangeQuery> queryProvider,
      88         145 :       @GerritPersonIdent PersonIdent myIdent) {
      89         145 :     this.jsonFactory = jsonFactory;
      90         145 :     this.contributorAgreements = contributorAgreements;
      91         145 :     this.user = user;
      92         145 :     this.gitManager = gitManager;
      93         145 :     this.batchUpdateFactory = batchUpdateFactory;
      94         145 :     this.patchSetInserterFactory = patchSetInserterFactory;
      95         145 :     this.queryProvider = queryProvider;
      96         145 :     this.serverZoneId = myIdent.getZoneId();
      97         145 :   }
      98             : 
      99             :   @Override
     100             :   public Response<ChangeInfo> apply(ChangeResource rsrc, ApplyPatchPatchSetInput input)
     101             :       throws IOException, UpdateException, RestApiException, PermissionBackendException,
     102             :           ConfigInvalidException, NoSuchProjectException, InvalidChangeOperationException {
     103           1 :     NameKey project = rsrc.getProject();
     104           1 :     contributorAgreements.check(project, rsrc.getUser());
     105           1 :     BranchNameKey destBranch = rsrc.getChange().getDest();
     106             : 
     107           1 :     try (Repository repo = gitManager.openRepository(project);
     108             :         // This inserter and revwalk *must* be passed to any BatchUpdates
     109             :         // created later on, to ensure the applied commit is flushed
     110             :         // before patch sets are updated.
     111           1 :         ObjectInserter oi = repo.newObjectInserter();
     112           1 :         ObjectReader reader = oi.newReader();
     113           1 :         CodeReviewRevWalk revWalk = CodeReviewCommit.newRevWalk(reader)) {
     114           1 :       Ref destRef = repo.getRefDatabase().exactRef(destBranch.branch());
     115           1 :       if (destRef == null) {
     116           0 :         throw new ResourceNotFoundException(
     117           0 :             String.format("Branch %s does not exist.", destBranch.branch()));
     118             :       }
     119           1 :       ChangeData destChange = rsrc.getChangeData();
     120           1 :       if (destChange == null) {
     121           0 :         throw new PreconditionFailedException(
     122             :             "patch:apply cannot be called without a destination change.");
     123             :       }
     124             : 
     125           1 :       if (destChange.change().isClosed()) {
     126           0 :         throw new PreconditionFailedException(
     127           0 :             String.format(
     128             :                 "patch:apply with Change-Id %s could not update the existing change %d "
     129             :                     + "in destination branch %s of project %s, because the change was closed (%s)",
     130           0 :                 destChange.getId(),
     131           0 :                 destChange.getId().get(),
     132           0 :                 destBranch.branch(),
     133           0 :                 destBranch.project(),
     134           0 :                 destChange.change().getStatus().name()));
     135             :       }
     136             : 
     137           1 :       RevCommit baseCommit =
     138           1 :           CommitUtil.getBaseCommit(
     139           1 :               project.get(), queryProvider.get(), revWalk, destRef, input.base);
     140           1 :       ObjectId treeId = ApplyPatchUtil.applyPatch(repo, oi, input.patch, baseCommit);
     141             : 
     142           1 :       Instant now = TimeUtil.now();
     143           1 :       PersonIdent committerIdent = user.get().newCommitterIdent(now, serverZoneId);
     144             :       PersonIdent authorIdent =
     145           1 :           input.author == null
     146           1 :               ? committerIdent
     147           1 :               : new PersonIdent(input.author.name, input.author.email, now, serverZoneId);
     148             :       String commitMessage =
     149           1 :           CommitMessageUtil.checkAndSanitizeCommitMessage(
     150           1 :               input.commitMessage != null
     151           1 :                   ? input.commitMessage
     152             :                   : "The following patch was applied:\n>\t"
     153           1 :                       + input.patch.patch.replaceAll("\n", "\n>\t"));
     154             : 
     155           1 :       ObjectId appliedCommit =
     156           1 :           CommitUtil.createCommitWithTree(
     157             :               oi, authorIdent, committerIdent, baseCommit, commitMessage, treeId);
     158           1 :       CodeReviewCommit commit = revWalk.parseCommit(appliedCommit);
     159           1 :       oi.flush();
     160             : 
     161             :       Change resultChange;
     162           1 :       try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.now())) {
     163           1 :         bu.setRepository(repo, revWalk, oi);
     164           1 :         resultChange =
     165           1 :             insertPatchSet(bu, repo, patchSetInserterFactory, destChange.notes(), commit);
     166           0 :       } catch (NoSuchChangeException | RepositoryNotFoundException e) {
     167           0 :         throw new ResourceConflictException(e.getMessage());
     168           1 :       }
     169           1 :       ChangeJson json = jsonFactory.create(ListChangesOption.CURRENT_REVISION);
     170           1 :       ChangeInfo changeInfo = json.format(resultChange);
     171           1 :       return Response.ok(changeInfo);
     172             :     }
     173             :   }
     174             : 
     175             :   private static Change insertPatchSet(
     176             :       BatchUpdate bu,
     177             :       Repository git,
     178             :       PatchSetInserter.Factory patchSetInserterFactory,
     179             :       ChangeNotes destNotes,
     180             :       CodeReviewCommit commit)
     181             :       throws IOException, UpdateException, RestApiException {
     182           1 :     Change destChange = destNotes.getChange();
     183           1 :     PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, destChange.currentPatchSetId());
     184           1 :     PatchSetInserter inserter = patchSetInserterFactory.create(destNotes, psId, commit);
     185           1 :     inserter.setMessage(buildMessageForPatchSet(psId));
     186           1 :     bu.addOp(destChange.getId(), inserter);
     187           1 :     bu.execute();
     188           1 :     return inserter.getChange();
     189             :   }
     190             : 
     191             :   private static String buildMessageForPatchSet(PatchSet.Id psId) {
     192           1 :     return new StringBuilder(String.format("Uploaded patch set %s.", psId.get())).toString();
     193             :   }
     194             : }

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