blob: 866ca7598a2cf8b76920f7ec8c744f2e76d90905 [file] [log] [blame]
// Copyright 2008 Google Inc.
//
// 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.codereview.manager.unpack;
import static org.spearce.jgit.lib.Constants.encodeASCII;
import com.google.codereview.internal.UploadPatchsetFile.UploadPatchsetFileRequest.StatusType;
import org.spearce.jgit.lib.Constants;
import org.spearce.jgit.lib.ObjectId;
import org.spearce.jgit.lib.ObjectWriter;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.lib.Tree;
import org.spearce.jgit.revwalk.RevCommit;
import org.spearce.jgit.util.RawParseUtils;
import java.io.IOException;
/** Parses 'git diff-tree' output into {@link FileDiff} objects. */
class DiffReader {
private static final byte[] GIT_DIFF = encodeASCII("diff --git a/");
private static final byte[] H_DELETED_FILE = encodeASCII("deleted file ");
private static final byte[] H_NEW_FILE = encodeASCII("new file ");
private static final byte[] H_INDEX = encodeASCII("index ");
static final byte[] H_NEWPATH = encodeASCII("+++ b/");
private static final byte[] H_BINARY = encodeASCII("Binary file");
private static final int MAX_PATCH_SIZE = 1024 * 1024; // bytes
static boolean match(final byte[] key, final byte[] line, int offset) {
int remain = line.length - offset;
if (remain < key.length) {
return false;
}
for (int k = 0; k < key.length;) {
if (key[k++] != line[offset++]) {
return false;
}
}
return true;
}
private static String str(final byte[] b, final int off) {
return RawParseUtils.decode(Constants.CHARSET, b, off, b.length);
}
private Process proc;
private RecordInputStream in;
private FileDiff current;
DiffReader(final Repository db, final RevCommit c) throws IOException {
final String baseTree;
if (c.getParentCount() > 0) {
baseTree = c.getParent(0).getTree().getId().name();
} else {
baseTree = new ObjectWriter(db).writeTree(new Tree(db)).name();
}
final String headTree = c.getTree().getId().name();
proc =
Runtime.getRuntime().exec(
new String[] {"git", "--git-dir=.", "diff-tree", "--unified=1",
"-M", "--full-index", baseTree, headTree}, null,
db.getDirectory());
proc.getOutputStream().close();
proc.getErrorStream().close();
in = new RecordInputStream(proc.getInputStream());
}
FileDiff next() throws IOException {
return readOneDiff();
}
private FileDiff readOneDiff() throws IOException {
boolean consume = false;
for (;;) {
final byte[] hdr = in.readRecord('\n');
if (hdr == null) {
final FileDiff prior = current;
current = null;
return prior;
}
if (match(GIT_DIFF, hdr, 0)) {
final FileDiff prior = current;
current = new FileDiff();
current.appendLine(hdr);
// TODO(sop) This can split the old and new names wrong if the
// old name was "f b/c". Until we can do diffs in-core we'll
// just assume nobody uses spaces in filenames.
//
final String hdrStr = str(hdr, 0);
final int newpos = hdrStr.indexOf(" b/");
if (newpos > 0) {
current.setFilename(hdrStr.substring(newpos + 3));
}
if (prior != null) {
return prior;
} else {
continue;
}
}
if (match(H_INDEX, hdr, 0)) {
current.setBaseId(ObjectId.fromString(hdr, H_INDEX.length));
current.setFinalId(ObjectId.fromString(hdr, H_INDEX.length
+ Constants.OBJECT_ID_LENGTH * 2 + 2));
} else if (match(H_NEW_FILE, hdr, 0)) {
current.setStatus(StatusType.ADD);
} else if (match(H_DELETED_FILE, hdr, 0)) {
current.setStatus(StatusType.DELETE);
} else if (match(H_BINARY, hdr, 0)) {
current.setBinary(true);
} else if (match(H_NEWPATH, hdr, 0)) {
current.setFilename(str(hdr, H_NEWPATH.length));
}
if (consume) {
continue;
} else if (current.getPatchSize() + hdr.length >= MAX_PATCH_SIZE) {
current.truncatePatch();
consume = true;
} else {
current.appendLine(hdr);
}
}
}
void close() throws IOException {
in.close();
try {
proc.waitFor();
} catch (InterruptedException ie) {
//
}
}
}