LCOV - code coverage report
Current view: top level - server/diff - DiffInfoCreator.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 149 162 92.0 %
Date: 2022-11-19 15:00:39 Functions: 9 9 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2019 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.diff;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkState;
      18             : 
      19             : import com.google.common.collect.ImmutableList;
      20             : import com.google.common.collect.ImmutableMap;
      21             : import com.google.common.collect.Lists;
      22             : import com.google.common.collect.Maps;
      23             : import com.google.common.flogger.FluentLogger;
      24             : import com.google.gerrit.common.data.PatchScript;
      25             : import com.google.gerrit.common.data.PatchScript.DisplayMethod;
      26             : import com.google.gerrit.common.data.PatchScript.PatchScriptFileInfo;
      27             : import com.google.gerrit.entities.Patch;
      28             : import com.google.gerrit.extensions.common.ChangeType;
      29             : import com.google.gerrit.extensions.common.DiffInfo;
      30             : import com.google.gerrit.extensions.common.DiffInfo.ContentEntry;
      31             : import com.google.gerrit.extensions.common.DiffInfo.FileMeta;
      32             : import com.google.gerrit.extensions.common.DiffInfo.IntraLineStatus;
      33             : import com.google.gerrit.extensions.common.DiffWebLinkInfo;
      34             : import com.google.gerrit.extensions.common.WebLinkInfo;
      35             : import com.google.gerrit.jgit.diff.ReplaceEdit;
      36             : import com.google.gerrit.prettify.common.SparseFileContent;
      37             : import com.google.gerrit.server.change.FileContentUtil;
      38             : import com.google.gerrit.server.project.ProjectState;
      39             : import java.util.List;
      40             : import java.util.Optional;
      41             : import java.util.Set;
      42             : import org.eclipse.jgit.diff.Edit;
      43             : 
      44             : /** Creates and fills a new {@link DiffInfo} object based on diff between files. */
      45             : public class DiffInfoCreator {
      46          10 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      47             : 
      48          10 :   private static final ImmutableMap<Patch.ChangeType, ChangeType> CHANGE_TYPE =
      49          10 :       Maps.immutableEnumMap(
      50             :           new ImmutableMap.Builder<Patch.ChangeType, ChangeType>()
      51          10 :               .put(Patch.ChangeType.ADDED, ChangeType.ADDED)
      52          10 :               .put(Patch.ChangeType.MODIFIED, ChangeType.MODIFIED)
      53          10 :               .put(Patch.ChangeType.DELETED, ChangeType.DELETED)
      54          10 :               .put(Patch.ChangeType.RENAMED, ChangeType.RENAMED)
      55          10 :               .put(Patch.ChangeType.COPIED, ChangeType.COPIED)
      56          10 :               .put(Patch.ChangeType.REWRITE, ChangeType.REWRITE)
      57          10 :               .build());
      58             : 
      59             :   private final DiffWebLinksProvider webLinksProvider;
      60             :   private final boolean intraline;
      61             :   private final ProjectState state;
      62             : 
      63             :   public DiffInfoCreator(
      64          10 :       ProjectState state, DiffWebLinksProvider webLinksProvider, boolean intraline) {
      65          10 :     this.webLinksProvider = webLinksProvider;
      66          10 :     this.state = state;
      67          10 :     this.intraline = intraline;
      68          10 :   }
      69             : 
      70             :   /* Returns the {@link DiffInfo} to display for end-users */
      71             :   public DiffInfo create(PatchScript ps, DiffSide sideA, DiffSide sideB) {
      72          10 :     DiffInfo result = new DiffInfo();
      73             : 
      74          10 :     ImmutableList<DiffWebLinkInfo> links = webLinksProvider.getDiffLinks();
      75          10 :     result.webLinks = links.isEmpty() ? null : links;
      76          10 :     ImmutableList<WebLinkInfo> editLinks = webLinksProvider.getEditWebLinks();
      77          10 :     result.editWebLinks = editLinks.isEmpty() ? null : editLinks;
      78             : 
      79          10 :     if (ps.isBinary()) {
      80           0 :       result.binary = true;
      81             :     }
      82          10 :     result.metaA = createFileMeta(sideA).orElse(null);
      83          10 :     result.metaB = createFileMeta(sideB).orElse(null);
      84             : 
      85          10 :     if (intraline) {
      86           4 :       if (ps.hasIntralineTimeout()) {
      87           0 :         result.intralineStatus = IntraLineStatus.TIMEOUT;
      88           4 :       } else if (ps.hasIntralineFailure()) {
      89           0 :         result.intralineStatus = IntraLineStatus.FAILURE;
      90             :       } else {
      91           4 :         result.intralineStatus = IntraLineStatus.OK;
      92             :       }
      93           4 :       logger.atFine().log("intralineStatus = %s", result.intralineStatus);
      94             :     }
      95             : 
      96          10 :     result.changeType = CHANGE_TYPE.get(ps.getChangeType());
      97          10 :     logger.atFine().log("changeType = %s", result.changeType);
      98          10 :     if (result.changeType == null) {
      99           0 :       throw new IllegalStateException("unknown change type: " + ps.getChangeType());
     100             :     }
     101             : 
     102          10 :     if (ps.getPatchHeader().size() > 0) {
     103           8 :       result.diffHeader = ps.getPatchHeader();
     104             :     }
     105          10 :     result.content = calculateDiffContentEntries(ps);
     106          10 :     return result;
     107             :   }
     108             : 
     109             :   private static List<ContentEntry> calculateDiffContentEntries(PatchScript ps) {
     110          10 :     ContentCollector contentCollector = new ContentCollector(ps);
     111          10 :     Set<Edit> editsDueToRebase = ps.getEditsDueToRebase();
     112          10 :     for (Edit edit : ps.getEdits()) {
     113          10 :       logger.atFine().log("next edit = %s", edit);
     114             : 
     115          10 :       if (edit.getType() == Edit.Type.EMPTY) {
     116           3 :         logger.atFine().log("skip empty edit");
     117           3 :         continue;
     118             :       }
     119          10 :       contentCollector.addCommon(edit.getBeginA());
     120             : 
     121          10 :       checkState(
     122          10 :           contentCollector.nextA == edit.getBeginA(),
     123             :           "nextA = %s; want %s",
     124             :           contentCollector.nextA,
     125          10 :           edit.getBeginA());
     126          10 :       checkState(
     127          10 :           contentCollector.nextB == edit.getBeginB(),
     128             :           "nextB = %s; want %s",
     129             :           contentCollector.nextB,
     130          10 :           edit.getBeginB());
     131          10 :       switch (edit.getType()) {
     132             :         case DELETE:
     133             :         case INSERT:
     134             :         case REPLACE:
     135             :           List<Edit> internalEdit =
     136          10 :               edit instanceof ReplaceEdit ? ((ReplaceEdit) edit).getInternalEdits() : null;
     137          10 :           boolean dueToRebase = editsDueToRebase.contains(edit);
     138          10 :           contentCollector.addDiff(edit.getEndA(), edit.getEndB(), internalEdit, dueToRebase);
     139          10 :           break;
     140             :         case EMPTY:
     141             :         default:
     142           0 :           throw new IllegalStateException();
     143             :       }
     144          10 :     }
     145          10 :     contentCollector.addCommon(ps.getA().getSize());
     146             : 
     147          10 :     return contentCollector.lines;
     148             :   }
     149             : 
     150             :   private Optional<FileMeta> createFileMeta(DiffSide side) {
     151          10 :     PatchScriptFileInfo fileInfo = side.fileInfo();
     152          10 :     if (fileInfo.displayMethod == DisplayMethod.NONE) {
     153           7 :       return Optional.empty();
     154             :     }
     155          10 :     FileMeta result = new FileMeta();
     156          10 :     result.name = side.fileName();
     157          10 :     result.contentType =
     158          10 :         FileContentUtil.resolveContentType(
     159          10 :             state, side.fileName(), fileInfo.mode, fileInfo.mimeType);
     160          10 :     result.lines = fileInfo.content.getSize();
     161          10 :     ImmutableList<WebLinkInfo> fileLinks = webLinksProvider.getFileWebLinks(side.type());
     162          10 :     result.webLinks = fileLinks.isEmpty() ? null : fileLinks;
     163          10 :     result.commitId = fileInfo.commitId;
     164          10 :     return Optional.of(result);
     165             :   }
     166             : 
     167             :   private static class ContentCollector {
     168             : 
     169             :     private final List<ContentEntry> lines;
     170             :     private final SparseFileContent.Accessor fileA;
     171             :     private final SparseFileContent.Accessor fileB;
     172             :     private final boolean ignoreWS;
     173             : 
     174             :     private int nextA;
     175             :     private int nextB;
     176             : 
     177          10 :     ContentCollector(PatchScript ps) {
     178          10 :       lines = Lists.newArrayListWithExpectedSize(ps.getEdits().size() + 2);
     179          10 :       fileA = ps.getA().createAccessor();
     180          10 :       fileB = ps.getB().createAccessor();
     181          10 :       ignoreWS = ps.isIgnoreWhitespace();
     182          10 :     }
     183             : 
     184             :     void addCommon(int end) {
     185          10 :       logger.atFine().log("addCommon: end = %d", end);
     186             : 
     187          10 :       end = Math.min(end, fileA.getSize());
     188          10 :       logger.atFine().log("end = %d", end);
     189             : 
     190          10 :       if (nextA >= end) {
     191           9 :         logger.atFine().log("nextA >= end: nextA = %d, end = %d", nextA, end);
     192           9 :         return;
     193             :       }
     194             : 
     195           5 :       while (nextA < end) {
     196           5 :         logger.atFine().log("nextA < end: nextA = %d, end = %d", nextA, end);
     197             : 
     198           5 :         if (!fileA.contains(nextA)) {
     199           2 :           logger.atFine().log("fileA does not contain nextA: nextA = %d", nextA);
     200             : 
     201           2 :           int endRegion = Math.min(end, nextA == 0 ? fileA.first() : fileA.next(nextA - 1));
     202           2 :           int len = endRegion - nextA;
     203           2 :           entry().skip = len;
     204           2 :           nextA = endRegion;
     205           2 :           nextB += len;
     206             : 
     207           2 :           logger.atFine().log("setting: nextA = %d, nextB = %d", nextA, nextB);
     208           2 :           continue;
     209             :         }
     210             : 
     211           5 :         ContentEntry e = null;
     212           5 :         for (int i = nextA; i == nextA && i < end; i = fileA.next(i), nextA++, nextB++) {
     213           5 :           if (ignoreWS && fileB.contains(nextB)) {
     214           0 :             if (e == null || e.common == null) {
     215           0 :               logger.atFine().log("create new common entry: nextA = %d, nextB = %d", nextA, nextB);
     216           0 :               e = entry();
     217           0 :               e.a = Lists.newArrayListWithCapacity(end - nextA);
     218           0 :               e.b = Lists.newArrayListWithCapacity(end - nextA);
     219           0 :               e.common = true;
     220             :             }
     221           0 :             e.a.add(fileA.get(nextA));
     222           0 :             e.b.add(fileB.get(nextB));
     223             :           } else {
     224           5 :             if (e == null || e.common != null) {
     225           5 :               logger.atFine().log(
     226             :                   "create new non-common entry: nextA = %d, nextB = %d", nextA, nextB);
     227           5 :               e = entry();
     228           5 :               e.ab = Lists.newArrayListWithCapacity(end - nextA);
     229             :             }
     230           5 :             e.ab.add(fileA.get(nextA));
     231             :           }
     232             :         }
     233           5 :       }
     234           5 :     }
     235             : 
     236             :     void addDiff(int endA, int endB, List<Edit> internalEdit, boolean dueToRebase) {
     237          10 :       logger.atFine().log(
     238             :           "addDiff: endA = %d, endB = %d, numberOfInternalEdits = %d, dueToRebase = %s",
     239          10 :           endA, endB, internalEdit != null ? internalEdit.size() : 0, dueToRebase);
     240             : 
     241          10 :       int lenA = endA - nextA;
     242          10 :       int lenB = endB - nextB;
     243          10 :       logger.atFine().log("lenA = %d, lenB = %d", lenA, lenB);
     244          10 :       checkState(lenA > 0 || lenB > 0);
     245             : 
     246          10 :       logger.atFine().log("create non-common entry");
     247          10 :       ContentEntry e = entry();
     248          10 :       if (lenA > 0) {
     249           6 :         logger.atFine().log("lenA > 0: lenA = %d", lenA);
     250           6 :         e.a = Lists.newArrayListWithCapacity(lenA);
     251           6 :         for (; nextA < endA; nextA++) {
     252           6 :           e.a.add(fileA.get(nextA));
     253             :         }
     254             :       }
     255          10 :       if (lenB > 0) {
     256          10 :         logger.atFine().log("lenB > 0: lenB = %d", lenB);
     257          10 :         e.b = Lists.newArrayListWithCapacity(lenB);
     258          10 :         for (; nextB < endB; nextB++) {
     259          10 :           e.b.add(fileB.get(nextB));
     260             :         }
     261             :       }
     262          10 :       if (internalEdit != null && !internalEdit.isEmpty()) {
     263           3 :         logger.atFine().log("processing internal edits");
     264             : 
     265           3 :         e.editA = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
     266           3 :         e.editB = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
     267           3 :         int lastA = 0;
     268           3 :         int lastB = 0;
     269           3 :         for (Edit edit : internalEdit) {
     270           3 :           logger.atFine().log("internal edit = %s", edit);
     271             : 
     272           3 :           if (edit.getBeginA() != edit.getEndA()) {
     273           3 :             logger.atFine().log(
     274             :                 "edit.getBeginA() != edit.getEndA(): edit.getBeginA() = %d, edit.getEndA() = %d",
     275           3 :                 edit.getBeginA(), edit.getEndA());
     276           3 :             e.editA.add(
     277           3 :                 ImmutableList.of(edit.getBeginA() - lastA, edit.getEndA() - edit.getBeginA()));
     278           3 :             lastA = edit.getEndA();
     279           3 :             logger.atFine().log("lastA = %d", lastA);
     280             :           }
     281           3 :           if (edit.getBeginB() != edit.getEndB()) {
     282           3 :             logger.atFine().log(
     283             :                 "edit.getBeginB() != edit.getEndB(): edit.getBeginB() = %d, edit.getEndB() = %d",
     284           3 :                 edit.getBeginB(), edit.getEndB());
     285           3 :             e.editB.add(
     286           3 :                 ImmutableList.of(edit.getBeginB() - lastB, edit.getEndB() - edit.getBeginB()));
     287           3 :             lastB = edit.getEndB();
     288           3 :             logger.atFine().log("lastB = %d", lastB);
     289             :           }
     290           3 :         }
     291             :       }
     292          10 :       e.dueToRebase = dueToRebase ? true : null;
     293          10 :     }
     294             : 
     295             :     private ContentEntry entry() {
     296          10 :       ContentEntry e = new ContentEntry();
     297          10 :       lines.add(e);
     298          10 :       return e;
     299             :     }
     300             :   }
     301             : }

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