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.gerrit.server.ioutil.BasicSerialization.readBytes; 18 : import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32; 19 : import static com.google.gerrit.server.ioutil.BasicSerialization.writeBytes; 20 : import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32; 21 : import static org.eclipse.jgit.lib.ObjectIdSerializer.read; 22 : import static org.eclipse.jgit.lib.ObjectIdSerializer.readWithoutMarker; 23 : import static org.eclipse.jgit.lib.ObjectIdSerializer.write; 24 : import static org.eclipse.jgit.lib.ObjectIdSerializer.writeWithoutMarker; 25 : 26 : import com.google.common.annotations.VisibleForTesting; 27 : import com.google.common.collect.ImmutableList; 28 : import com.google.gerrit.common.Nullable; 29 : import com.google.gerrit.entities.Patch; 30 : import com.google.gerrit.entities.Patch.ChangeType; 31 : import com.google.gerrit.git.ObjectIds; 32 : import java.io.ByteArrayInputStream; 33 : import java.io.ByteArrayOutputStream; 34 : import java.io.IOException; 35 : import java.io.ObjectInputStream; 36 : import java.io.ObjectOutputStream; 37 : import java.io.Serializable; 38 : import java.util.Arrays; 39 : import java.util.Collections; 40 : import java.util.Comparator; 41 : import java.util.List; 42 : import java.util.zip.DeflaterOutputStream; 43 : import java.util.zip.InflaterInputStream; 44 : import org.eclipse.jgit.lib.AnyObjectId; 45 : import org.eclipse.jgit.lib.ObjectId; 46 : 47 : public class PatchList implements Serializable { 48 : private static final long serialVersionUID = PatchListKey.serialVersionUID; 49 : 50 : @VisibleForTesting 51 0 : static final Comparator<String> FILE_PATH_CMP = 52 0 : Comparator.comparing(Patch::isMagic).reversed().thenComparing(Comparator.naturalOrder()); 53 : 54 : /** 55 : * We use the ChangeType comparator for a rare case when PatchList contains two entries for the 56 : * same file, e.g. {ADDED, DELETED}. We return a single entry according to the following order. 57 : * Check the following bug for an example case: 58 : * https://bugs.chromium.org/p/gerrit/issues/detail?id=13914. 59 : */ 60 : @VisibleForTesting 61 0 : static class ChangeTypeCmp implements Comparator<ChangeType> { 62 0 : static final List<ChangeType> order = 63 0 : ImmutableList.of( 64 : ChangeType.ADDED, 65 : ChangeType.RENAMED, 66 : ChangeType.MODIFIED, 67 : ChangeType.COPIED, 68 : ChangeType.REWRITE, 69 : ChangeType.DELETED); 70 : 71 : @Override 72 : public int compare(ChangeType o1, ChangeType o2) { 73 0 : int idx1 = priority(o1); 74 0 : int idx2 = priority(o2); 75 0 : return idx1 - idx2; 76 : } 77 : 78 : private int priority(ChangeType changeType) { 79 0 : int idx = order.indexOf(changeType); 80 : // Return least priority if the element is not in the order list. 81 0 : return idx == -1 ? order.size() : idx; 82 : } 83 : } 84 : 85 0 : @VisibleForTesting static final Comparator<ChangeType> CHANGE_TYPE_CMP = new ChangeTypeCmp(); 86 : 87 0 : private static final Comparator<PatchListEntry> PATCH_CMP = 88 0 : Comparator.comparing(PatchListEntry::getNewName, FILE_PATH_CMP) 89 0 : .thenComparing(PatchListEntry::getChangeType, CHANGE_TYPE_CMP); 90 : 91 : @Nullable private transient ObjectId oldId; 92 : private transient ObjectId newId; 93 : private transient boolean isMerge; 94 : private transient ComparisonType comparisonType; 95 : private transient int insertions; 96 : private transient int deletions; 97 : private transient PatchListEntry[] patches; 98 : 99 : public PatchList( 100 : @Nullable AnyObjectId oldId, 101 : AnyObjectId newId, 102 : boolean isMerge, 103 : ComparisonType comparisonType, 104 0 : PatchListEntry[] patches) { 105 0 : this.oldId = ObjectIds.copyOrNull(oldId); 106 0 : this.newId = newId.copy(); 107 0 : this.isMerge = isMerge; 108 0 : this.comparisonType = comparisonType; 109 : 110 0 : Arrays.sort(patches, 0, patches.length, PATCH_CMP); 111 : 112 : // Skip magic files 113 0 : int i = 0; 114 0 : for (; i < patches.length; i++) { 115 0 : if (!Patch.isMagic(patches[i].getNewName())) { 116 0 : break; 117 : } 118 : } 119 0 : for (; i < patches.length; i++) { 120 0 : insertions += patches[i].getInsertions(); 121 0 : deletions += patches[i].getDeletions(); 122 : } 123 : 124 0 : this.patches = patches; 125 0 : } 126 : 127 : /** Old side tree or commit; null only if this is a combined diff. */ 128 : @Nullable 129 : public ObjectId getOldId() { 130 0 : return oldId; 131 : } 132 : 133 : /** New side commit. */ 134 : public ObjectId getNewId() { 135 0 : return newId; 136 : } 137 : 138 : /** Get a sorted, unmodifiable list of all files in this list. */ 139 : public List<PatchListEntry> getPatches() { 140 0 : return Collections.unmodifiableList(Arrays.asList(patches)); 141 : } 142 : 143 : /** Returns the comparison type */ 144 : public ComparisonType getComparisonType() { 145 0 : return comparisonType; 146 : } 147 : 148 : /** Returns total number of new lines added. */ 149 : public int getInsertions() { 150 0 : return insertions; 151 : } 152 : 153 : /** Returns total number of lines removed. */ 154 : public int getDeletions() { 155 0 : return deletions; 156 : } 157 : 158 : /** Find an entry by name, returning an empty entry if not present. */ 159 : public PatchListEntry get(String fileName) { 160 0 : int index = search(fileName); 161 0 : if (index >= 0) { 162 0 : return patches[index]; 163 : } 164 : // If index is negative, it marks the insertion point of the object in the list. 165 : // index = (-(insertion point) - 1). 166 : // Since we use the ChangeType in the comparison, the object that we are using in the lookup 167 : // (which has a ADDED ChangeType) may have a different ChangeType than the object in the list. 168 : // For this reason, we look at the file name of the object at the insertion point and return it 169 : // if it has the same name. 170 0 : index = -1 * (index + 1); 171 0 : if (index < patches.length && patches[index].getNewName().equals(fileName)) { 172 0 : return patches[index]; 173 : } 174 0 : return PatchListEntry.empty(fileName); 175 : } 176 : 177 : private int search(String fileName) { 178 0 : PatchListEntry want = PatchListEntry.empty(fileName, ChangeType.ADDED); 179 0 : return Arrays.binarySearch(patches, 0, patches.length, want, PATCH_CMP); 180 : } 181 : 182 : private void writeObject(ObjectOutputStream output) throws IOException { 183 0 : final ByteArrayOutputStream buf = new ByteArrayOutputStream(); 184 0 : try (DeflaterOutputStream out = new DeflaterOutputStream(buf)) { 185 0 : write(out, oldId); 186 0 : writeWithoutMarker(out, newId); 187 0 : writeVarInt32(out, isMerge ? 1 : 0); 188 0 : comparisonType.writeTo(out); 189 0 : writeVarInt32(out, insertions); 190 0 : writeVarInt32(out, deletions); 191 0 : writeVarInt32(out, patches.length); 192 0 : for (PatchListEntry p : patches) { 193 0 : p.writeTo(out); 194 : } 195 : } 196 0 : writeBytes(output, buf.toByteArray()); 197 0 : } 198 : 199 : private void readObject(ObjectInputStream input) throws IOException { 200 0 : final ByteArrayInputStream buf = new ByteArrayInputStream(readBytes(input)); 201 0 : try (InflaterInputStream in = new InflaterInputStream(buf)) { 202 0 : oldId = read(in); 203 0 : newId = readWithoutMarker(in); 204 0 : isMerge = readVarInt32(in) != 0; 205 0 : comparisonType = ComparisonType.readFrom(in); 206 0 : insertions = readVarInt32(in); 207 0 : deletions = readVarInt32(in); 208 0 : final int cnt = readVarInt32(in); 209 0 : final PatchListEntry[] all = new PatchListEntry[cnt]; 210 0 : for (int i = 0; i < all.length; i++) { 211 0 : all[i] = PatchListEntry.readFrom(in); 212 : } 213 0 : patches = all; 214 : } 215 0 : } 216 : }