Replace git diff-tree invocation

Gerrit currently runs git diff-tree to discover changed files.
That command might not be available in all environments, so I
replaced it with a Java based implementation.

The Java based implementation does not yet support whitespace
ignore, rename detection, or merge commits.

Change-Id: I0adfb7b68e7beba635176f1295c442ffdd397d4e
(cherry picked from commit 34f5af4c85)
Change-Id: Ib76aaad601c858399a58219fdf179a55bdaacf85
This commit is contained in:
Jeff Schumacher
2010-06-22 18:20:52 -07:00
committed by Shawn O. Pearce
parent 65b21e02ce
commit 02f44c9cf9

View File

@@ -11,6 +11,52 @@
// 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.
//
// Some portions (e.g. outputDiff) below are:
//
// Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com>
// Copyright (C) 2009, Johannes E. Schindelin
// Copyright (C) 2009, Johannes Schindelin <johannes.schindelin@gmx.de>
// and other copyright owners as documented in the project's IP log.
//
// This program and the accompanying materials are made available
// under the terms of the Eclipse Distribution License v1.0 which
// accompanies this distribution, is reproduced below, and is
// available at http://www.eclipse.org/org/documents/edl-v10.php
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or
// without modification, are permitted provided that the following
// conditions are met:
//
// - Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// - Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// - Neither the name of the Eclipse Foundation, Inc. nor the
// names of its contributors may be used to endorse or promote
// products derived from this software without specific prior
// written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
package com.google.gerrit.server.patch;
@@ -31,8 +77,10 @@ import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.MyersDiff;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.ReplaceEdit;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
@@ -48,9 +96,13 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.QuotedString;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -129,64 +181,40 @@ public class PatchListCacheImpl implements PatchListCache {
private PatchList readPatchList(final PatchListKey key,
final Repository repo) throws IOException {
// TODO(jeffschu) correctly handle file renames
// TODO(jeffschu) correctly handle merge commits
// TODO(jeffschu) implement whitespace ignore
final RevWalk rw = new RevWalk(repo);
final RevCommit b = rw.parseCommit(key.getNewId());
final AnyObjectId a = aFor(key, repo, b);
final List<String> args = new ArrayList<String>();
args.add("git");
args.add("--git-dir=.");
args.add("diff-tree");
args.add("-M");
switch (key.getWhitespace()) {
case IGNORE_NONE:
break;
case IGNORE_SPACE_AT_EOL:
args.add("--ignore-space-at-eol");
break;
case IGNORE_SPACE_CHANGE:
args.add("--ignore-space-change");
break;
case IGNORE_ALL_SPACE:
args.add("--ignore-all-space");
break;
default:
throw new IOException("Unsupported whitespace " + key.getWhitespace());
}
if (a == null /* want combined diff */) {
args.add("--cc");
args.add(b.name());
} else {
args.add("--unified=1");
args.add(a.name());
args.add(b.name());
if (a == null) {
return new PatchList(a, b, computeIntraline, new PatchListEntry[0]);
}
final org.eclipse.jgit.patch.Patch p = new org.eclipse.jgit.patch.Patch();
final Process diffProcess = exec(repo, args);
try {
diffProcess.getOutputStream().close();
diffProcess.getErrorStream().close();
final InputStream in = diffProcess.getInputStream();
try {
p.parse(in);
} finally {
in.close();
}
} finally {
try {
final int rc = diffProcess.waitFor();
if (rc != 0) {
throw new IOException("git diff-tree exited abnormally: " + rc);
}
} catch (InterruptedException ie) {
}
}
RevTree aTree = a != null ? rw.parseTree(a) : null;
RevTree aTree = rw.parseTree(a);
RevTree bTree = b.getTree();
final TreeWalk walk = new TreeWalk(repo);
walk.reset();
walk.setRecursive(true);
walk.addTree(aTree);
walk.addTree(bTree);
walk.setFilter(TreeFilter.ANY_DIFF);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(buf, true, "UTF-8");
while (walk.next()) {
outputDiff(ps, walk.getPathString(), walk.getObjectId(0), walk
.getFileMode(0), walk.getObjectId(1), walk.getFileMode(1), repo);
}
org.eclipse.jgit.patch.Patch p = new org.eclipse.jgit.patch.Patch();
ps.flush();
p.parse(new ByteArrayInputStream(buf.toByteArray()));
final int cnt = p.getFiles().size();
final PatchListEntry[] entries = new PatchListEntry[cnt];
for (int i = 0; i < cnt; i++) {
@@ -195,6 +223,59 @@ public class PatchListCacheImpl implements PatchListCache {
return new PatchList(a, b, computeIntraline, entries);
}
private void outputDiff(PrintStream out, String path, ObjectId id1,
FileMode mode1, ObjectId id2, FileMode mode2, Repository repo)
throws IOException {
DiffFormatter fmt = new DiffFormatter();
String name1 = "a/" + path;
if (needsQuoting(name1)) {
name1 = QuotedString.GIT_PATH.quote(name1);
}
String name2 = "b/" + path;
if (needsQuoting(name2)) {
name2 = QuotedString.GIT_PATH.quote(name2);
}
out.print("diff --git " + name1 + " " + name2 + "\n");
boolean isNew = FileMode.MISSING.equals(mode1);
boolean isDelete = FileMode.MISSING.equals(mode2);
if (isNew) {
out.print("new file mode " + mode2 + "\n");
} else if (isDelete) {
out.print("deleted file mode " + mode1 + "\n");
} else if (!mode1.equals(mode2)) {
out.print("old mode " + mode1 + "\n");
out.print("new mode " + mode2 + "\n");
}
out.print("index " + id1.abbreviate(repo, 7).name() + ".."
+ id2.abbreviate(repo, 7).name()
+ (mode1.equals(mode2) ? " " + mode1 : "") + "\n");
out.print("--- " + (isNew ? "/dev/null" : name1) + "\n");
out.print("+++ " + (isDelete ? "/dev/null" : name2) + "\n");
RawText a = getRawText(id1, repo);
RawText b = getRawText(id2, repo);
MyersDiff diff = new MyersDiff(a, b);
fmt.formatEdits(out, a, b, diff.getEdits());
}
private static boolean needsQuoting(String path) {
// We should quote the path if the quoted form of the path
// differs by more than simply having a leading and trailing
// double quote added.
//
return !QuotedString.GIT_PATH.quote(path).equals('"' + path + '"');
}
private RawText getRawText(ObjectId id, Repository repo) throws IOException {
if (id.equals(ObjectId.zeroId())) {
return new RawText(new byte[] {});
}
return new RawText(repo.openBlob(id).getCachedBytes());
}
private PatchListEntry newEntry(Repository repo, RevTree aTree,
RevTree bTree, FileHeader fileHeader) throws IOException {
final FileMode oldMode = fileHeader.getOldMode();
@@ -521,12 +602,6 @@ public class PatchListCacheImpl implements PatchListCache {
}
}
private static Process exec(final Repository repo, final List<String> args)
throws IOException {
final String[] argv = args.toArray(new String[args.size()]);
return Runtime.getRuntime().exec(argv, null, repo.getDirectory());
}
private static ObjectId emptyTree(final Repository repo) throws IOException {
return new ObjectWriter(repo).writeCanonicalTree(new byte[0]);
}