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>
This commit is contained in:
parent
0f5eae53d6
commit
ec2fdbf2ba
|
@ -168,6 +168,7 @@ usage_listCreateOrDeleteBranches=List, create, or delete branches
|
|||
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
|
||||
|
|
|
@ -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,17 +135,28 @@ 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);
|
||||
nameStatus(out, diffFmt.scan(oldTree, newTree));
|
||||
out.flush();
|
||||
|
||||
} else {
|
||||
diffFmt.setRepository(db);
|
||||
diffFmt.format(files);
|
||||
diffFmt.format(oldTree, newTree);
|
||||
diffFmt.flush();
|
||||
}
|
||||
} finally {
|
||||
diffFmt.release();
|
||||
}
|
||||
}
|
||||
|
||||
static void nameStatus(PrintWriter out, List<DiffEntry> files) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -155,6 +153,24 @@ protected RevWalk createWalk() {
|
|||
return ret;
|
||||
}
|
||||
|
||||
@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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(), //
|
||||
byte[] aRaw = open(ent.getOldPath(), //
|
||||
ent.getOldMode(), //
|
||||
ent.getOldId());
|
||||
bRaw = open(reader, //
|
||||
ent.getNewPath(), //
|
||||
byte[] bRaw = open(ent.getNewPath(), //
|
||||
ent.getNewMode(), //
|
||||
ent.getNewId());
|
||||
} finally {
|
||||
reader.release();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -304,20 +306,40 @@ public List<DiffEntry> compute() throws IOException {
|
|||
* file contents cannot be read from the repository.
|
||||
*/
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue