LCOV - code coverage report
Current view: top level - server/patch - PatchScriptFactory.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 91 113 80.5 %
Date: 2022-11-19 15:00:39 Functions: 12 12 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2009 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.patch;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkArgument;
      18             : import static com.google.common.base.Preconditions.checkState;
      19             : 
      20             : import com.google.common.collect.ImmutableList;
      21             : import com.google.common.flogger.FluentLogger;
      22             : import com.google.gerrit.common.Nullable;
      23             : import com.google.gerrit.common.data.PatchScript;
      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.exceptions.StorageException;
      28             : import com.google.gerrit.extensions.client.DiffPreferencesInfo;
      29             : import com.google.gerrit.extensions.restapi.AuthException;
      30             : import com.google.gerrit.server.CurrentUser;
      31             : import com.google.gerrit.server.PatchSetUtil;
      32             : import com.google.gerrit.server.edit.ChangeEdit;
      33             : import com.google.gerrit.server.edit.ChangeEditUtil;
      34             : import com.google.gerrit.server.git.GitRepositoryManager;
      35             : import com.google.gerrit.server.git.LargeObjectException;
      36             : import com.google.gerrit.server.notedb.ChangeNotes;
      37             : import com.google.gerrit.server.patch.PatchScriptBuilder.IntraLineDiffCalculatorResult;
      38             : import com.google.gerrit.server.patch.filediff.FileDiffOutput;
      39             : import com.google.gerrit.server.permissions.ChangePermission;
      40             : import com.google.gerrit.server.permissions.PermissionBackend;
      41             : import com.google.gerrit.server.permissions.PermissionBackendException;
      42             : import com.google.gerrit.server.project.InvalidChangeOperationException;
      43             : import com.google.gerrit.server.project.NoSuchChangeException;
      44             : import com.google.gerrit.server.project.ProjectCache;
      45             : import com.google.gerrit.server.project.ProjectState;
      46             : import com.google.inject.Provider;
      47             : import com.google.inject.assistedinject.Assisted;
      48             : import com.google.inject.assistedinject.AssistedInject;
      49             : import java.io.IOException;
      50             : import java.util.Optional;
      51             : import java.util.Set;
      52             : import java.util.concurrent.Callable;
      53             : import org.eclipse.jgit.diff.Edit;
      54             : import org.eclipse.jgit.errors.RepositoryNotFoundException;
      55             : import org.eclipse.jgit.lib.ObjectId;
      56             : import org.eclipse.jgit.lib.Repository;
      57             : 
      58             : public class PatchScriptFactory implements Callable<PatchScript> {
      59             : 
      60          12 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      61             : 
      62             :   public interface Factory {
      63             : 
      64             :     PatchScriptFactory create(
      65             :         ChangeNotes notes,
      66             :         String fileName,
      67             :         @Assisted("patchSetA") PatchSet.Id patchSetA,
      68             :         @Assisted("patchSetB") PatchSet.Id patchSetB,
      69             :         DiffPreferencesInfo diffPrefs,
      70             :         CurrentUser currentUser);
      71             : 
      72             :     PatchScriptFactory create(
      73             :         ChangeNotes notes,
      74             :         String fileName,
      75             :         int parentNum,
      76             :         PatchSet.Id patchSetB,
      77             :         DiffPreferencesInfo diffPrefs,
      78             :         CurrentUser currentUser);
      79             :   }
      80             : 
      81             :   private final GitRepositoryManager repoManager;
      82             :   private final PatchSetUtil psUtil;
      83             :   private final Provider<PatchScriptBuilder> builderFactory;
      84             :   private final PatchListCache patchListCache;
      85             : 
      86             :   private final String fileName;
      87             :   @Nullable private final PatchSet.Id psa;
      88             :   private final int parentNum;
      89             :   private final PatchSet.Id psb;
      90             :   private final DiffPreferencesInfo diffPrefs;
      91             :   private final CurrentUser currentUser;
      92             : 
      93             :   private final ChangeEditUtil editReader;
      94             :   private final PermissionBackend permissionBackend;
      95             :   private final ProjectCache projectCache;
      96             :   private final DiffOperations diffOperations;
      97             : 
      98             :   private final Change.Id changeId;
      99             : 
     100             :   private ChangeNotes notes;
     101             : 
     102             :   @AssistedInject
     103             :   PatchScriptFactory(
     104             :       GitRepositoryManager grm,
     105             :       PatchSetUtil psUtil,
     106             :       Provider<PatchScriptBuilder> builderFactory,
     107             :       PatchListCache patchListCache,
     108             :       ChangeEditUtil editReader,
     109             :       PermissionBackend permissionBackend,
     110             :       ProjectCache projectCache,
     111             :       DiffOperations diffOperations,
     112             :       @Assisted ChangeNotes notes,
     113             :       @Assisted String fileName,
     114             :       @Assisted("patchSetA") @Nullable PatchSet.Id patchSetA,
     115             :       @Assisted("patchSetB") PatchSet.Id patchSetB,
     116             :       @Assisted DiffPreferencesInfo diffPrefs,
     117          12 :       @Assisted CurrentUser currentUser) {
     118          12 :     this.repoManager = grm;
     119          12 :     this.psUtil = psUtil;
     120          12 :     this.builderFactory = builderFactory;
     121          12 :     this.patchListCache = patchListCache;
     122          12 :     this.notes = notes;
     123          12 :     this.editReader = editReader;
     124          12 :     this.permissionBackend = permissionBackend;
     125          12 :     this.projectCache = projectCache;
     126          12 :     this.diffOperations = diffOperations;
     127             : 
     128          12 :     this.fileName = fileName;
     129          12 :     this.psa = patchSetA;
     130          12 :     this.parentNum = 0;
     131          12 :     this.psb = patchSetB;
     132          12 :     this.diffPrefs = diffPrefs;
     133          12 :     this.currentUser = currentUser;
     134          12 :     changeId = patchSetB.changeId();
     135          12 :   }
     136             : 
     137             :   @AssistedInject
     138             :   PatchScriptFactory(
     139             :       GitRepositoryManager grm,
     140             :       PatchSetUtil psUtil,
     141             :       Provider<PatchScriptBuilder> builderFactory,
     142             :       PatchListCache patchListCache,
     143             :       ChangeEditUtil editReader,
     144             :       PermissionBackend permissionBackend,
     145             :       ProjectCache projectCache,
     146             :       DiffOperations diffOperations,
     147             :       @Assisted ChangeNotes notes,
     148             :       @Assisted String fileName,
     149             :       @Assisted int parentNum,
     150             :       @Assisted PatchSet.Id patchSetB,
     151             :       @Assisted DiffPreferencesInfo diffPrefs,
     152           3 :       @Assisted CurrentUser currentUser) {
     153           3 :     this.repoManager = grm;
     154           3 :     this.psUtil = psUtil;
     155           3 :     this.builderFactory = builderFactory;
     156           3 :     this.patchListCache = patchListCache;
     157           3 :     this.notes = notes;
     158           3 :     this.editReader = editReader;
     159           3 :     this.permissionBackend = permissionBackend;
     160           3 :     this.projectCache = projectCache;
     161           3 :     this.diffOperations = diffOperations;
     162             : 
     163           3 :     this.fileName = fileName;
     164           3 :     this.psa = null;
     165           3 :     this.parentNum = parentNum;
     166           3 :     this.psb = patchSetB;
     167           3 :     this.diffPrefs = diffPrefs;
     168           3 :     this.currentUser = currentUser;
     169           3 :     changeId = patchSetB.changeId();
     170           3 :     checkArgument(parentNum > 0, "parentNum must be > 0");
     171           3 :   }
     172             : 
     173             :   @Override
     174             :   public PatchScript call()
     175             :       throws LargeObjectException, AuthException, InvalidChangeOperationException, IOException,
     176             :           PermissionBackendException {
     177             : 
     178          12 :     if (!permissionBackend.user(currentUser).change(notes).test(ChangePermission.READ)) {
     179           0 :       throw new NoSuchChangeException(changeId);
     180             :     }
     181             : 
     182          12 :     if (!projectCache
     183          12 :         .get(notes.getProjectName())
     184          12 :         .map(ProjectState::statePermitsRead)
     185          12 :         .orElse(false)) {
     186           0 :       throw new NoSuchChangeException(changeId);
     187             :     }
     188             : 
     189          12 :     try (Repository git = repoManager.openRepository(notes.getProjectName())) {
     190             :       try {
     191          12 :         validatePatchSetId(psa);
     192          12 :         validatePatchSetId(psb);
     193             : 
     194          12 :         ObjectId aId = getAId().orElse(null);
     195          12 :         ObjectId bId = getBId().orElse(null);
     196          12 :         if (bId == null) {
     197             :           // Change edit: create synthetic PatchSet corresponding to the edit.
     198           1 :           Optional<ChangeEdit> edit = editReader.byChange(notes);
     199           1 :           if (!edit.isPresent()) {
     200           0 :             throw new NoSuchChangeException(notes.getChangeId());
     201             :           }
     202           1 :           bId = edit.get().getEditCommit();
     203             :         }
     204          12 :         return getPatchScript(git, aId, bId);
     205           0 :       } catch (DiffNotAvailableException e) {
     206           0 :         throw new StorageException(e);
     207           0 :       } catch (IOException e) {
     208           0 :         logger.atSevere().withCause(e).log("File content unavailable");
     209           0 :         throw new NoSuchChangeException(changeId, e);
     210           0 :       } catch (org.eclipse.jgit.errors.LargeObjectException err) {
     211           0 :         throw new LargeObjectException("File content is too large", err);
     212             :       }
     213           0 :     } catch (RepositoryNotFoundException e) {
     214           0 :       logger.atSevere().withCause(e).log("Repository %s not found", notes.getProjectName());
     215           0 :       throw new NoSuchChangeException(changeId, e);
     216           0 :     } catch (IOException e) {
     217           0 :       logger.atSevere().withCause(e).log("Cannot open repository %s", notes.getProjectName());
     218           0 :       throw new NoSuchChangeException(changeId, e);
     219             :     }
     220             :   }
     221             : 
     222             :   private PatchScript getPatchScript(Repository git, ObjectId aId, ObjectId bId)
     223             :       throws IOException, DiffNotAvailableException {
     224             :     FileDiffOutput fileDiffOutput =
     225          12 :         aId == null
     226           7 :             ? diffOperations.getModifiedFileAgainstParent(
     227           7 :                 notes.getProjectName(), bId, parentNum, fileName, diffPrefs.ignoreWhitespace)
     228          12 :             : diffOperations.getModifiedFile(
     229           7 :                 notes.getProjectName(), aId, bId, fileName, diffPrefs.ignoreWhitespace);
     230          12 :     return newBuilder().toPatchScript(git, fileDiffOutput);
     231             :   }
     232             : 
     233             :   private Optional<ObjectId> getAId() {
     234          12 :     if (psa == null) {
     235           7 :       return Optional.empty();
     236             :     }
     237           7 :     checkState(parentNum == 0, "expected no parentNum when psa is present");
     238           7 :     checkArgument(psa.get() != 0, "edit not supported for left side");
     239           7 :     return Optional.of(getCommitId(psa));
     240             :   }
     241             : 
     242             :   private Optional<ObjectId> getBId() {
     243          12 :     if (psb.get() == 0) {
     244             :       // Change edit
     245           1 :       return Optional.empty();
     246             :     }
     247          11 :     return Optional.of(getCommitId(psb));
     248             :   }
     249             : 
     250             :   private PatchScriptBuilder newBuilder() {
     251          12 :     final PatchScriptBuilder b = builderFactory.get();
     252          12 :     b.setDiffPrefs(diffPrefs);
     253          12 :     if (diffPrefs.intralineDifference) {
     254           6 :       b.setIntraLineDiffCalculator(
     255           6 :           new IntraLineDiffCalculator(patchListCache, notes.getProjectName(), diffPrefs));
     256             :     }
     257          12 :     return b;
     258             :   }
     259             : 
     260             :   private ObjectId getCommitId(PatchSet.Id psId) {
     261          11 :     PatchSet ps = psUtil.get(notes, psId);
     262          11 :     if (ps == null) {
     263           0 :       throw new NoSuchChangeException(psId.changeId());
     264             :     }
     265          11 :     return ps.commitId();
     266             :   }
     267             : 
     268             :   private void validatePatchSetId(PatchSet.Id psId) throws NoSuchChangeException {
     269          12 :     if (psId == null) { // OK, means use base;
     270          12 :     } else if (changeId.equals(psId.changeId())) { // OK, same change;
     271             :     } else {
     272           0 :       throw new NoSuchChangeException(changeId);
     273             :     }
     274          12 :   }
     275             : 
     276             :   private static class IntraLineDiffCalculator
     277             :       implements PatchScriptBuilder.IntraLineDiffCalculator {
     278             : 
     279             :     private final PatchListCache patchListCache;
     280             :     private final Project.NameKey projectKey;
     281             :     private final DiffPreferencesInfo diffPrefs;
     282             : 
     283             :     IntraLineDiffCalculator(
     284           6 :         PatchListCache patchListCache, Project.NameKey projectKey, DiffPreferencesInfo diffPrefs) {
     285           6 :       this.patchListCache = patchListCache;
     286           6 :       this.projectKey = projectKey;
     287           6 :       this.diffPrefs = diffPrefs;
     288           6 :     }
     289             : 
     290             :     @Override
     291             :     public IntraLineDiffCalculatorResult calculateIntraLineDiff(
     292             :         ImmutableList<Edit> edits,
     293             :         Set<Edit> editsDueToRebase,
     294             :         ObjectId aId,
     295             :         ObjectId bId,
     296             :         Text aSrc,
     297             :         Text bSrc,
     298             :         ObjectId bTreeId,
     299             :         String bPath) {
     300           5 :       IntraLineDiff d =
     301           5 :           patchListCache.getIntraLineDiff(
     302           5 :               IntraLineDiffKey.create(aId, bId, diffPrefs.ignoreWhitespace),
     303           5 :               IntraLineDiffArgs.create(
     304             :                   aSrc, bSrc, edits, editsDueToRebase, projectKey, bTreeId, bPath));
     305           5 :       if (d == null) {
     306           0 :         return IntraLineDiffCalculatorResult.FAILURE;
     307             :       }
     308           5 :       switch (d.getStatus()) {
     309             :         case EDIT_LIST:
     310           5 :           return IntraLineDiffCalculatorResult.success(d.getEdits());
     311             : 
     312             :         case ERROR:
     313           0 :           return IntraLineDiffCalculatorResult.FAILURE;
     314             : 
     315             :         case TIMEOUT:
     316           0 :           return IntraLineDiffCalculatorResult.TIMEOUT;
     317             : 
     318             :         case DISABLED:
     319             :         default:
     320           0 :           return IntraLineDiffCalculatorResult.NO_RESULT;
     321             :       }
     322             :     }
     323             :   }
     324             : }

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