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 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;
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<DiffEntry> 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);
}
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.getNewName().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.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;
+ }
}
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;
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<RevCommit> commits = new ArrayList<RevCommit>();
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));
--- /dev/null
+/*
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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() + ")";
+ }
+}
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;
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());
}
// 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) {
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<DiffEntry> 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;
+ }
}
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;
}
@Override
- public TreeFilter clone() {
+ public PathFilter clone() {
return this;
}