Merge "Revert "Adds FilteredRevCommit that can overwrites its parents in the DAG.""
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java
index f807889..204c89d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java
@@ -301,4 +301,25 @@ public void testFilesShouldBeCleanedInSubSubFolders()
 		writeTrashFile("this_is/not_ok/more/subdirs/file.txt", "2");
 		git.clean().setCleanDirectories(true).setIgnore(false).call();
 	}
+
+	@Test
+	public void testPrefix() throws Exception {
+		File a = writeTrashFile("a.txt", "a");
+		File b = writeTrashFile("a/a.txt", "sub a");
+		File dir = b.getParentFile();
+		git.clean().call();
+		assertFalse(a.exists());
+		assertTrue(dir.exists());
+		assertTrue(b.exists());
+	}
+
+	@Test
+	public void testPrefixWithDir() throws Exception {
+		File a = writeTrashFile("a.txt", "a");
+		File b = writeTrashFile("a/a.txt", "sub a");
+		File dir = b.getParentFile();
+		git.clean().setCleanDirectories(true).call();
+		assertFalse(a.exists());
+		assertFalse(dir.exists());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java
index f47f447..b175ead 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java
@@ -13,50 +13,88 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import java.util.Iterator;
+
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.blame.BlameGenerator;
 import org.eclipse.jgit.blame.BlameResult;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.revwalk.FilteredRevCommit;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
 import org.junit.Test;
 
 /** Unit tests of {@link BlameGenerator}. */
 public class BlameGeneratorTest extends RepositoryTestCase {
+
+	public static final String OTHER_FILE = "other_file.txt";
+
+	public static final String INTERESTING_FILE = "interesting_file.txt";
+
 	@Test
-	public void testBoundLineDelete() throws Exception {
-		try (Git git = new Git(db)) {
-			String[] content1 = new String[] { "first", "second" };
-			writeTrashFile("file.txt", join(content1));
-			git.add().addFilepattern("file.txt").call();
+	public void testSingleBlame() throws Exception {
+
+		/**
+		 * <pre>
+		 * (ts) 	OTHER_FILE			INTERESTING_FILE
+		 * 1 		a
+		 * 2	 	a, b
+		 * 3							1, 2				c1 <--
+		 * 4	 	a, b, c										 |
+		 * 5							1, 2, 3				c2---
+		 * </pre>
+		 */
+		try (Git git = new Git(db);
+				RevWalk revWalk = new RevWalk(git.getRepository())) {
+			writeTrashFile(OTHER_FILE, join("a"));
+			git.add().addFilepattern(OTHER_FILE).call();
+			git.commit().setMessage("create file").call();
+
+			writeTrashFile(OTHER_FILE, join("a", "b"));
+			git.add().addFilepattern(OTHER_FILE).call();
+			git.commit().setMessage("amend file").call();
+
+			writeTrashFile(INTERESTING_FILE, join("1", "2"));
+			git.add().addFilepattern(INTERESTING_FILE).call();
 			RevCommit c1 = git.commit().setMessage("create file").call();
 
-			String[] content2 = new String[] { "third", "first", "second" };
-			writeTrashFile("file.txt", join(content2));
-			git.add().addFilepattern("file.txt").call();
-			RevCommit c2 = git.commit().setMessage("create file").call();
+			writeTrashFile(OTHER_FILE, join("a", "b", "c"));
+			git.add().addFilepattern(OTHER_FILE).call();
+			git.commit().setMessage("amend file").call();
 
-			try (BlameGenerator generator = new BlameGenerator(db, "file.txt")) {
-				generator.push(null, db.resolve(Constants.HEAD));
+			writeTrashFile(INTERESTING_FILE, join("1", "2", "3"));
+			git.add().addFilepattern(INTERESTING_FILE).call();
+			RevCommit c2 = git.commit().setMessage("amend file").call();
+
+			RevCommit filteredC1 = new FilteredRevCommit(c1);
+			RevCommit filteredC2 = new FilteredRevCommit(c2, filteredC1);
+
+			revWalk.parseHeaders(filteredC2);
+
+			try (BlameGenerator generator = new BlameGenerator(db,
+					INTERESTING_FILE)) {
+				generator.push(filteredC2);
 				assertEquals(3, generator.getResultContents().size());
 
 				assertTrue(generator.next());
 				assertEquals(c2, generator.getSourceCommit());
 				assertEquals(1, generator.getRegionLength());
-				assertEquals(0, generator.getResultStart());
-				assertEquals(1, generator.getResultEnd());
-				assertEquals(0, generator.getSourceStart());
-				assertEquals(1, generator.getSourceEnd());
-				assertEquals("file.txt", generator.getSourcePath());
+				assertEquals(2, generator.getResultStart());
+				assertEquals(3, generator.getResultEnd());
+				assertEquals(2, generator.getSourceStart());
+				assertEquals(3, generator.getSourceEnd());
+				assertEquals(INTERESTING_FILE, generator.getSourcePath());
 
 				assertTrue(generator.next());
 				assertEquals(c1, generator.getSourceCommit());
 				assertEquals(2, generator.getRegionLength());
-				assertEquals(1, generator.getResultStart());
-				assertEquals(3, generator.getResultEnd());
+				assertEquals(0, generator.getResultStart());
+				assertEquals(2, generator.getResultEnd());
 				assertEquals(0, generator.getSourceStart());
 				assertEquals(2, generator.getSourceEnd());
-				assertEquals("file.txt", generator.getSourcePath());
+				assertEquals(INTERESTING_FILE, generator.getSourcePath());
 
 				assertFalse(generator.next());
 			}
@@ -64,6 +102,242 @@ public void testBoundLineDelete() throws Exception {
 	}
 
 	@Test
+	public void testMergeSingleBlame() throws Exception {
+		try (Git git = new Git(db);
+				RevWalk revWalk = new RevWalk(git.getRepository())) {
+
+			/**
+			 *
+			 *
+			 * <pre>
+			 *  refs/heads/master
+			 *      A
+			 *     / \       		 refs/heads/side
+			 *    /   ---------------->  side
+			 *   /                        |
+			 *  merge <-------------------
+			 * </pre>
+			 */
+
+			writeTrashFile(INTERESTING_FILE, join("1", "2"));
+			git.add().addFilepattern(INTERESTING_FILE).call();
+			RevCommit c1 = git.commit().setMessage("create file").call();
+
+			createBranch(c1, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+			writeTrashFile(INTERESTING_FILE, join("1", "2", "3", "4"));
+			git.add().addFilepattern(INTERESTING_FILE).call();
+			RevCommit sideCommit = git.commit()
+					.setMessage("amend file in another branch").call();
+
+			checkoutBranch("refs/heads/master");
+			git.merge().setMessage("merge").include(sideCommit)
+					.setStrategy(MergeStrategy.RESOLVE).call();
+
+			Iterator<RevCommit> it = git.log().call().iterator();
+			RevCommit mergeCommit = it.next();
+
+			RevCommit filteredC1 = new FilteredRevCommit(c1);
+			RevCommit filteredSide = new FilteredRevCommit(sideCommit,
+					filteredC1);
+			RevCommit filteredMerge = new FilteredRevCommit(mergeCommit,
+					filteredSide, filteredC1);
+
+			revWalk.parseHeaders(filteredMerge);
+
+			try (BlameGenerator generator = new BlameGenerator(db,
+					INTERESTING_FILE)) {
+				generator.push(filteredMerge);
+				assertEquals(4, generator.getResultContents().size());
+
+				assertTrue(generator.next());
+				assertEquals(mergeCommit, generator.getSourceCommit());
+				assertEquals(2, generator.getRegionLength());
+				assertEquals(2, generator.getResultStart());
+				assertEquals(4, generator.getResultEnd());
+				assertEquals(2, generator.getSourceStart());
+				assertEquals(4, generator.getSourceEnd());
+				assertEquals(INTERESTING_FILE, generator.getSourcePath());
+
+				assertTrue(generator.next());
+				assertEquals(filteredC1, generator.getSourceCommit());
+				assertEquals(2, generator.getRegionLength());
+				assertEquals(0, generator.getResultStart());
+				assertEquals(2, generator.getResultEnd());
+				assertEquals(0, generator.getSourceStart());
+				assertEquals(2, generator.getSourceEnd());
+				assertEquals(INTERESTING_FILE, generator.getSourcePath());
+
+				assertFalse(generator.next());
+			}
+		}
+	}
+
+	@Test
+	public void testMergeBlame() throws Exception {
+		try (Git git = new Git(db);
+				RevWalk revWalk = new RevWalk(git.getRepository())) {
+
+			/**
+			 *
+			 *
+			 * <pre>
+			 *  refs/heads/master
+			 *      A
+			 *     / \       		 refs/heads/side
+			 *    B   ---------------->  side
+			 *   /                        |
+			 *  merge <-------------------
+			 * </pre>
+			 */
+			writeTrashFile(INTERESTING_FILE, join("1", "2"));
+			git.add().addFilepattern(INTERESTING_FILE).call();
+			RevCommit c1 = git.commit().setMessage("create file").call();
+
+			createBranch(c1, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+			writeTrashFile(INTERESTING_FILE, join("1", "2", "3"));
+			git.add().addFilepattern(INTERESTING_FILE).call();
+			RevCommit sideCommit = git.commit().setMessage("amend file").call();
+
+			checkoutBranch("refs/heads/master");
+			writeTrashFile(INTERESTING_FILE, join("1", "2", "4"));
+			git.add().addFilepattern(INTERESTING_FILE).call();
+			RevCommit c2 = git.commit().setMessage("delete and amend file")
+					.call();
+
+			git.merge().setMessage("merge").include(sideCommit)
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			writeTrashFile(INTERESTING_FILE, join("1", "2", "3", "4"));
+			git.add().addFilepattern(INTERESTING_FILE).call();
+			RevCommit mergeCommit = git.commit().setMessage("merge commit")
+					.call();
+
+			RevCommit filteredC1 = new FilteredRevCommit(c1);
+			RevCommit filteredSide = new FilteredRevCommit(sideCommit,
+					filteredC1);
+			RevCommit filteredC2 = new FilteredRevCommit(c2, filteredC1);
+
+			RevCommit filteredMerge = new FilteredRevCommit(mergeCommit,
+					filteredSide, filteredC2);
+
+			revWalk.parseHeaders(filteredMerge);
+
+			try (BlameGenerator generator = new BlameGenerator(db,
+					INTERESTING_FILE)) {
+				generator.push(filteredMerge);
+				assertEquals(4, generator.getResultContents().size());
+
+				assertTrue(generator.next());
+				assertEquals(filteredC2, generator.getSourceCommit());
+				assertEquals(1, generator.getRegionLength());
+				assertEquals(3, generator.getResultStart());
+				assertEquals(4, generator.getResultEnd());
+				assertEquals(2, generator.getSourceStart());
+				assertEquals(3, generator.getSourceEnd());
+				assertEquals(INTERESTING_FILE, generator.getSourcePath());
+
+				assertTrue(generator.next());
+				assertEquals(filteredSide, generator.getSourceCommit());
+				assertEquals(1, generator.getRegionLength());
+				assertEquals(2, generator.getResultStart());
+				assertEquals(3, generator.getResultEnd());
+				assertEquals(2, generator.getSourceStart());
+				assertEquals(3, generator.getSourceEnd());
+				assertEquals(INTERESTING_FILE, generator.getSourcePath());
+
+				assertTrue(generator.next());
+				assertEquals(filteredC1, generator.getSourceCommit());
+				assertEquals(2, generator.getRegionLength());
+				assertEquals(0, generator.getResultStart());
+				assertEquals(2, generator.getResultEnd());
+				assertEquals(0, generator.getSourceStart());
+				assertEquals(2, generator.getSourceEnd());
+				assertEquals(INTERESTING_FILE, generator.getSourcePath());
+
+				assertFalse(generator.next());
+			}
+		}
+	}
+
+	@Test
+	public void testSingleBlame_compareWithWalk() throws Exception {
+		/**
+		 * <pre>
+		 * (ts) 	OTHER_FILE			INTERESTING_FILE
+		 * 1 		a
+		 * 2	 	a, b
+		 * 3							1, 2				c1 <--
+		 * 4	 	a, b, c										 |
+		 * 6							3, 1, 2				c2---
+		 * </pre>
+		 */
+		try (Git git = new Git(db);
+				RevWalk revWalk = new RevWalk(git.getRepository())) {
+			writeTrashFile(OTHER_FILE, join("a"));
+			git.add().addFilepattern(OTHER_FILE).call();
+			git.commit().setMessage("create file").call();
+
+			writeTrashFile(OTHER_FILE, join("a", "b"));
+			git.add().addFilepattern(OTHER_FILE).call();
+			git.commit().setMessage("amend file").call();
+
+			writeTrashFile(INTERESTING_FILE, join("1", "2"));
+			git.add().addFilepattern(INTERESTING_FILE).call();
+			RevCommit c1 = git.commit().setMessage("create file").call();
+
+			writeTrashFile(OTHER_FILE, join("a", "b", "c"));
+			git.add().addFilepattern(OTHER_FILE).call();
+			git.commit().setMessage("amend file").call();
+
+			writeTrashFile(INTERESTING_FILE, join("3", "1", "2"));
+			git.add().addFilepattern(INTERESTING_FILE).call();
+			RevCommit c2 = git.commit().setMessage("prepend").call();
+
+			RevCommit filteredC1 = new FilteredRevCommit(c1);
+			RevCommit filteredC2 = new FilteredRevCommit(c2, filteredC1);
+
+			revWalk.parseHeaders(filteredC2);
+
+			try (BlameGenerator g1 = new BlameGenerator(db, INTERESTING_FILE);
+					BlameGenerator g2 = new BlameGenerator(db,
+							INTERESTING_FILE)) {
+				g1.push(null, c2);
+				g2.push(null, filteredC2);
+
+				assertEquals(g1.getResultContents().size(),
+						g2.getResultContents().size()); // 3
+
+				assertTrue(g1.next());
+				assertTrue(g2.next());
+
+				assertEquals(g1.getSourceCommit(), g2.getSourceCommit()); // c2
+				assertEquals(INTERESTING_FILE, g1.getSourcePath());
+				assertEquals(g1.getRegionLength(), g2.getRegionLength()); // 1
+				assertEquals(g1.getResultStart(), g2.getResultStart()); // 0
+				assertEquals(g1.getResultEnd(), g2.getResultEnd()); // 1
+				assertEquals(g1.getSourceStart(), g2.getSourceStart()); // 0
+				assertEquals(g1.getSourceEnd(), g2.getSourceEnd()); // 1
+				assertEquals(g1.getSourcePath(), g2.getSourcePath()); // INTERESTING_FILE
+
+				assertTrue(g1.next());
+				assertTrue(g2.next());
+
+				assertEquals(g1.getSourceCommit(), g2.getSourceCommit()); // c1
+				assertEquals(g1.getRegionLength(), g2.getRegionLength()); // 2
+				assertEquals(g1.getResultStart(), g2.getResultStart()); // 1
+				assertEquals(g1.getResultEnd(), g2.getResultEnd()); // 3
+				assertEquals(g1.getSourceStart(), g2.getSourceStart()); // 0
+				assertEquals(g1.getSourceEnd(), g2.getSourceEnd()); // 2
+				assertEquals(g1.getSourcePath(), g2.getSourcePath()); // INTERESTING_FILE
+
+				assertFalse(g1.next());
+				assertFalse(g2.next());
+			}
+		}
+	}
+
+	@Test
 	public void testRenamedBoundLineDelete() throws Exception {
 		try (Git git = new Git(db)) {
 			final String FILENAME_1 = "subdir/file1.txt";
@@ -87,7 +361,8 @@ public void testRenamedBoundLineDelete() throws Exception {
 			git.add().addFilepattern(FILENAME_2).call();
 			RevCommit c2 = git.commit().setMessage("change file2").call();
 
-			try (BlameGenerator generator = new BlameGenerator(db, FILENAME_2)) {
+			try (BlameGenerator generator = new BlameGenerator(db,
+					FILENAME_2)) {
 				generator.push(null, db.resolve(Constants.HEAD));
 				assertEquals(3, generator.getResultContents().size());
 
@@ -113,7 +388,8 @@ public void testRenamedBoundLineDelete() throws Exception {
 			}
 
 			// and test again with other BlameGenerator API:
-			try (BlameGenerator generator = new BlameGenerator(db, FILENAME_2)) {
+			try (BlameGenerator generator = new BlameGenerator(db,
+					FILENAME_2)) {
 				generator.push(null, db.resolve(Constants.HEAD));
 				BlameResult result = generator.computeBlameResult();
 
@@ -136,21 +412,22 @@ public void testLinesAllDeletedShortenedWalk() throws Exception {
 		try (Git git = new Git(db)) {
 			String[] content1 = new String[] { "first", "second", "third" };
 
-			writeTrashFile("file.txt", join(content1));
-			git.add().addFilepattern("file.txt").call();
+			writeTrashFile(INTERESTING_FILE, join(content1));
+			git.add().addFilepattern(INTERESTING_FILE).call();
 			git.commit().setMessage("create file").call();
 
 			String[] content2 = new String[] { "" };
 
-			writeTrashFile("file.txt", join(content2));
-			git.add().addFilepattern("file.txt").call();
+			writeTrashFile(INTERESTING_FILE, join(content2));
+			git.add().addFilepattern(INTERESTING_FILE).call();
 			git.commit().setMessage("create file").call();
 
-			writeTrashFile("file.txt", join(content1));
-			git.add().addFilepattern("file.txt").call();
+			writeTrashFile(INTERESTING_FILE, join(content1));
+			git.add().addFilepattern(INTERESTING_FILE).call();
 			RevCommit c3 = git.commit().setMessage("create file").call();
 
-			try (BlameGenerator generator = new BlameGenerator(db, "file.txt")) {
+			try (BlameGenerator generator = new BlameGenerator(db,
+					INTERESTING_FILE)) {
 				generator.push(null, db.resolve(Constants.HEAD));
 				assertEquals(3, generator.getResultContents().size());
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java
index c697688..2fae909 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java
@@ -13,7 +13,9 @@
 import static org.eclipse.jgit.util.Paths.compare;
 import static org.eclipse.jgit.util.Paths.compareSameName;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
@@ -32,6 +34,23 @@ public void testStripTrailingSeparator() {
 	}
 
 	@Test
+	public void testPrefix() {
+		assertTrue(Paths.isEqualOrPrefix("a", "a"));
+		assertTrue(Paths.isEqualOrPrefix("a", "a/b"));
+		assertTrue(Paths.isEqualOrPrefix("a", "a/a.txt"));
+		assertFalse(Paths.isEqualOrPrefix("a", "ab"));
+		assertFalse(Paths.isEqualOrPrefix("a", "a.txt"));
+		assertFalse(Paths.isEqualOrPrefix("a", "b/a.txt"));
+		assertFalse(Paths.isEqualOrPrefix("a", "b/a"));
+		assertFalse(Paths.isEqualOrPrefix("a", "ab/a.txt"));
+		assertFalse(Paths.isEqualOrPrefix("", "a"));
+		assertTrue(Paths.isEqualOrPrefix("", ""));
+		assertTrue(Paths.isEqualOrPrefix("a/b", "a/b"));
+		assertTrue(Paths.isEqualOrPrefix("a/b", "a/b/c"));
+		assertFalse(Paths.isEqualOrPrefix("a/b", "a/bc"));
+	}
+
+	@Test
 	public void testPathCompare() {
 		byte[] a = Constants.encode("afoo/bar.c");
 		byte[] b = Constants.encode("bfoo/bar.c");
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 8aa84f3..4af7adc 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -83,4 +83,11 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/jgit/util/Paths.java" type="org.eclipse.jgit.util.Paths">
+        <filter id="337768515">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.Paths"/>
+            </message_arguments>
+        </filter>
+    </resource>
 </component>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
index 69272b7..36ca97d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
@@ -25,6 +25,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.Paths;
 
 /**
  * Remove untracked files from the working tree
@@ -91,15 +92,16 @@ public Set<String> call() throws NoWorkTreeException, GitAPIException {
 			Set<String> notIgnoredDirs = filterIgnorePaths(untrackedDirs,
 					status.getIgnoredNotInIndex(), false);
 
-			for (String file : notIgnoredFiles)
+			for (String file : notIgnoredFiles) {
 				if (paths.isEmpty() || paths.contains(file)) {
 					files = cleanPath(file, files);
 				}
-
-			for (String dir : notIgnoredDirs)
+			}
+			for (String dir : notIgnoredDirs) {
 				if (paths.isEmpty() || paths.contains(dir)) {
 					files = cleanPath(dir, files);
 				}
+			}
 		} catch (IOException e) {
 			throw new JGitInternalException(e.getMessage(), e);
 		} finally {
@@ -142,14 +144,14 @@ private Set<String> cleanPath(String path, Set<String> inFiles)
 							FileUtils.delete(curFile, FileUtils.RECURSIVE
 									| FileUtils.SKIP_MISSING);
 						}
-						inFiles.add(path + "/"); //$NON-NLS-1$
+						inFiles.add(path + '/');
 					}
 				} else {
 					if (!dryRun) {
 						FileUtils.delete(curFile,
 								FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
 					}
-					inFiles.add(path + "/"); //$NON-NLS-1$
+					inFiles.add(path + '/');
 				}
 			}
 		} else {
@@ -166,14 +168,16 @@ private Set<String> filterIgnorePaths(Set<String> inputPaths,
 			Set<String> ignoredNotInIndex, boolean exact) {
 		if (ignore) {
 			Set<String> filtered = new TreeSet<>(inputPaths);
-			for (String path : inputPaths)
-				for (String ignored : ignoredNotInIndex)
+			for (String path : inputPaths) {
+				for (String ignored : ignoredNotInIndex) {
 					if ((exact && path.equals(ignored))
-							|| (!exact && path.startsWith(ignored))) {
+							|| (!exact
+									&& Paths.isEqualOrPrefix(ignored, path))) {
 						filtered.remove(path);
 						break;
 					}
-
+				}
+			}
 			return filtered;
 		}
 		return inputPaths;
@@ -182,14 +186,14 @@ private Set<String> filterIgnorePaths(Set<String> inputPaths,
 	private Set<String> filterFolders(Set<String> untracked,
 			Set<String> untrackedFolders) {
 		Set<String> filtered = new TreeSet<>(untracked);
-		for (String file : untracked)
-			for (String folder : untrackedFolders)
-				if (file.startsWith(folder)) {
+		for (String file : untracked) {
+			for (String folder : untrackedFolders) {
+				if (Paths.isEqualOrPrefix(folder, file)) {
 					filtered.remove(file);
 					break;
 				}
-
-
+			}
+		}
 		return filtered;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
index 77967df..93ddfc6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
@@ -129,6 +129,7 @@ public class BlameGenerator implements AutoCloseable {
 
 	/** Blame is currently assigned to this source. */
 	private Candidate outCandidate;
+
 	private Region outRegion;
 
 	/**
@@ -403,6 +404,35 @@ private List<RevCommit> getHeads(Repository repo, ObjectId head)
 	 * revision (if the index is interesting), and finally the working tree copy
 	 * (if the working tree is interesting).
 	 *
+	 * @param blameCommit
+	 *            ordered commits to use instead of RevWalk.
+	 * @return {@code this}
+	 * @throws java.io.IOException
+	 *             the repository cannot be read.
+	 * @since 6.3
+	 */
+	public BlameGenerator push(RevCommit blameCommit) throws IOException {
+		if (!find(blameCommit, resultPath)) {
+			return this;
+		}
+
+		Candidate c = new Candidate(getRepository(), blameCommit, resultPath);
+		c.sourceBlob = idBuf.toObjectId();
+		c.loadText(reader);
+		c.regionList = new Region(0, 0, c.sourceText.size());
+		remaining = c.sourceText.size();
+		push(c);
+		return this;
+	}
+
+	/**
+	 * Push a candidate object onto the generator's traversal stack.
+	 * <p>
+	 * Candidates should be pushed in history order from oldest-to-newest.
+	 * Applications should push the starting commit first, then the index
+	 * revision (if the index is interesting), and finally the working tree copy
+	 * (if the working tree is interesting).
+	 *
 	 * @param description
 	 *            description of the blob revision, such as "Working Tree".
 	 * @param id
@@ -428,16 +458,7 @@ public BlameGenerator push(String description, AnyObjectId id)
 		}
 
 		RevCommit commit = revPool.parseCommit(id);
-		if (!find(commit, resultPath))
-			return this;
-
-		Candidate c = new Candidate(getRepository(), commit, resultPath);
-		c.sourceBlob = idBuf.toObjectId();
-		c.loadText(reader);
-		c.regionList = new Region(0, 0, c.sourceText.size());
-		remaining = c.sourceText.size();
-		push(c);
-		return this;
+		return push(commit);
 	}
 
 	/**
@@ -605,7 +626,7 @@ public boolean next() throws IOException {
 				// Do not generate a tip of a reverse. The region
 				// survives and should not appear to be deleted.
 
-			} else /* if (pCnt == 0) */{
+			} else /* if (pCnt == 0) */ {
 				// Root commit, with at least one surviving region.
 				// Assign the remaining blame here.
 				return result(n);
@@ -846,8 +867,8 @@ private boolean processMerge(Candidate n) throws IOException {
 				editList = new EditList(0);
 			} else {
 				p.loadText(reader);
-				editList = diffAlgorithm.diff(textComparator,
-						p.sourceText, n.sourceText);
+				editList = diffAlgorithm.diff(textComparator, p.sourceText,
+						n.sourceText);
 			}
 
 			if (editList.isEmpty()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java
index 5a39f95..ae13ef7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java
@@ -18,7 +18,8 @@
  *
  * @since 4.2
  */
-public class Paths {
+public final class Paths {
+
 	/**
 	 * Remove trailing {@code '/'} if present.
 	 *
@@ -43,6 +44,33 @@ public static String stripTrailingSeparator(String path) {
 	}
 
 	/**
+	 * Determines whether a git path {@code folder} is a prefix of another git
+	 * path {@code path}, or the same as {@code path}. An empty {@code folder}
+	 * is <em>not</em> not considered a prefix and matches only if {@code path}
+	 * is also empty.
+	 *
+	 * @param folder
+	 *            a git path for a directory, without trailing slash
+	 * @param path
+	 *            a git path
+	 * @return {@code true} if {@code folder} is a directory prefix of
+	 *         {@code path}, or is equal to {@code path}, {@code false}
+	 *         otherwise
+	 * @since 6.3
+	 */
+	public static boolean isEqualOrPrefix(String folder, String path) {
+		if (folder.isEmpty()) {
+			return path.isEmpty();
+		}
+		boolean isPrefix = path.startsWith(folder);
+		if (isPrefix) {
+			int length = folder.length();
+			return path.length() == length || path.charAt(length) == '/';
+		}
+		return false;
+	}
+
+	/**
 	 * Compare two paths according to Git path sort ordering rules.
 	 *
 	 * @param aPath
@@ -63,9 +91,8 @@ public static String stripTrailingSeparator(String path) {
 	 * @param bMode
 	 *            mode of the second file. Trees are sorted as though
 	 *            {@code bPath[bEnd] == '/'}, even if bEnd does not exist.
-	 * @return &lt;0 if {@code aPath} sorts before {@code bPath};
-	 *         0 if the paths are the same;
-	 *         &gt;0 if {@code aPath} sorts after {@code bPath}.
+	 * @return &lt;0 if {@code aPath} sorts before {@code bPath}; 0 if the paths
+	 *         are the same; &gt;0 if {@code aPath} sorts after {@code bPath}.
 	 */
 	public static int compare(byte[] aPath, int aPos, int aEnd, int aMode,
 			byte[] bPath, int bPos, int bEnd, int bMode) {