diff options
author | Thomas Wolf <twolf@apache.org> | 2022-07-30 00:27:49 +0200 |
---|---|---|
committer | Thomas Wolf <twolf@apache.org> | 2022-08-03 07:42:27 +0200 |
commit | 8184683f7e8d0e62bb70ff27af92dcc2d7332939 (patch) | |
tree | ea2bbcb361f919369ce49efa22680e06f299c418 | |
parent | 59e8bec6e7705a89b5d0b9c6ac004b323ffa16b0 (diff) | |
download | jgit-8184683f7e8d0e62bb70ff27af92dcc2d7332939.tar.gz jgit-8184683f7e8d0e62bb70ff27af92dcc2d7332939.zip |
CleanCommand: fix prefix matching
String.startsWith() is not a valid test for file path prefixes:
directory "a" is _not_ a prefix of a file "ab", only of "a/b".
Add a proper Paths.isEqualOrPrefix() method and use it in CleanCommand.
Bug: 580478
Change-Id: I6863e6ba94a8ffba6561835cc57044a0945d2770
Signed-off-by: Thomas Wolf <twolf@apache.org>
5 files changed, 96 insertions, 18 deletions
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 f8078890b7..204c89db9b 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 class CleanCommandTest extends RepositoryTestCase { 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/util/PathsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java index c6976882e4..2fae9099fb 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 @@ package org.eclipse.jgit.util; 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 class PathsTest { } @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 8aa84f3ac1..4af7adc72b 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 69272b7547..36ca97d694 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.events.WorkingTreeModifiedEvent; 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 class CleanCommand extends GitCommand<Set<String>> { 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 @@ public class CleanCommand extends GitCommand<Set<String>> { 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 @@ public class CleanCommand extends GitCommand<Set<String>> { 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 @@ public class CleanCommand extends GitCommand<Set<String>> { 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/util/Paths.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java index 5a39f95b2b..ae13ef7f3c 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 @@ import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; * * @since 4.2 */ -public class Paths { +public final class Paths { + /** * Remove trailing {@code '/'} if present. * @@ -43,6 +44,33 @@ public class Paths { } /** + * 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 class Paths { * @param bMode * mode of the second file. Trees are sorted as though * {@code bPath[bEnd] == '/'}, even if bEnd does not exist. - * @return <0 if {@code aPath} sorts before {@code bPath}; - * 0 if the paths are the same; - * >0 if {@code aPath} sorts after {@code bPath}. + * @return <0 if {@code aPath} sorts before {@code bPath}; 0 if the paths + * are the same; >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) { |