diff options
Diffstat (limited to 'java/com/google/gerrit/server/patch/PatchListEntry.java')
-rw-r--r-- | java/com/google/gerrit/server/patch/PatchListEntry.java | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/patch/PatchListEntry.java b/java/com/google/gerrit/server/patch/PatchListEntry.java new file mode 100644 index 0000000000..6b1a153edc --- /dev/null +++ b/java/com/google/gerrit/server/patch/PatchListEntry.java @@ -0,0 +1,389 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.patch; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.gerrit.server.ioutil.BasicSerialization.readBytes; +import static com.google.gerrit.server.ioutil.BasicSerialization.readEnum; +import static com.google.gerrit.server.ioutil.BasicSerialization.readFixInt64; +import static com.google.gerrit.server.ioutil.BasicSerialization.readString; +import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32; +import static com.google.gerrit.server.ioutil.BasicSerialization.writeBytes; +import static com.google.gerrit.server.ioutil.BasicSerialization.writeEnum; +import static com.google.gerrit.server.ioutil.BasicSerialization.writeFixInt64; +import static com.google.gerrit.server.ioutil.BasicSerialization.writeString; +import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.gerrit.reviewdb.client.Patch; +import com.google.gerrit.reviewdb.client.Patch.ChangeType; +import com.google.gerrit.reviewdb.client.Patch.PatchType; +import com.google.gerrit.reviewdb.client.PatchSet; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import org.eclipse.jgit.diff.Edit; +import org.eclipse.jgit.patch.CombinedFileHeader; +import org.eclipse.jgit.patch.FileHeader; +import org.eclipse.jgit.util.IntList; +import org.eclipse.jgit.util.RawParseUtils; + +public class PatchListEntry { + private static final byte[] EMPTY_HEADER = {}; + + static PatchListEntry empty(String fileName) { + return new PatchListEntry( + ChangeType.MODIFIED, + PatchType.UNIFIED, + null, + fileName, + EMPTY_HEADER, + ImmutableList.of(), + ImmutableSet.of(), + 0, + 0, + 0, + 0); + } + + private final ChangeType changeType; + private final PatchType patchType; + private final String oldName; + private final String newName; + private final byte[] header; + private final ImmutableList<Edit> edits; + private final ImmutableSet<Edit> editsDueToRebase; + private final int insertions; + private final int deletions; + private final long size; + private final long sizeDelta; + // Note: When adding new fields, the serialVersionUID in PatchListKey must be + // incremented so that entries from the cache are automatically invalidated. + + PatchListEntry( + FileHeader hdr, List<Edit> editList, Set<Edit> editsDueToRebase, long size, long sizeDelta) { + changeType = toChangeType(hdr); + patchType = toPatchType(hdr); + + switch (changeType) { + case DELETED: + oldName = null; + newName = hdr.getOldPath(); + break; + + case ADDED: + case MODIFIED: + case REWRITE: + oldName = null; + newName = hdr.getNewPath(); + break; + + case COPIED: + case RENAMED: + oldName = hdr.getOldPath(); + newName = hdr.getNewPath(); + break; + + default: + throw new IllegalArgumentException("Unsupported type " + changeType); + } + + header = compact(hdr); + + if (hdr instanceof CombinedFileHeader || hdr.getHunks().isEmpty()) { + edits = ImmutableList.of(); + } else { + edits = ImmutableList.copyOf(editList); + } + this.editsDueToRebase = ImmutableSet.copyOf(editsDueToRebase); + + int ins = 0; + int del = 0; + for (Edit e : editList) { + if (!editsDueToRebase.contains(e)) { + del += e.getEndA() - e.getBeginA(); + ins += e.getEndB() - e.getBeginB(); + } + } + insertions = ins; + deletions = del; + this.size = size; + this.sizeDelta = sizeDelta; + } + + private PatchListEntry( + ChangeType changeType, + PatchType patchType, + String oldName, + String newName, + byte[] header, + ImmutableList<Edit> edits, + ImmutableSet<Edit> editsDueToRebase, + int insertions, + int deletions, + long size, + long sizeDelta) { + this.changeType = changeType; + this.patchType = patchType; + this.oldName = oldName; + this.newName = newName; + this.header = header; + this.edits = edits; + this.editsDueToRebase = editsDueToRebase; + this.insertions = insertions; + this.deletions = deletions; + this.size = size; + this.sizeDelta = sizeDelta; + } + + int weigh() { + int size = 16 + 6 * 8 + 2 * 4 + 20 + 16 + 8 + 4 + 20; + size += stringSize(oldName); + size += stringSize(newName); + size += header.length; + size += (8 + 16 + 4 * 4) * edits.size(); + size += (8 + 16 + 4 * 4) * editsDueToRebase.size(); + return size; + } + + private static int stringSize(String str) { + if (str != null) { + return 16 + 3 * 4 + 16 + str.length() * 2; + } + return 0; + } + + public ChangeType getChangeType() { + return changeType; + } + + public PatchType getPatchType() { + return patchType; + } + + public String getOldName() { + return oldName; + } + + public String getNewName() { + return newName; + } + + public ImmutableList<Edit> getEdits() { + // Edits are mutable objects. As we serialize PatchListEntry asynchronously in H2CacheImpl, we + // must ensure that its state isn't modified until it was properly stored in the cache. + return deepCopyEdits(edits); + } + + public ImmutableSet<Edit> getEditsDueToRebase() { + return deepCopyEdits(editsDueToRebase); + } + + public int getInsertions() { + return insertions; + } + + public int getDeletions() { + return deletions; + } + + public long getSize() { + return size; + } + + public long getSizeDelta() { + return sizeDelta; + } + + public List<String> getHeaderLines() { + final IntList m = RawParseUtils.lineMap(header, 0, header.length); + final List<String> headerLines = new ArrayList<>(m.size() - 1); + for (int i = 1; i < m.size() - 1; i++) { + final int b = m.get(i); + int e = m.get(i + 1); + if (header[e - 1] == '\n') { + e--; + } + headerLines.add(RawParseUtils.decode(UTF_8, header, b, e)); + } + return headerLines; + } + + Patch toPatch(PatchSet.Id setId) { + final Patch p = new Patch(new Patch.Key(setId, getNewName())); + p.setChangeType(getChangeType()); + p.setPatchType(getPatchType()); + p.setSourceFileName(getOldName()); + p.setInsertions(insertions); + p.setDeletions(deletions); + return p; + } + + private static ImmutableList<Edit> deepCopyEdits(List<Edit> edits) { + return edits.stream().map(PatchListEntry::copy).collect(toImmutableList()); + } + + private static ImmutableSet<Edit> deepCopyEdits(Set<Edit> edits) { + return edits.stream().map(PatchListEntry::copy).collect(toImmutableSet()); + } + + private static Edit copy(Edit edit) { + return new Edit(edit.getBeginA(), edit.getEndA(), edit.getBeginB(), edit.getEndB()); + } + + void writeTo(OutputStream out) throws IOException { + writeEnum(out, changeType); + writeEnum(out, patchType); + writeString(out, oldName); + writeString(out, newName); + writeBytes(out, header); + writeVarInt32(out, insertions); + writeVarInt32(out, deletions); + writeFixInt64(out, size); + writeFixInt64(out, sizeDelta); + + writeEditArray(out, edits); + writeEditArray(out, editsDueToRebase); + } + + private static void writeEditArray(OutputStream out, Collection<Edit> edits) throws IOException { + writeVarInt32(out, edits.size()); + for (Edit edit : edits) { + writeVarInt32(out, edit.getBeginA()); + writeVarInt32(out, edit.getEndA()); + writeVarInt32(out, edit.getBeginB()); + writeVarInt32(out, edit.getEndB()); + } + } + + static PatchListEntry readFrom(InputStream in) throws IOException { + ChangeType changeType = readEnum(in, ChangeType.values()); + PatchType patchType = readEnum(in, PatchType.values()); + String oldName = readString(in); + String newName = readString(in); + byte[] hdr = readBytes(in); + int ins = readVarInt32(in); + int del = readVarInt32(in); + long size = readFixInt64(in); + long sizeDelta = readFixInt64(in); + + Edit[] editArray = readEditArray(in); + Edit[] editsDueToRebase = readEditArray(in); + + return new PatchListEntry( + changeType, + patchType, + oldName, + newName, + hdr, + ImmutableList.copyOf(editArray), + ImmutableSet.copyOf(editsDueToRebase), + ins, + del, + size, + sizeDelta); + } + + private static Edit[] readEditArray(InputStream in) throws IOException { + int numEdits = readVarInt32(in); + Edit[] edits = new Edit[numEdits]; + for (int i = 0; i < numEdits; i++) { + int beginA = readVarInt32(in); + int endA = readVarInt32(in); + int beginB = readVarInt32(in); + int endB = readVarInt32(in); + edits[i] = new Edit(beginA, endA, beginB, endB); + } + return edits; + } + + private static byte[] compact(FileHeader h) { + final int end = end(h); + if (h.getStartOffset() == 0 && end == h.getBuffer().length) { + return h.getBuffer(); + } + + final byte[] buf = new byte[end - h.getStartOffset()]; + System.arraycopy(h.getBuffer(), h.getStartOffset(), buf, 0, buf.length); + return buf; + } + + private static int end(FileHeader h) { + if (h instanceof CombinedFileHeader) { + return h.getEndOffset(); + } + if (!h.getHunks().isEmpty()) { + return h.getHunks().get(0).getStartOffset(); + } + return h.getEndOffset(); + } + + private static ChangeType toChangeType(FileHeader hdr) { + switch (hdr.getChangeType()) { + case ADD: + return Patch.ChangeType.ADDED; + case MODIFY: + return Patch.ChangeType.MODIFIED; + case DELETE: + return Patch.ChangeType.DELETED; + case RENAME: + return Patch.ChangeType.RENAMED; + case COPY: + return Patch.ChangeType.COPIED; + default: + throw new IllegalArgumentException("Unsupported type " + hdr.getChangeType()); + } + } + + private static PatchType toPatchType(FileHeader hdr) { + PatchType pt; + + switch (hdr.getPatchType()) { + case UNIFIED: + pt = Patch.PatchType.UNIFIED; + break; + case GIT_BINARY: + case BINARY: + pt = Patch.PatchType.BINARY; + break; + default: + throw new IllegalArgumentException("Unsupported type " + hdr.getPatchType()); + } + + if (pt != PatchType.BINARY) { + final byte[] buf = hdr.getBuffer(); + for (int ptr = hdr.getStartOffset(); ptr < hdr.getEndOffset(); ptr++) { + if (buf[ptr] == '\0') { + // Its really binary, but Git couldn't see the nul early enough + // to realize its binary, and instead produced the diff. + // + // Force it to be a binary; it really should have been that. + // + pt = PatchType.BINARY; + break; + } + } + } + + return pt; + } +} |