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.collect.ImmutableList.toImmutableList;
18 : import static com.google.common.collect.ImmutableSet.toImmutableSet;
19 : import static com.google.gerrit.server.ioutil.BasicSerialization.readBytes;
20 : import static com.google.gerrit.server.ioutil.BasicSerialization.readEnum;
21 : import static com.google.gerrit.server.ioutil.BasicSerialization.readFixInt64;
22 : import static com.google.gerrit.server.ioutil.BasicSerialization.readString;
23 : import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
24 : import static com.google.gerrit.server.ioutil.BasicSerialization.writeBytes;
25 : import static com.google.gerrit.server.ioutil.BasicSerialization.writeEnum;
26 : import static com.google.gerrit.server.ioutil.BasicSerialization.writeFixInt64;
27 : import static com.google.gerrit.server.ioutil.BasicSerialization.writeString;
28 : import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
29 : import static java.nio.charset.StandardCharsets.UTF_8;
30 :
31 : import com.google.common.collect.ImmutableList;
32 : import com.google.common.collect.ImmutableSet;
33 : import com.google.gerrit.entities.Patch;
34 : import com.google.gerrit.entities.Patch.ChangeType;
35 : import com.google.gerrit.entities.Patch.PatchType;
36 : import java.io.IOException;
37 : import java.io.InputStream;
38 : import java.io.OutputStream;
39 : import java.util.Collection;
40 : import java.util.List;
41 : import java.util.Set;
42 : import org.eclipse.jgit.diff.Edit;
43 : import org.eclipse.jgit.patch.CombinedFileHeader;
44 : import org.eclipse.jgit.patch.FileHeader;
45 : import org.eclipse.jgit.util.IntList;
46 : import org.eclipse.jgit.util.RawParseUtils;
47 :
48 : public class PatchListEntry {
49 1 : private static final byte[] EMPTY_HEADER = {};
50 :
51 : static PatchListEntry empty(String fileName) {
52 1 : return empty(fileName, ChangeType.MODIFIED);
53 : }
54 :
55 : static PatchListEntry empty(String fileName, ChangeType changeType) {
56 1 : return new PatchListEntry(
57 : changeType,
58 : PatchType.UNIFIED,
59 : null,
60 : fileName,
61 : EMPTY_HEADER,
62 1 : ImmutableList.of(),
63 1 : ImmutableSet.of(),
64 : 0,
65 : 0,
66 : 0,
67 : 0);
68 : }
69 :
70 : private final ChangeType changeType;
71 : private final PatchType patchType;
72 : private final String oldName;
73 : private final String newName;
74 : private final byte[] header;
75 : private final ImmutableList<Edit> edits;
76 : private final ImmutableSet<Edit> editsDueToRebase;
77 : private final int insertions;
78 : private final int deletions;
79 : private final long size;
80 : private final long sizeDelta;
81 : // Note: When adding new fields, the serialVersionUID in PatchListKey must be
82 : // incremented so that entries from the cache are automatically invalidated.
83 :
84 : public PatchListEntry(
85 0 : FileHeader hdr, List<Edit> editList, Set<Edit> editsDueToRebase, long size, long sizeDelta) {
86 0 : changeType = toChangeType(hdr);
87 0 : patchType = toPatchType(hdr);
88 :
89 0 : switch (changeType) {
90 : case DELETED:
91 0 : oldName = null;
92 0 : newName = hdr.getOldPath();
93 0 : break;
94 :
95 : case ADDED:
96 : case MODIFIED:
97 : case REWRITE:
98 0 : oldName = null;
99 0 : newName = hdr.getNewPath();
100 0 : break;
101 :
102 : case COPIED:
103 : case RENAMED:
104 0 : oldName = hdr.getOldPath();
105 0 : newName = hdr.getNewPath();
106 0 : break;
107 :
108 : default:
109 0 : throw new IllegalArgumentException("Unsupported type " + changeType);
110 : }
111 :
112 0 : header = compact(hdr);
113 :
114 0 : if (hdr instanceof CombinedFileHeader || hdr.getHunks().isEmpty()) {
115 0 : edits = ImmutableList.of();
116 : } else {
117 0 : edits = ImmutableList.copyOf(editList);
118 : }
119 0 : this.editsDueToRebase = ImmutableSet.copyOf(editsDueToRebase);
120 :
121 0 : int ins = 0;
122 0 : int del = 0;
123 0 : for (Edit e : editList) {
124 0 : if (!editsDueToRebase.contains(e)) {
125 0 : del += e.getEndA() - e.getBeginA();
126 0 : ins += e.getEndB() - e.getBeginB();
127 : }
128 0 : }
129 0 : insertions = ins;
130 0 : deletions = del;
131 0 : this.size = size;
132 0 : this.sizeDelta = sizeDelta;
133 0 : }
134 :
135 : private PatchListEntry(
136 : ChangeType changeType,
137 : PatchType patchType,
138 : String oldName,
139 : String newName,
140 : byte[] header,
141 : ImmutableList<Edit> edits,
142 : ImmutableSet<Edit> editsDueToRebase,
143 : int insertions,
144 : int deletions,
145 : long size,
146 1 : long sizeDelta) {
147 1 : this.changeType = changeType;
148 1 : this.patchType = patchType;
149 1 : this.oldName = oldName;
150 1 : this.newName = newName;
151 1 : this.header = header;
152 1 : this.edits = edits;
153 1 : this.editsDueToRebase = editsDueToRebase;
154 1 : this.insertions = insertions;
155 1 : this.deletions = deletions;
156 1 : this.size = size;
157 1 : this.sizeDelta = sizeDelta;
158 1 : }
159 :
160 : int weigh() {
161 0 : int size = 16 + 6 * 8 + 2 * 4 + 20 + 16 + 8 + 4 + 20;
162 0 : size += stringSize(oldName);
163 0 : size += stringSize(newName);
164 0 : size += header.length;
165 0 : size += (8 + 16 + 4 * 4) * edits.size();
166 0 : size += (8 + 16 + 4 * 4) * editsDueToRebase.size();
167 0 : return size;
168 : }
169 :
170 : private static int stringSize(String str) {
171 0 : if (str != null) {
172 0 : return 16 + 3 * 4 + 16 + str.length() * 2;
173 : }
174 0 : return 0;
175 : }
176 :
177 : public ChangeType getChangeType() {
178 1 : return changeType;
179 : }
180 :
181 : public PatchType getPatchType() {
182 1 : return patchType;
183 : }
184 :
185 : public String getOldName() {
186 1 : return oldName;
187 : }
188 :
189 : public String getNewName() {
190 1 : return newName;
191 : }
192 :
193 : public ImmutableList<Edit> getEdits() {
194 : // Edits are mutable objects. As we serialize PatchListEntry asynchronously in H2CacheImpl, we
195 : // must ensure that its state isn't modified until it was properly stored in the cache.
196 1 : return deepCopyEdits(edits);
197 : }
198 :
199 : public ImmutableSet<Edit> getEditsDueToRebase() {
200 0 : return deepCopyEdits(editsDueToRebase);
201 : }
202 :
203 : public int getInsertions() {
204 0 : return insertions;
205 : }
206 :
207 : public int getDeletions() {
208 0 : return deletions;
209 : }
210 :
211 : public long getSize() {
212 0 : return size;
213 : }
214 :
215 : public long getSizeDelta() {
216 0 : return sizeDelta;
217 : }
218 :
219 : public ImmutableList<String> getHeaderLines() {
220 0 : final IntList m = RawParseUtils.lineMap(header, 0, header.length);
221 0 : final ImmutableList.Builder<String> headerLines =
222 0 : ImmutableList.builderWithExpectedSize(m.size() - 1);
223 0 : for (int i = 1; i < m.size() - 1; i++) {
224 0 : final int b = m.get(i);
225 0 : int e = m.get(i + 1);
226 0 : if (header[e - 1] == '\n') {
227 0 : e--;
228 : }
229 0 : headerLines.add(RawParseUtils.decode(UTF_8, header, b, e));
230 : }
231 0 : return headerLines.build();
232 : }
233 :
234 : private static ImmutableList<Edit> deepCopyEdits(List<Edit> edits) {
235 1 : return edits.stream().map(PatchListEntry::copy).collect(toImmutableList());
236 : }
237 :
238 : private static ImmutableSet<Edit> deepCopyEdits(Set<Edit> edits) {
239 0 : return edits.stream().map(PatchListEntry::copy).collect(toImmutableSet());
240 : }
241 :
242 : private static Edit copy(Edit edit) {
243 0 : return new Edit(edit.getBeginA(), edit.getEndA(), edit.getBeginB(), edit.getEndB());
244 : }
245 :
246 : void writeTo(OutputStream out) throws IOException {
247 0 : writeEnum(out, changeType);
248 0 : writeEnum(out, patchType);
249 0 : writeString(out, oldName);
250 0 : writeString(out, newName);
251 0 : writeBytes(out, header);
252 0 : writeVarInt32(out, insertions);
253 0 : writeVarInt32(out, deletions);
254 0 : writeFixInt64(out, size);
255 0 : writeFixInt64(out, sizeDelta);
256 :
257 0 : writeEditArray(out, edits);
258 0 : writeEditArray(out, editsDueToRebase);
259 0 : }
260 :
261 : private static void writeEditArray(OutputStream out, Collection<Edit> edits) throws IOException {
262 0 : writeVarInt32(out, edits.size());
263 0 : for (Edit edit : edits) {
264 0 : writeVarInt32(out, edit.getBeginA());
265 0 : writeVarInt32(out, edit.getEndA());
266 0 : writeVarInt32(out, edit.getBeginB());
267 0 : writeVarInt32(out, edit.getEndB());
268 0 : }
269 0 : }
270 :
271 : static PatchListEntry readFrom(InputStream in) throws IOException {
272 0 : ChangeType changeType = readEnum(in, ChangeType.values());
273 0 : PatchType patchType = readEnum(in, PatchType.values());
274 0 : String oldName = readString(in);
275 0 : String newName = readString(in);
276 0 : byte[] hdr = readBytes(in);
277 0 : int ins = readVarInt32(in);
278 0 : int del = readVarInt32(in);
279 0 : long size = readFixInt64(in);
280 0 : long sizeDelta = readFixInt64(in);
281 :
282 0 : Edit[] editArray = readEditArray(in);
283 0 : Edit[] editsDueToRebase = readEditArray(in);
284 :
285 0 : return new PatchListEntry(
286 : changeType,
287 : patchType,
288 : oldName,
289 : newName,
290 : hdr,
291 0 : ImmutableList.copyOf(editArray),
292 0 : ImmutableSet.copyOf(editsDueToRebase),
293 : ins,
294 : del,
295 : size,
296 : sizeDelta);
297 : }
298 :
299 : private static Edit[] readEditArray(InputStream in) throws IOException {
300 0 : int numEdits = readVarInt32(in);
301 0 : Edit[] edits = new Edit[numEdits];
302 0 : for (int i = 0; i < numEdits; i++) {
303 0 : int beginA = readVarInt32(in);
304 0 : int endA = readVarInt32(in);
305 0 : int beginB = readVarInt32(in);
306 0 : int endB = readVarInt32(in);
307 0 : edits[i] = new Edit(beginA, endA, beginB, endB);
308 : }
309 0 : return edits;
310 : }
311 :
312 : private static byte[] compact(FileHeader h) {
313 0 : final int end = end(h);
314 0 : if (h.getStartOffset() == 0 && end == h.getBuffer().length) {
315 0 : return h.getBuffer();
316 : }
317 :
318 0 : final byte[] buf = new byte[end - h.getStartOffset()];
319 0 : System.arraycopy(h.getBuffer(), h.getStartOffset(), buf, 0, buf.length);
320 0 : return buf;
321 : }
322 :
323 : private static int end(FileHeader h) {
324 0 : if (h instanceof CombinedFileHeader) {
325 0 : return h.getEndOffset();
326 : }
327 0 : if (!h.getHunks().isEmpty()) {
328 0 : return h.getHunks().get(0).getStartOffset();
329 : }
330 0 : return h.getEndOffset();
331 : }
332 :
333 : private static ChangeType toChangeType(FileHeader hdr) {
334 0 : switch (hdr.getChangeType()) {
335 : case ADD:
336 0 : return Patch.ChangeType.ADDED;
337 : case MODIFY:
338 0 : return Patch.ChangeType.MODIFIED;
339 : case DELETE:
340 0 : return Patch.ChangeType.DELETED;
341 : case RENAME:
342 0 : return Patch.ChangeType.RENAMED;
343 : case COPY:
344 0 : return Patch.ChangeType.COPIED;
345 : default:
346 0 : throw new IllegalArgumentException("Unsupported type " + hdr.getChangeType());
347 : }
348 : }
349 :
350 : private static PatchType toPatchType(FileHeader hdr) {
351 : PatchType pt;
352 :
353 0 : switch (hdr.getPatchType()) {
354 : case UNIFIED:
355 0 : pt = Patch.PatchType.UNIFIED;
356 0 : break;
357 : case GIT_BINARY:
358 : case BINARY:
359 0 : pt = Patch.PatchType.BINARY;
360 0 : break;
361 : default:
362 0 : throw new IllegalArgumentException("Unsupported type " + hdr.getPatchType());
363 : }
364 :
365 0 : if (pt != PatchType.BINARY) {
366 0 : final byte[] buf = hdr.getBuffer();
367 0 : for (int ptr = hdr.getStartOffset(); ptr < hdr.getEndOffset(); ptr++) {
368 0 : if (buf[ptr] == '\0') {
369 : // Its really binary, but Git couldn't see the nul early enough
370 : // to realize its binary, and instead produced the diff.
371 : //
372 : // Force it to be a binary; it really should have been that.
373 : //
374 0 : pt = PatchType.BINARY;
375 0 : break;
376 : }
377 : }
378 : }
379 :
380 0 : return pt;
381 : }
382 : }
|