LCOV - code coverage report
Current view: top level - server/patch/filediff - FileDiffOutput.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 130 135 96.3 %
Date: 2022-11-19 15:00:39 Functions: 15 16 93.8 %

          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.server.patch.filediff;
      16             : 
      17             : import static com.google.gerrit.server.patch.DiffUtil.stringSize;
      18             : 
      19             : import com.google.auto.value.AutoValue;
      20             : import com.google.common.base.Converter;
      21             : import com.google.common.base.Enums;
      22             : import com.google.common.collect.ImmutableList;
      23             : import com.google.gerrit.entities.Patch;
      24             : import com.google.gerrit.entities.Patch.ChangeType;
      25             : import com.google.gerrit.entities.Patch.FileMode;
      26             : import com.google.gerrit.entities.Patch.PatchType;
      27             : import com.google.gerrit.proto.Protos;
      28             : import com.google.gerrit.server.cache.proto.Cache.FileDiffOutputProto;
      29             : import com.google.gerrit.server.cache.serialize.CacheSerializer;
      30             : import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
      31             : import com.google.gerrit.server.patch.ComparisonType;
      32             : import com.google.protobuf.Descriptors.FieldDescriptor;
      33             : import java.io.Serializable;
      34             : import java.util.Optional;
      35             : import java.util.stream.Collectors;
      36             : import org.eclipse.jgit.lib.ObjectId;
      37             : 
      38             : /** File diff for a single file path. Produced as output of the {@link FileDiffCache}. */
      39             : @AutoValue
      40         105 : public abstract class FileDiffOutput implements Serializable {
      41             :   private static final long serialVersionUID = 1L;
      42             : 
      43             :   /**
      44             :    * The 20 bytes SHA-1 object ID of the old git commit used in the diff, or {@link
      45             :    * ObjectId#zeroId()} if {@link #newCommitId()} was a root commit.
      46             :    */
      47             :   public abstract ObjectId oldCommitId();
      48             : 
      49             :   /** The 20 bytes SHA-1 object ID of the new git commit used in the diff. */
      50             :   public abstract ObjectId newCommitId();
      51             : 
      52             :   /** Comparison type of old and new commits: against another patchset, parent or auto-merge. */
      53             :   public abstract ComparisonType comparisonType();
      54             : 
      55             :   /**
      56             :    * The file path at the old commit. Returns an empty Optional if {@link #changeType()} is equal to
      57             :    * {@link ChangeType#ADDED}.
      58             :    */
      59             :   public abstract Optional<String> oldPath();
      60             : 
      61             :   /**
      62             :    * The file path at the new commit. Returns an empty optional if {@link #changeType()} is equal to
      63             :    * {@link ChangeType#DELETED}.
      64             :    */
      65             :   public abstract Optional<String> newPath();
      66             : 
      67             :   /**
      68             :    * The file mode of the old file at the old git tree diff identified by {@link #oldCommitId()}
      69             :    * ()}.
      70             :    */
      71             :   public abstract Optional<Patch.FileMode> oldMode();
      72             : 
      73             :   /**
      74             :    * The file mode of the new file at the new git tree diff identified by {@link #newCommitId()}
      75             :    * ()}.
      76             :    */
      77             :   public abstract Optional<Patch.FileMode> newMode();
      78             : 
      79             :   /** The change type of the underlying file, e.g. added, deleted, renamed, etc... */
      80             :   public abstract Patch.ChangeType changeType();
      81             : 
      82             :   /** The patch type of the underlying file, e.g. unified, binary , etc... */
      83             :   public abstract Optional<Patch.PatchType> patchType();
      84             : 
      85             :   /**
      86             :    * A list of strings representation of the header lines of the {@link
      87             :    * org.eclipse.jgit.patch.FileHeader} that is produced as output of the diff.
      88             :    */
      89             :   public abstract ImmutableList<String> headerLines();
      90             : 
      91             :   /** The list of edits resulting from the diff hunks of the file. */
      92             :   public abstract ImmutableList<TaggedEdit> edits();
      93             : 
      94             :   /** The file size at the new commit. */
      95             :   public abstract long size();
      96             : 
      97             :   /** Difference in file size between the old and new commits. */
      98             :   public abstract long sizeDelta();
      99             : 
     100             :   /**
     101             :    * Returns {@code true} if the diff computation was not able to compute a diff, i.e. for diffs
     102             :    * taking a very long time to compute. We cache negative result in this case.
     103             :    */
     104             :   public abstract Optional<Boolean> negative();
     105             : 
     106             :   public abstract Builder toBuilder();
     107             : 
     108             :   /** A boolean indicating if all underlying edits of the file diff are due to rebase. */
     109             :   public boolean allEditsDueToRebase() {
     110         104 :     return !edits().isEmpty() && edits().stream().allMatch(TaggedEdit::dueToRebase);
     111             :   }
     112             : 
     113             :   /** Returns the number of inserted lines for the file diff. */
     114             :   public int insertions() {
     115         103 :     int ins = 0;
     116         103 :     for (TaggedEdit e : edits()) {
     117         103 :       if (!e.dueToRebase()) {
     118         103 :         ins += e.edit().endB() - e.edit().beginB();
     119             :       }
     120         103 :     }
     121         103 :     return ins;
     122             :   }
     123             : 
     124             :   /** Returns the number of deleted lines for the file diff. */
     125             :   public int deletions() {
     126         103 :     int del = 0;
     127         103 :     for (TaggedEdit e : edits()) {
     128         103 :       if (!e.dueToRebase()) {
     129         103 :         del += e.edit().endA() - e.edit().beginA();
     130             :       }
     131         103 :     }
     132         103 :     return del;
     133             :   }
     134             : 
     135             :   /** Returns an entity representing an unchanged file between two commits. */
     136             :   public static FileDiffOutput empty(String filePath, ObjectId oldCommitId, ObjectId newCommitId) {
     137          25 :     return builder()
     138          25 :         .oldCommitId(oldCommitId)
     139          25 :         .newCommitId(newCommitId)
     140          25 :         .comparisonType(ComparisonType.againstOtherPatchSet()) // not important
     141          25 :         .oldPath(Optional.empty())
     142          25 :         .newPath(Optional.of(filePath))
     143          25 :         .changeType(ChangeType.MODIFIED)
     144          25 :         .headerLines(ImmutableList.of())
     145          25 :         .edits(ImmutableList.of())
     146          25 :         .size(0)
     147          25 :         .sizeDelta(0)
     148          25 :         .build();
     149             :   }
     150             : 
     151             :   /**
     152             :    * Create a negative file diff. We use this to cache negative diffs for entries that result in
     153             :    * timeouts.
     154             :    */
     155             :   public static FileDiffOutput createNegative(
     156             :       String filePath, ObjectId oldCommitId, ObjectId newCommitId) {
     157           0 :     return empty(filePath, oldCommitId, newCommitId)
     158           0 :         .toBuilder()
     159           0 :         .negative(Optional.of(true))
     160           0 :         .build();
     161             :   }
     162             : 
     163             :   /** Returns true if this entity represents an unchanged file between two commits. */
     164             :   public boolean isEmpty() {
     165         104 :     return headerLines().isEmpty() && edits().isEmpty();
     166             :   }
     167             : 
     168             :   /**
     169             :    * Returns {@code true} if the diff computation was not able to compute a diff. We cache negative
     170             :    * result in this case.
     171             :    */
     172             :   public boolean isNegative() {
     173         104 :     return negative().isPresent() && negative().get();
     174             :   }
     175             : 
     176             :   public static Builder builder() {
     177         105 :     return new AutoValue_FileDiffOutput.Builder();
     178             :   }
     179             : 
     180             :   public int weight() {
     181         104 :     int result = 0;
     182         104 :     if (oldPath().isPresent()) {
     183          54 :       result += stringSize(oldPath().get());
     184             :     }
     185         104 :     if (newPath().isPresent()) {
     186         104 :       result += stringSize(newPath().get());
     187             :     }
     188         104 :     result += 20 + 20; // old and new commit IDs
     189         104 :     result += 4; // comparison type
     190         104 :     result += 4; // changeType
     191         104 :     if (patchType().isPresent()) {
     192         104 :       result += 4;
     193             :     }
     194         104 :     result += 4 + 4; // insertions and deletions
     195         104 :     result += 4 + 4; // size and size delta
     196         104 :     result += 20 * edits().size(); // each edit is 4 Integers + boolean = 4 * 4 + 4 = 20
     197         104 :     for (String s : headerLines()) {
     198         104 :       s += stringSize(s);
     199         104 :     }
     200         104 :     if (negative().isPresent()) {
     201           0 :       result += 1;
     202             :     }
     203         104 :     return result;
     204             :   }
     205             : 
     206             :   @AutoValue.Builder
     207         105 :   public abstract static class Builder {
     208             : 
     209             :     public abstract Builder oldCommitId(ObjectId value);
     210             : 
     211             :     public abstract Builder newCommitId(ObjectId value);
     212             : 
     213             :     public abstract Builder comparisonType(ComparisonType value);
     214             : 
     215             :     public abstract Builder oldPath(Optional<String> value);
     216             : 
     217             :     public abstract Builder newPath(Optional<String> value);
     218             : 
     219             :     public abstract Builder oldMode(Optional<Patch.FileMode> oldMode);
     220             : 
     221             :     public abstract Builder newMode(Optional<Patch.FileMode> newMode);
     222             : 
     223             :     public abstract Builder changeType(ChangeType value);
     224             : 
     225             :     public abstract Builder patchType(Optional<PatchType> value);
     226             : 
     227             :     public abstract Builder headerLines(ImmutableList<String> value);
     228             : 
     229             :     public abstract Builder edits(ImmutableList<TaggedEdit> value);
     230             : 
     231             :     public abstract Builder size(long value);
     232             : 
     233             :     public abstract Builder sizeDelta(long value);
     234             : 
     235             :     public abstract Builder negative(Optional<Boolean> value);
     236             : 
     237             :     public abstract FileDiffOutput build();
     238             :   }
     239             : 
     240         153 :   public enum Serializer implements CacheSerializer<FileDiffOutput> {
     241         153 :     INSTANCE;
     242             : 
     243         153 :     private static final Converter<String, FileMode> FILE_MODE_CONVERTER =
     244         153 :         Enums.stringConverter(Patch.FileMode.class);
     245             : 
     246             :     private static final FieldDescriptor OLD_PATH_DESCRIPTOR =
     247         153 :         FileDiffOutputProto.getDescriptor().findFieldByNumber(1);
     248             : 
     249             :     private static final FieldDescriptor NEW_PATH_DESCRIPTOR =
     250         153 :         FileDiffOutputProto.getDescriptor().findFieldByNumber(2);
     251             : 
     252             :     private static final FieldDescriptor PATCH_TYPE_DESCRIPTOR =
     253         153 :         FileDiffOutputProto.getDescriptor().findFieldByNumber(4);
     254             : 
     255             :     private static final FieldDescriptor NEGATIVE_DESCRIPTOR =
     256         153 :         FileDiffOutputProto.getDescriptor().findFieldByNumber(12);
     257             : 
     258             :     private static final FieldDescriptor OLD_MODE_DESCRIPTOR =
     259         153 :         FileDiffOutputProto.getDescriptor().findFieldByNumber(13);
     260             : 
     261         153 :     private static final FieldDescriptor NEW_MODE_DESCRIPTOR =
     262         153 :         FileDiffOutputProto.getDescriptor().findFieldByNumber(14);
     263             : 
     264             :     @Override
     265             :     public byte[] serialize(FileDiffOutput fileDiff) {
     266           7 :       ObjectIdConverter idConverter = ObjectIdConverter.create();
     267             :       FileDiffOutputProto.Builder builder =
     268           7 :           FileDiffOutputProto.newBuilder()
     269           7 :               .setOldCommit(idConverter.toByteString(fileDiff.oldCommitId().toObjectId()))
     270           7 :               .setNewCommit(idConverter.toByteString(fileDiff.newCommitId().toObjectId()))
     271           7 :               .setComparisonType(fileDiff.comparisonType().toProto())
     272           7 :               .setSize(fileDiff.size())
     273           7 :               .setSizeDelta(fileDiff.sizeDelta())
     274           7 :               .addAllHeaderLines(fileDiff.headerLines())
     275           7 :               .setChangeType(fileDiff.changeType().name())
     276           7 :               .addAllEdits(
     277           7 :                   fileDiff.edits().stream()
     278           7 :                       .map(
     279             :                           e ->
     280           7 :                               FileDiffOutputProto.TaggedEdit.newBuilder()
     281           7 :                                   .setEdit(
     282           7 :                                       FileDiffOutputProto.Edit.newBuilder()
     283           7 :                                           .setBeginA(e.edit().beginA())
     284           7 :                                           .setEndA(e.edit().endA())
     285           7 :                                           .setBeginB(e.edit().beginB())
     286           7 :                                           .setEndB(e.edit().endB())
     287           7 :                                           .build())
     288           7 :                                   .setDueToRebase(e.dueToRebase())
     289           7 :                                   .build())
     290           7 :                       .collect(Collectors.toList()));
     291             : 
     292           7 :       if (fileDiff.oldPath().isPresent()) {
     293           1 :         builder.setOldPath(fileDiff.oldPath().get());
     294             :       }
     295             : 
     296           7 :       if (fileDiff.newPath().isPresent()) {
     297           6 :         builder.setNewPath(fileDiff.newPath().get());
     298             :       }
     299             : 
     300           7 :       if (fileDiff.patchType().isPresent()) {
     301           7 :         builder.setPatchType(fileDiff.patchType().get().name());
     302             :       }
     303             : 
     304           7 :       if (fileDiff.negative().isPresent()) {
     305           1 :         builder.setNegative(fileDiff.negative().get());
     306             :       }
     307             : 
     308           7 :       if (fileDiff.oldMode().isPresent()) {
     309           5 :         builder.setOldMode(FILE_MODE_CONVERTER.reverse().convert(fileDiff.oldMode().get()));
     310             :       }
     311           7 :       if (fileDiff.newMode().isPresent()) {
     312           5 :         builder.setNewMode(FILE_MODE_CONVERTER.reverse().convert(fileDiff.newMode().get()));
     313             :       }
     314             : 
     315           7 :       return Protos.toByteArray(builder.build());
     316             :     }
     317             : 
     318             :     @Override
     319             :     public FileDiffOutput deserialize(byte[] in) {
     320           2 :       ObjectIdConverter idConverter = ObjectIdConverter.create();
     321           2 :       FileDiffOutputProto proto = Protos.parseUnchecked(FileDiffOutputProto.parser(), in);
     322           2 :       FileDiffOutput.Builder builder = FileDiffOutput.builder();
     323           2 :       builder
     324           2 :           .oldCommitId(idConverter.fromByteString(proto.getOldCommit()))
     325           2 :           .newCommitId(idConverter.fromByteString(proto.getNewCommit()))
     326           2 :           .comparisonType(ComparisonType.fromProto(proto.getComparisonType()))
     327           2 :           .size(proto.getSize())
     328           2 :           .sizeDelta(proto.getSizeDelta())
     329           2 :           .headerLines(proto.getHeaderLinesList().stream().collect(ImmutableList.toImmutableList()))
     330           2 :           .changeType(ChangeType.valueOf(proto.getChangeType()))
     331           2 :           .edits(
     332           2 :               proto.getEditsList().stream()
     333           2 :                   .map(
     334             :                       e ->
     335           2 :                           TaggedEdit.create(
     336           2 :                               Edit.create(
     337           2 :                                   e.getEdit().getBeginA(),
     338           2 :                                   e.getEdit().getEndA(),
     339           2 :                                   e.getEdit().getBeginB(),
     340           2 :                                   e.getEdit().getEndB()),
     341           2 :                               e.getDueToRebase()))
     342           2 :                   .collect(ImmutableList.toImmutableList()));
     343             : 
     344           2 :       if (proto.hasField(OLD_PATH_DESCRIPTOR)) {
     345           1 :         builder.oldPath(Optional.of(proto.getOldPath()));
     346             :       }
     347           2 :       if (proto.hasField(NEW_PATH_DESCRIPTOR)) {
     348           1 :         builder.newPath(Optional.of(proto.getNewPath()));
     349             :       }
     350           2 :       if (proto.hasField(PATCH_TYPE_DESCRIPTOR)) {
     351           2 :         builder.patchType(Optional.of(Patch.PatchType.valueOf(proto.getPatchType())));
     352             :       }
     353           2 :       if (proto.hasField(NEGATIVE_DESCRIPTOR)) {
     354           1 :         builder.negative(Optional.of(proto.getNegative()));
     355             :       }
     356           2 :       if (proto.hasField(OLD_MODE_DESCRIPTOR)) {
     357           1 :         builder.oldMode(Optional.of(FILE_MODE_CONVERTER.convert(proto.getOldMode())));
     358             :       }
     359           2 :       if (proto.hasField(NEW_MODE_DESCRIPTOR)) {
     360           1 :         builder.newMode(Optional.of(FILE_MODE_CONVERTER.convert(proto.getNewMode())));
     361             :       }
     362           2 :       return builder.build();
     363             :     }
     364             :   }
     365             : }

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