Move rename detection, path following into DiffFormatter
Applications just want a quick way to configure our diff
implementation, and then just want to use it without a lot of fuss.
Move all of the rename detection logic and path following logic
out of our pgm package and into DiffFormatter itself, making it
much easier for a GUI to take advantage of the features without
duplicating a lot of code.
Change-Id: I4b54e987bb6dc804fb270cbc495fe4cae26c7b0e
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties
index dc738d3..e7dce1b 100644
--- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties
+++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties
@@ -168,6 +168,7 @@
usage_logAllPretty=format:%H %ct %P' output=log --all '--pretty=format:%H %ct %P' output
usage_moveRenameABranch=move/rename a branch
usage_nameStatus=show only name and status of files
+usage_noRenames=disable rename detection
usage_outputFile=Output file
usage_path=path
usage_performFsckStyleChecksOnReceive=perform fsck style checks on receive
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
index 2be5722..b6650a4 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
@@ -46,9 +46,7 @@
package org.eclipse.jgit.pgm;
import java.io.BufferedOutputStream;
-import java.io.IOException;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.List;
import org.eclipse.jgit.diff.DiffEntry;
@@ -62,8 +60,6 @@
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
@@ -74,12 +70,10 @@ class Diff extends TextBuiltin {
new BufferedOutputStream(System.out));
@Argument(index = 0, metaVar = "metaVar_treeish", required = true)
- void tree_0(final AbstractTreeIterator c) {
- trees.add(c);
- }
+ private AbstractTreeIterator oldTree;
@Argument(index = 1, metaVar = "metaVar_treeish", required = true)
- private final List<AbstractTreeIterator> trees = new ArrayList<AbstractTreeIterator>();
+ private AbstractTreeIterator newTree;
@Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = PathTreeFilterHandler.class)
private TreeFilter pathFilter = TreeFilter.ALL;
@@ -89,7 +83,12 @@ void tree_0(final AbstractTreeIterator c) {
boolean showPatch;
@Option(name = "-M", usage = "usage_detectRenames")
- private boolean detectRenames;
+ private Boolean detectRenames;
+
+ @Option(name = "--no-renames", usage = "usage_noRenames")
+ void noRenames(@SuppressWarnings("unused") boolean on) {
+ detectRenames = Boolean.FALSE;
+ }
@Option(name = "-l", usage = "usage_renameLimit")
private Integer renameLimit;
@@ -136,16 +135,27 @@ void abbrev(@SuppressWarnings("unused") boolean on) {
@Override
protected void run() throws Exception {
- List<DiffEntry> files = scan();
+ diffFmt.setRepository(db);
+ try {
+ diffFmt.setProgressMonitor(new TextProgressMonitor());
+ diffFmt.setPathFilter(pathFilter);
+ if (detectRenames != null)
+ diffFmt.setDetectRenames(detectRenames.booleanValue());
+ if (renameLimit != null && diffFmt.isDetectRenames()) {
+ RenameDetector rd = diffFmt.getRenameDetector();
+ rd.setRenameLimit(renameLimit.intValue());
+ }
- if (showNameAndStatusOnly) {
- nameStatus(out, files);
- out.flush();
+ if (showNameAndStatusOnly) {
+ nameStatus(out, diffFmt.scan(oldTree, newTree));
+ out.flush();
- } else {
- diffFmt.setRepository(db);
- diffFmt.format(files);
- diffFmt.flush();
+ } else {
+ diffFmt.format(oldTree, newTree);
+ diffFmt.flush();
+ }
+ } finally {
+ diffFmt.release();
}
}
@@ -174,23 +184,4 @@ static void nameStatus(PrintWriter out, List<DiffEntry> files) {
}
}
}
-
- private List<DiffEntry> scan() throws IOException {
- final TreeWalk walk = new TreeWalk(db);
- walk.reset();
- walk.setRecursive(true);
- for (final AbstractTreeIterator i : trees)
- walk.addTree(i);
- walk.setFilter(AndTreeFilter.create(TreeFilter.ANY_DIFF, pathFilter));
-
- List<DiffEntry> files = DiffEntry.scan(walk);
- if (detectRenames) {
- RenameDetector rd = new RenameDetector(db);
- if (renameLimit != null)
- rd.setRenameLimit(renameLimit.intValue());
- rd.addAll(files);
- files = rd.compute(new TextProgressMonitor());
- }
- return files;
- }
}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
index d0ae22a..2b29f73 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
@@ -51,32 +51,25 @@
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
-import java.util.Collections;
import java.util.Iterator;
-import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
-import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawTextIgnoreAllWhitespace;
import org.eclipse.jgit.diff.RawTextIgnoreLeadingWhitespace;
import org.eclipse.jgit.diff.RawTextIgnoreTrailingWhitespace;
import org.eclipse.jgit.diff.RawTextIgnoreWhitespaceChange;
import org.eclipse.jgit.diff.RenameDetector;
-import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.revwalk.FollowFilter;
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.AndTreeFilter;
-import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.kohsuke.args4j.Option;
@Command(common = true, usage = "usage_viewCommitHistory")
@@ -98,7 +91,12 @@ class Log extends RevWalkTextBuiltin {
boolean showPatch;
@Option(name = "-M", usage = "usage_detectRenames")
- private boolean detectRenames;
+ private Boolean detectRenames;
+
+ @Option(name = "--no-renames", usage = "usage_noRenames")
+ void noRenames(@SuppressWarnings("unused") boolean on) {
+ detectRenames = Boolean.FALSE;
+ }
@Option(name = "-l", usage = "usage_renameLimit")
private Integer renameLimit;
@@ -156,6 +154,24 @@ protected RevWalk createWalk() {
}
@Override
+ protected void run() throws Exception {
+ diffFmt.setRepository(db);
+ try {
+ diffFmt.setPathFilter(pathFilter);
+ if (detectRenames != null)
+ diffFmt.setDetectRenames(detectRenames.booleanValue());
+ if (renameLimit != null && diffFmt.isDetectRenames()) {
+ RenameDetector rd = diffFmt.getRenameDetector();
+ rd.setRenameLimit(renameLimit.intValue());
+ }
+
+ super.run();
+ } finally {
+ diffFmt.release();
+ }
+ }
+
+ @Override
protected void show(final RevCommit c) throws Exception {
out.print(CLIText.get().commitLabel);
out.print(" ");
@@ -196,71 +212,16 @@ protected void show(final RevCommit c) throws Exception {
}
private void showDiff(RevCommit c) throws IOException {
- final TreeWalk tw = new TreeWalk(db);
- tw.setRecursive(true);
- tw.reset();
- tw.addTree(c.getParent(0).getTree());
- tw.addTree(c.getTree());
- tw.setFilter(AndTreeFilter.create(pathFilter, TreeFilter.ANY_DIFF));
+ final RevTree a = c.getParent(0).getTree();
+ final RevTree b = c.getTree();
- List<DiffEntry> files = DiffEntry.scan(tw);
- if (pathFilter instanceof FollowFilter && isAdd(files)) {
- // The file we are following was added here, find where it
- // came from so we can properly show the rename or copy,
- // then continue digging backwards.
- //
- tw.reset();
- tw.addTree(c.getParent(0).getTree());
- tw.addTree(c.getTree());
- tw.setFilter(TreeFilter.ANY_DIFF);
- files = updateFollowFilter(detectRenames(DiffEntry.scan(tw)));
-
- } else if (detectRenames)
- files = detectRenames(files);
-
- if (showNameAndStatusOnly) {
- Diff.nameStatus(out, files);
-
- } else {
- diffFmt.setRepository(db);
- diffFmt.format(files);
+ if (showNameAndStatusOnly)
+ Diff.nameStatus(out, diffFmt.scan(a, b));
+ else {
+ diffFmt.format(a, b);
diffFmt.flush();
}
out.println();
- }
-
- private List<DiffEntry> detectRenames(List<DiffEntry> files)
- throws IOException {
- RenameDetector rd = new RenameDetector(db);
- if (renameLimit != null)
- rd.setRenameLimit(renameLimit.intValue());
- rd.addAll(files);
- return rd.compute();
- }
-
- private boolean isAdd(List<DiffEntry> files) {
- String oldPath = ((FollowFilter) pathFilter).getPath();
- for (DiffEntry ent : files) {
- if (ent.getChangeType() == ChangeType.ADD
- && ent.getNewPath().equals(oldPath))
- return true;
- }
- return false;
- }
-
- private List<DiffEntry> updateFollowFilter(List<DiffEntry> files) {
- String oldPath = ((FollowFilter) pathFilter).getPath();
- for (DiffEntry ent : files) {
- if (isRename(ent) && ent.getNewPath().equals(oldPath)) {
- pathFilter = FollowFilter.create(ent.getOldPath());
- return Collections.singletonList(ent);
- }
- }
- return Collections.emptyList();
- }
-
- private static boolean isRename(DiffEntry ent) {
- return ent.getChangeType() == ChangeType.RENAME
- || ent.getChangeType() == ChangeType.COPY;
+ out.flush();
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java
index d7a10e4..eefbefb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java
@@ -77,10 +77,17 @@ public void setUp() throws Exception {
df.setAbbreviationLength(8);
}
+ @Override
+ public void tearDown() throws Exception {
+ if (df != null)
+ df.release();
+ super.tearDown();
+ }
+
public void testCreateFileHeader_Add() throws Exception {
ObjectId adId = blob("a\nd\n");
DiffEntry ent = DiffEntry.add("FOO", adId);
- FileHeader fh = df.createFileHeader(ent);
+ FileHeader fh = df.toFileHeader(ent);
String diffHeader = "diff --git a/FOO b/FOO\n" //
+ "new file mode " + REGULAR_FILE + "\n"
@@ -115,7 +122,7 @@ public void testCreateFileHeader_Add() throws Exception {
public void testCreateFileHeader_Delete() throws Exception {
ObjectId adId = blob("a\nd\n");
DiffEntry ent = DiffEntry.delete("FOO", adId);
- FileHeader fh = df.createFileHeader(ent);
+ FileHeader fh = df.toFileHeader(ent);
String diffHeader = "diff --git a/FOO b/FOO\n" //
+ "deleted file mode " + REGULAR_FILE + "\n"
@@ -158,7 +165,7 @@ public void testCreateFileHeader_Modify() throws Exception {
DiffEntry mod = DiffEntry.pair(ChangeType.MODIFY, ad, abcd, 0);
- FileHeader fh = df.createFileHeader(mod);
+ FileHeader fh = df.toFileHeader(mod);
assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
assertEquals(0, fh.getStartOffset());
@@ -193,7 +200,7 @@ public void testCreateFileHeader_Binary() throws Exception {
DiffEntry mod = DiffEntry.pair(ChangeType.MODIFY, ad, abcd, 0);
- FileHeader fh = df.createFileHeader(mod);
+ FileHeader fh = df.toFileHeader(mod);
assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
assertEquals(FileHeader.PatchType.BINARY, fh.getPatchType());
@@ -218,7 +225,7 @@ public void testCreateFileHeader_GitLink() throws Exception {
DiffEntry mod = DiffEntry.pair(ChangeType.MODIFY, ad, abcd, 0);
- FileHeader fh = df.createFileHeader(mod);
+ FileHeader fh = df.toFileHeader(mod);
assertEquals(diffHeader, RawParseUtils.decode(fh.getBuffer()));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
index 91b7467..4b86f55 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
@@ -55,12 +55,28 @@ public DiffConfig parse(final Config cfg) {
}
};
+ private final boolean noPrefix;
+
+ private final boolean renames;
+
private final int renameLimit;
private DiffConfig(final Config rc) {
+ noPrefix = rc.getBoolean("diff", "noprefix", false);
+ renames = rc.getBoolean("diff", "renames", false);
renameLimit = rc.getInt("diff", "renamelimit", 200);
}
+ /** @return true if the prefix "a/" and "b/" should be suppressed. */
+ public boolean isNoPrefix() {
+ return noPrefix;
+ }
+
+ /** @return true if rename detection is enabled by default. */
+ public boolean isRenameDetectionEnabled() {
+ return renames;
+ }
+
/** @return limit on number of paths to perform inexact rename detection. */
public int getRenameLimit() {
return renameLimit;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
index cb145e4..3590ef5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -45,6 +45,7 @@
package org.eclipse.jgit.diff;
import static org.eclipse.jgit.diff.DiffEntry.ChangeType.ADD;
+import static org.eclipse.jgit.diff.DiffEntry.ChangeType.COPY;
import static org.eclipse.jgit.diff.DiffEntry.ChangeType.DELETE;
import static org.eclipse.jgit.diff.DiffEntry.ChangeType.MODIFY;
import static org.eclipse.jgit.diff.DiffEntry.ChangeType.RENAME;
@@ -56,6 +57,7 @@
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import org.eclipse.jgit.JGitText;
@@ -65,16 +67,26 @@
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.patch.HunkHeader;
import org.eclipse.jgit.patch.FileHeader.PatchType;
+import org.eclipse.jgit.revwalk.FollowFilter;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.pack.PackConfig;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.QuotedString;
import org.eclipse.jgit.util.io.DisabledOutputStream;
@@ -96,6 +108,8 @@ public class DiffFormatter {
private Repository db;
+ private ObjectReader reader;
+
private int context = 3;
private int abbreviationLength = 7;
@@ -108,6 +122,12 @@ public class DiffFormatter {
private String newPrefix = "b/";
+ private TreeFilter pathFilter = TreeFilter.ALL;
+
+ private RenameDetector renameDetector;
+
+ private ProgressMonitor progressMonitor;
+
/**
* Create a new formatter with a default level of context.
*
@@ -128,11 +148,25 @@ protected OutputStream getOutputStream() {
/**
* Set the repository the formatter can load object contents from.
*
+ * Once a repository has been set, the formatter must be released to ensure
+ * the internal ObjectReader is able to release its resources.
+ *
* @param repository
* source repository holding referenced objects.
*/
public void setRepository(Repository repository) {
+ if (reader != null)
+ reader.release();
+
db = repository;
+ reader = db.newObjectReader();
+
+ DiffConfig dc = db.getConfig().get(DiffConfig.KEY);
+ if (dc.isNoPrefix()) {
+ setOldPrefix("");
+ setNewPrefix("");
+ }
+ setDetectRenames(dc.isRenameDetectionEnabled());
}
/**
@@ -220,6 +254,64 @@ public void setNewPrefix(String prefix) {
newPrefix = prefix;
}
+ /** @return true if rename detection is enabled. */
+ public boolean isDetectRenames() {
+ return renameDetector != null;
+ }
+
+ /**
+ * Enable or disable rename detection.
+ *
+ * Before enabling rename detection the repository must be set with
+ * {@link #setRepository(Repository)}. Once enabled the detector can be
+ * configured away from its defaults by obtaining the instance directly from
+ * {@link #getRenameDetector()} and invoking configuration.
+ *
+ * @param on
+ * if rename detection should be enabled.
+ */
+ public void setDetectRenames(boolean on) {
+ if (on && renameDetector == null) {
+ assertHaveRepository();
+ renameDetector = new RenameDetector(db);
+ } else if (!on)
+ renameDetector = null;
+ }
+
+ /** @return the rename detector if rename detection is enabled. */
+ public RenameDetector getRenameDetector() {
+ return renameDetector;
+ }
+
+ /**
+ * Set the progress monitor for long running rename detection.
+ *
+ * @param pm
+ * progress monitor to receive rename detection status through.
+ */
+ public void setProgressMonitor(ProgressMonitor pm) {
+ progressMonitor = pm;
+ }
+
+ /**
+ * Set the filter to produce only specific paths.
+ *
+ * If the filter is an instance of {@link FollowFilter}, the filter path
+ * will be updated during successive scan or format invocations. The updated
+ * path can be obtained from {@link #getPathFilter()}.
+ *
+ * @param filter
+ * the tree filter to apply.
+ */
+ public void setPathFilter(TreeFilter filter) {
+ pathFilter = filter != null ? filter : TreeFilter.ALL;
+ }
+
+ /** @return the current path filter. */
+ public TreeFilter getPathFilter() {
+ return pathFilter;
+ }
+
/**
* Flush the underlying output stream of this formatter.
*
@@ -230,6 +322,208 @@ public void flush() throws IOException {
out.flush();
}
+ /** Release the internal ObjectReader state. */
+ public void release() {
+ if (reader != null)
+ reader.release();
+ }
+
+ /**
+ * Determine the differences between two trees.
+ *
+ * No output is created, instead only the file paths that are different are
+ * returned. Callers may choose to format these paths themselves, or convert
+ * them into {@link FileHeader} instances with a complete edit list by
+ * calling {@link #toFileHeader(DiffEntry)}.
+ *
+ * @param a
+ * the old (or previous) side.
+ * @param b
+ * the new (or updated) side.
+ * @return the paths that are different.
+ * @throws IOException
+ * trees cannot be read or file contents cannot be read.
+ */
+ public List<DiffEntry> scan(AnyObjectId a, AnyObjectId b)
+ throws IOException {
+ assertHaveRepository();
+
+ RevWalk rw = new RevWalk(reader);
+ return scan(rw.parseTree(a), rw.parseTree(b));
+ }
+
+ /**
+ * Determine the differences between two trees.
+ *
+ * No output is created, instead only the file paths that are different are
+ * returned. Callers may choose to format these paths themselves, or convert
+ * them into {@link FileHeader} instances with a complete edit list by
+ * calling {@link #toFileHeader(DiffEntry)}.
+ *
+ * @param a
+ * the old (or previous) side.
+ * @param b
+ * the new (or updated) side.
+ * @return the paths that are different.
+ * @throws IOException
+ * trees cannot be read or file contents cannot be read.
+ */
+ public List<DiffEntry> scan(RevTree a, RevTree b) throws IOException {
+ assertHaveRepository();
+
+ CanonicalTreeParser aParser = new CanonicalTreeParser();
+ CanonicalTreeParser bParser = new CanonicalTreeParser();
+
+ aParser.reset(reader, a);
+ bParser.reset(reader, b);
+
+ return scan(aParser, bParser);
+ }
+
+ /**
+ * Determine the differences between two trees.
+ *
+ * No output is created, instead only the file paths that are different are
+ * returned. Callers may choose to format these paths themselves, or convert
+ * them into {@link FileHeader} instances with a complete edit list by
+ * calling {@link #toFileHeader(DiffEntry)}.
+ *
+ * @param a
+ * the old (or previous) side.
+ * @param b
+ * the new (or updated) side.
+ * @return the paths that are different.
+ * @throws IOException
+ * trees cannot be read or file contents cannot be read.
+ */
+ public List<DiffEntry> scan(AbstractTreeIterator a, AbstractTreeIterator b)
+ throws IOException {
+ assertHaveRepository();
+
+ TreeWalk walk = new TreeWalk(reader);
+ walk.reset();
+ walk.addTree(a);
+ walk.addTree(b);
+ walk.setRecursive(true);
+
+ if (pathFilter == TreeFilter.ALL) {
+ walk.setFilter(TreeFilter.ANY_DIFF);
+ } else if (pathFilter instanceof FollowFilter) {
+ walk.setFilter(pathFilter);
+ } else {
+ walk.setFilter(AndTreeFilter
+ .create(pathFilter, TreeFilter.ANY_DIFF));
+ }
+
+ List<DiffEntry> files = DiffEntry.scan(walk);
+ if (pathFilter instanceof FollowFilter && isAdd(files)) {
+ // The file we are following was added here, find where it
+ // came from so we can properly show the rename or copy,
+ // then continue digging backwards.
+ //
+ a.reset();
+ b.reset();
+ walk.reset();
+ walk.addTree(a);
+ walk.addTree(b);
+ walk.setFilter(TreeFilter.ANY_DIFF);
+
+ if (renameDetector == null)
+ setDetectRenames(true);
+ files = updateFollowFilter(detectRenames(DiffEntry.scan(walk)));
+
+ } else if (renameDetector != null)
+ files = detectRenames(files);
+
+ return files;
+ }
+
+ private List<DiffEntry> detectRenames(List<DiffEntry> files)
+ throws IOException {
+ renameDetector.reset();
+ renameDetector.addAll(files);
+ return renameDetector.compute(reader, progressMonitor);
+ }
+
+ private boolean isAdd(List<DiffEntry> files) {
+ String oldPath = ((FollowFilter) pathFilter).getPath();
+ for (DiffEntry ent : files) {
+ if (ent.getChangeType() == ADD && ent.getNewPath().equals(oldPath))
+ return true;
+ }
+ return false;
+ }
+
+ private List<DiffEntry> updateFollowFilter(List<DiffEntry> files) {
+ String oldPath = ((FollowFilter) pathFilter).getPath();
+ for (DiffEntry ent : files) {
+ if (isRename(ent) && ent.getNewPath().equals(oldPath)) {
+ pathFilter = FollowFilter.create(ent.getOldPath());
+ return Collections.singletonList(ent);
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ private static boolean isRename(DiffEntry ent) {
+ return ent.getChangeType() == RENAME || ent.getChangeType() == COPY;
+ }
+
+ /**
+ * Format the differences between two trees.
+ *
+ * The patch is expressed as instructions to modify {@code a} to make it
+ * {@code b}.
+ *
+ * @param a
+ * the old (or previous) side.
+ * @param b
+ * the new (or updated) side.
+ * @throws IOException
+ * trees cannot be read, file contents cannot be read, or the
+ * patch cannot be output.
+ */
+ public void format(AnyObjectId a, AnyObjectId b) throws IOException {
+ format(scan(a, b));
+ }
+
+ /**
+ * Format the differences between two trees.
+ *
+ * The patch is expressed as instructions to modify {@code a} to make it
+ * {@code b}.
+ *
+ * @param a
+ * the old (or previous) side.
+ * @param b
+ * the new (or updated) side.
+ * @throws IOException
+ * trees cannot be read, file contents cannot be read, or the
+ * patch cannot be output.
+ */
+ public void format(RevTree a, RevTree b) throws IOException {
+ format(scan(a, b));
+ }
+
+ /**
+ * Format the differences between two trees.
+ *
+ * The patch is expressed as instructions to modify {@code a} to make it
+ * {@code b}.
+ *
+ * @param a
+ * the old (or previous) side.
+ * @param b
+ * the new (or updated) side.
+ * @throws IOException
+ * trees cannot be read, file contents cannot be read, or the
+ * patch cannot be output.
+ */
+ public void format(AbstractTreeIterator a, AbstractTreeIterator b)
+ throws IOException {
+ format(scan(a, b));
+ }
+
/**
* Format a patch script from a list of difference entries.
*
@@ -272,13 +566,10 @@ private void writeGitLinkDiffText(OutputStream o, DiffEntry ent)
private String format(AbbreviatedObjectId id) {
if (id.isComplete() && db != null) {
- ObjectReader reader = db.newObjectReader();
try {
id = reader.abbreviate(id.toObjectId(), abbreviationLength);
} catch (IOException cannotAbbreviate) {
// Ignore this. We'll report the full identity.
- } finally {
- reader.release();
}
}
return id.name();
@@ -319,22 +610,22 @@ public void format(final FileHeader head, final RawText a, final RawText b)
end = head.getHunks().get(0).getStartOffset();
out.write(head.getBuffer(), start, end - start);
if (head.getPatchType() == PatchType.UNIFIED)
- formatEdits(a, b, head.toEditList());
+ format(head.toEditList(), a, b);
}
/**
* Formats a list of edits in unified diff format
*
+ * @param edits
+ * some differences which have been calculated between A and B
* @param a
* the text A which was compared
* @param b
* the text B which was compared
- * @param edits
- * some differences which have been calculated between A and B
* @throws IOException
*/
- public void formatEdits(final RawText a, final RawText b,
- final EditList edits) throws IOException {
+ public void format(final EditList edits, final RawText a, final RawText b)
+ throws IOException {
for (int curIdx = 0; curIdx < edits.size();) {
Edit curEdit = edits.get(curIdx);
final int endIdx = findCombinedEnd(edits, curIdx);
@@ -513,7 +804,7 @@ protected void writeLine(final char prefix, final RawText text,
* @throws MissingObjectException
* one of the blobs referenced by the DiffEntry is missing.
*/
- public FileHeader createFileHeader(DiffEntry ent) throws IOException,
+ public FileHeader toFileHeader(DiffEntry ent) throws IOException,
CorruptObjectException, MissingObjectException {
return createFormatResult(ent).header;
}
@@ -542,24 +833,14 @@ private FormatResult createFormatResult(DiffEntry ent) throws IOException,
type = PatchType.UNIFIED;
} else {
- if (db == null)
- throw new IllegalStateException(
- JGitText.get().repositoryIsRequired);
+ assertHaveRepository();
- ObjectReader reader = db.newObjectReader();
- byte[] aRaw, bRaw;
- try {
- aRaw = open(reader, //
- ent.getOldPath(), //
- ent.getOldMode(), //
- ent.getOldId());
- bRaw = open(reader, //
- ent.getNewPath(), //
- ent.getNewMode(), //
- ent.getNewId());
- } finally {
- reader.release();
- }
+ byte[] aRaw = open(ent.getOldPath(), //
+ ent.getOldMode(), //
+ ent.getOldId());
+ byte[] bRaw = open(ent.getNewPath(), //
+ ent.getNewMode(), //
+ ent.getNewId());
if (aRaw == BINARY || bRaw == BINARY //
|| RawText.isBinary(aRaw) || RawText.isBinary(bRaw)) {
@@ -592,8 +873,13 @@ private FormatResult createFormatResult(DiffEntry ent) throws IOException,
return res;
}
- private byte[] open(ObjectReader reader, String path, FileMode mode,
- AbbreviatedObjectId id) throws IOException {
+ private void assertHaveRepository() {
+ if (db == null)
+ throw new IllegalStateException(JGitText.get().repositoryIsRequired);
+ }
+
+ private byte[] open(String path, FileMode mode, AbbreviatedObjectId id)
+ throws IOException {
if (mode == FileMode.MISSING)
return EMPTY;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
index 9c1310a..bd4a5e2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
@@ -100,11 +100,11 @@ private int sortOf(ChangeType changeType) {
}
};
- private List<DiffEntry> entries = new ArrayList<DiffEntry>();
+ private List<DiffEntry> entries;
- private List<DiffEntry> deleted = new ArrayList<DiffEntry>();
+ private List<DiffEntry> deleted;
- private List<DiffEntry> added = new ArrayList<DiffEntry>();
+ private List<DiffEntry> added;
private boolean done;
@@ -137,6 +137,8 @@ public RenameDetector(Repository repo) {
DiffConfig cfg = repo.getConfig().get(DiffConfig.KEY);
renameLimit = cfg.getRenameLimit();
+
+ reset();
}
/**
@@ -305,19 +307,39 @@ public List<DiffEntry> compute() throws IOException {
*/
public List<DiffEntry> compute(ProgressMonitor pm) throws IOException {
if (!done) {
+ ObjectReader reader = repo.newObjectReader();
+ try {
+ return compute(reader, pm);
+ } finally {
+ reader.release();
+ }
+ }
+ return Collections.unmodifiableList(entries);
+ }
+
+ /**
+ * Detect renames in the current file set.
+ *
+ * @param reader
+ * reader to obtain objects from the repository with.
+ * @param pm
+ * report progress during the detection phases.
+ * @return an unmodifiable list of {@link DiffEntry}s representing all files
+ * that have been changed.
+ * @throws IOException
+ * file contents cannot be read from the repository.
+ */
+ public List<DiffEntry> compute(ObjectReader reader, ProgressMonitor pm)
+ throws IOException {
+ if (!done) {
done = true;
if (pm == null)
pm = NullProgressMonitor.INSTANCE;
- ObjectReader reader = repo.newObjectReader();
- try {
breakModifies(reader, pm);
findExactRenames(pm);
findContentRenames(reader, pm);
rejoinModifies(pm);
- } finally {
- reader.release();
- }
entries.addAll(added);
added = null;
@@ -330,6 +352,14 @@ public List<DiffEntry> compute(ProgressMonitor pm) throws IOException {
return Collections.unmodifiableList(entries);
}
+ /** Reset this rename detector for another rename detection pass. */
+ public void reset() {
+ entries = new ArrayList<DiffEntry>();
+ deleted = new ArrayList<DiffEntry>();
+ added = new ArrayList<DiffEntry>();
+ done = false;
+ }
+
private void breakModifies(ObjectReader reader, ProgressMonitor pm)
throws IOException {
if (breakScore <= 0)