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>
This commit is contained in:
Thomas Wolf 2022-07-30 00:27:49 +02:00
parent 59e8bec6e7
commit 8184683f7e
5 changed files with 96 additions and 18 deletions

View File

@ -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());
}
}

View File

@ -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;
@ -31,6 +33,23 @@ public void testStripTrailingSeparator() {
assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo///"));
}
@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");

View File

@ -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>

View File

@ -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;
}

View File

@ -18,7 +18,8 @@
*
* @since 4.2
*/
public class Paths {
public final class Paths {
/**
* Remove trailing {@code '/'} if present.
*
@ -42,6 +43,33 @@ public static String stripTrailingSeparator(String path) {
return path.substring(0, i);
}
/**
* 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.
*
@ -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) {