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 83ef6eb87..48a05915e 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,6 +51,7 @@ 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; @@ -65,11 +66,12 @@ 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.lib.TextProgressMonitor; +import org.eclipse.jgit.revwalk.FollowFilter; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; @@ -195,20 +197,26 @@ protected void show(final RevCommit c) throws Exception { private void showDiff(RevCommit c) throws IOException { final TreeWalk tw = new TreeWalk(db); - tw.reset(); tw.setRecursive(true); + tw.reset(); tw.addTree(c.getParent(0).getTree()); tw.addTree(c.getTree()); - tw.setFilter(AndTreeFilter.create(TreeFilter.ANY_DIFF, pathFilter)); + tw.setFilter(AndTreeFilter.create(pathFilter, TreeFilter.ANY_DIFF)); List files = DiffEntry.scan(tw); - if (detectRenames) { - RenameDetector rd = new RenameDetector(db); - if (renameLimit != null) - rd.setRenameLimit(renameLimit.intValue()); - rd.addAll(files); - files = rd.compute(new TextProgressMonitor()); - } + 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); @@ -220,4 +228,39 @@ private void showDiff(RevCommit c) throws IOException { } out.println(); } + + private List detectRenames(List 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 files) { + String oldPath = ((FollowFilter) pathFilter).getPath(); + for (DiffEntry ent : files) { + if (ent.getChangeType() == ChangeType.ADD + && ent.getNewName().equals(oldPath)) + return true; + } + return false; + } + + private List updateFollowFilter(List files) { + String oldPath = ((FollowFilter) pathFilter).getPath(); + for (DiffEntry ent : files) { + if (isRename(ent) && ent.getNewName().equals(oldPath)) { + pathFilter = FollowFilter.create(ent.getOldName()); + return Collections.singletonList(ent); + } + } + return Collections.emptyList(); + } + + private static boolean isRename(DiffEntry ent) { + return ent.getChangeType() == ChangeType.RENAME + || ent.getChangeType() == ChangeType.COPY; + } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java index beb961d99..bf3924b70 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java @@ -53,6 +53,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler; +import org.eclipse.jgit.revwalk.FollowFilter; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; @@ -110,6 +111,11 @@ void enableBoundary(final boolean on) { enableRevSort(RevSort.BOUNDARY, on); } + @Option(name = "--follow", metaVar = "metaVar_path") + void follow(final String path) { + pathFilter = FollowFilter.create(path); + } + @Argument(index = 0, metaVar = "metaVar_commitish") private final List commits = new ArrayList(); @@ -139,7 +145,9 @@ protected void run() throws Exception { for (final RevSort s : sorting) walk.sort(s, true); - if (pathFilter != TreeFilter.ALL) + if (pathFilter instanceof FollowFilter) + walk.setTreeFilter(pathFilter); + else if (pathFilter != TreeFilter.ALL) walk.setTreeFilter(AndTreeFilter.create(pathFilter, TreeFilter.ANY_DIFF)); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FollowFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FollowFilter.java new file mode 100644 index 000000000..953620617 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FollowFilter.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2010, Google Inc. + * 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 org.eclipse.jgit.revwalk; + +import java.io.IOException; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +/** + * Updates the internal path filter to follow copy/renames. + *

+ * This is a special filter that performs {@code AND(path, ANY_DIFF)}, but also + * triggers rename detection so that the path node is updated to include a prior + * file name as the RevWalk traverses history. + *

+ * Results with this filter are unpredictable if the path being followed is a + * subdirectory. + */ +public class FollowFilter extends TreeFilter { + /** + * Create a new tree filter for a user supplied path. + *

+ * Path strings are relative to the root of the repository. If the user's + * input should be assumed relative to a subdirectory of the repository the + * caller must prepend the subdirectory's path prior to creating the filter. + *

+ * Path strings use '/' to delimit directories on all platforms. + * + * @param path + * the path to filter on. Must not be the empty string. All + * trailing '/' characters will be trimmed before string's length + * is checked or is used as part of the constructed filter. + * @return a new filter for the requested path. + * @throws IllegalArgumentException + * the path supplied was the empty string. + */ + public static FollowFilter create(String path) { + return new FollowFilter(PathFilter.create(path)); + } + + private final PathFilter path; + + FollowFilter(final PathFilter path) { + this.path = path; + } + + /** @return the path this filter matches. */ + public String getPath() { + return path.getPath(); + } + + @Override + public boolean include(final TreeWalk walker) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + return path.include(walker) && ANY_DIFF.include(walker); + } + + @Override + public boolean shouldBeRecursive() { + return path.shouldBeRecursive() || ANY_DIFF.shouldBeRecursive(); + } + + @Override + public TreeFilter clone() { + return new FollowFilter(path.clone()); + } + + @Override + public String toString() { + return "(FOLLOW(" + path.toString() + ")" // + + " AND " // + + ANY_DIFF.toString() + ")"; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java index d7e5c8028..4c5a2a77e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java @@ -44,11 +44,17 @@ package org.eclipse.jgit.revwalk; import java.io.IOException; +import java.util.List; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.diff.RenameDetector; +import org.eclipse.jgit.diff.DiffEntry.ChangeType; +import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.StopWalkException; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.TreeFilter; @@ -76,8 +82,11 @@ class RewriteTreeFilter extends RevFilter { private final TreeWalk pathFilter; + private final Repository repo; + RewriteTreeFilter(final RevWalk walker, final TreeFilter t) { - pathFilter = new TreeWalk(walker.db); + repo = walker.db; + pathFilter = new TreeWalk(repo); pathFilter.setFilter(t); pathFilter.setRecursive(t.shouldBeRecursive()); } @@ -128,6 +137,13 @@ public boolean include(final RevWalk walker, final RevCommit c) // We have interesting items, but neither of the special // cases denoted above. // + if (adds > 0 && tw.getFilter() instanceof FollowFilter) { + // One of the paths we care about was added in this + // commit. We need to update our filter to its older + // name, if we can discover it. Find out what that is. + // + updateFollowFilter(trees); + } return true; } } else if (nParents == 0) { @@ -213,4 +229,32 @@ public boolean include(final RevWalk walker, final RevCommit c) c.flags |= REWRITE; return false; } + + private void updateFollowFilter(ObjectId[] trees) + throws MissingObjectException, IncorrectObjectTypeException, + CorruptObjectException, IOException { + TreeWalk tw = pathFilter; + FollowFilter oldFilter = (FollowFilter) tw.getFilter(); + tw.setFilter(TreeFilter.ANY_DIFF); + tw.reset(trees); + + List files = DiffEntry.scan(tw); + RenameDetector rd = new RenameDetector(repo); + rd.addAll(files); + files = rd.compute(); + + TreeFilter newFilter = oldFilter; + for (DiffEntry ent : files) { + if (isRename(ent) && ent.getNewName().equals(oldFilter.getPath())) { + newFilter = FollowFilter.create(ent.getOldName()); + break; + } + } + tw.setFilter(newFilter); + } + + private static boolean isRename(DiffEntry ent) { + return ent.getChangeType() == ChangeType.RENAME + || ent.getChangeType() == ChangeType.COPY; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java index e31778984..3f83114fd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java @@ -90,6 +90,11 @@ private PathFilter(final String s) { pathRaw = Constants.encode(pathStr); } + /** @return the path this filter matches. */ + public String getPath() { + return pathStr; + } + @Override public boolean include(final TreeWalk walker) { return walker.isPathPrefix(pathRaw, pathRaw.length) == 0; @@ -104,7 +109,7 @@ public boolean shouldBeRecursive() { } @Override - public TreeFilter clone() { + public PathFilter clone() { return this; }