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:
committed by
Shawn O. Pearce
parent
65b21e02ce
commit
02f44c9cf9
@@ -11,6 +11,52 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// 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;
|
package com.google.gerrit.server.patch;
|
||||||
|
|
||||||
@@ -31,8 +77,10 @@ import com.google.inject.Singleton;
|
|||||||
import com.google.inject.TypeLiteral;
|
import com.google.inject.TypeLiteral;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.diff.DiffFormatter;
|
||||||
import org.eclipse.jgit.diff.Edit;
|
import org.eclipse.jgit.diff.Edit;
|
||||||
import org.eclipse.jgit.diff.MyersDiff;
|
import org.eclipse.jgit.diff.MyersDiff;
|
||||||
|
import org.eclipse.jgit.diff.RawText;
|
||||||
import org.eclipse.jgit.diff.ReplaceEdit;
|
import org.eclipse.jgit.diff.ReplaceEdit;
|
||||||
import org.eclipse.jgit.lib.AnyObjectId;
|
import org.eclipse.jgit.lib.AnyObjectId;
|
||||||
import org.eclipse.jgit.lib.Config;
|
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.RevTree;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.PrintStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -129,64 +181,40 @@ public class PatchListCacheImpl implements PatchListCache {
|
|||||||
|
|
||||||
private PatchList readPatchList(final PatchListKey key,
|
private PatchList readPatchList(final PatchListKey key,
|
||||||
final Repository repo) throws IOException {
|
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 RevWalk rw = new RevWalk(repo);
|
||||||
final RevCommit b = rw.parseCommit(key.getNewId());
|
final RevCommit b = rw.parseCommit(key.getNewId());
|
||||||
final AnyObjectId a = aFor(key, repo, b);
|
final AnyObjectId a = aFor(key, repo, b);
|
||||||
|
|
||||||
final List<String> args = new ArrayList<String>();
|
if (a == null) {
|
||||||
args.add("git");
|
return new PatchList(a, b, computeIntraline, new PatchListEntry[0]);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final org.eclipse.jgit.patch.Patch p = new org.eclipse.jgit.patch.Patch();
|
RevTree aTree = rw.parseTree(a);
|
||||||
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 bTree = b.getTree();
|
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 int cnt = p.getFiles().size();
|
||||||
final PatchListEntry[] entries = new PatchListEntry[cnt];
|
final PatchListEntry[] entries = new PatchListEntry[cnt];
|
||||||
for (int i = 0; i < cnt; i++) {
|
for (int i = 0; i < cnt; i++) {
|
||||||
@@ -195,6 +223,59 @@ public class PatchListCacheImpl implements PatchListCache {
|
|||||||
return new PatchList(a, b, computeIntraline, entries);
|
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,
|
private PatchListEntry newEntry(Repository repo, RevTree aTree,
|
||||||
RevTree bTree, FileHeader fileHeader) throws IOException {
|
RevTree bTree, FileHeader fileHeader) throws IOException {
|
||||||
final FileMode oldMode = fileHeader.getOldMode();
|
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 {
|
private static ObjectId emptyTree(final Repository repo) throws IOException {
|
||||||
return new ObjectWriter(repo).writeCanonicalTree(new byte[0]);
|
return new ObjectWriter(repo).writeCanonicalTree(new byte[0]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user