blob: 625f56c2532d4901253ca26cc7b5937d624d6786 [file] [log] [blame]
// 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.entities.Patch;
import com.google.gerrit.entities.Patch.ChangeType;
import com.google.gerrit.entities.Patch.PatchType;
import com.google.gerrit.entities.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(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;
}
}