@@ -138,4 +138,23 @@ | |||
</sync> | |||
</target> | |||
<target name="casesensitivity-test" depends="setUp" description="Tests the bug fix for bz-62890"> | |||
<mkdir dir="${src}/casecheck"/> | |||
<mkdir dir="${dest}/casecheck"/> | |||
<!-- lower case file in source dir --> | |||
<touch file="${src}/casecheck/a.txt"/> | |||
<!-- upper case file in dest dir --> | |||
<touch file="${dest}/casecheck/A.txt"/> | |||
<!-- some random file in source dir --> | |||
<touch file="${src}/casecheck/foo.txt"/> | |||
<!-- some random file in dest dir --> | |||
<touch file="${dest}/casecheck/bar.txt"/> | |||
<sync todir="${dest}/casecheck" | |||
includeEmptyDirs="true" | |||
overwrite="false"> | |||
<fileset dir="${src}/casecheck"/> | |||
</sync> | |||
</target> | |||
</project> |
@@ -23,6 +23,7 @@ import java.util.Collections; | |||
import java.util.HashSet; | |||
import java.util.LinkedHashSet; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import java.util.Set; | |||
import java.util.stream.Stream; | |||
@@ -40,6 +41,7 @@ import org.apache.tools.ant.types.resources.Restrict; | |||
import org.apache.tools.ant.types.resources.selectors.Exists; | |||
import org.apache.tools.ant.types.selectors.FileSelector; | |||
import org.apache.tools.ant.types.selectors.NoneSelector; | |||
import org.apache.tools.ant.util.FileUtils; | |||
/** | |||
* Synchronize a local target directory from the files defined | |||
@@ -222,6 +224,13 @@ public class Sync extends Task { | |||
} else { | |||
ds = new DirectoryScanner(); | |||
ds.setBasedir(toDir); | |||
// set the case sensitivity of the directory scanner based on the | |||
// directory we are scanning, if we are able to determine that detail. | |||
// Else let the directory scanner default it to whatever it does internally | |||
final Optional<Boolean> caseSensitive = FileUtils.isCaseSensitiveFileSystem(toDir.toPath()); | |||
if (caseSensitive.isPresent()) { | |||
ds.setCaseSensitive(caseSensitive.get()); | |||
} | |||
} | |||
ds.addExcludes(excls); | |||
@@ -31,6 +31,7 @@ import java.net.MalformedURLException; | |||
import java.net.URL; | |||
import java.net.URLConnection; | |||
import java.nio.channels.Channel; | |||
import java.nio.file.FileSystem; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.nio.file.Paths; | |||
@@ -38,7 +39,11 @@ import java.nio.file.StandardOpenOption; | |||
import java.text.DecimalFormat; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Locale; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import java.util.Random; | |||
import java.util.Stack; | |||
import java.util.StringTokenizer; | |||
@@ -75,6 +80,7 @@ public class FileUtils { | |||
private static final boolean ON_DOS = Os.isFamily("dos"); | |||
private static final boolean ON_WIN9X = Os.isFamily("win9x"); | |||
private static final boolean ON_WINDOWS = Os.isFamily("windows"); | |||
private static final Map<FileSystem, Boolean> fileSystemCaseSensitivity = new HashMap<>(); | |||
static final int BUF_SIZE = 8192; | |||
@@ -1769,4 +1775,67 @@ public class FileUtils { | |||
return Files.newOutputStream(path); | |||
} | |||
} | |||
/** | |||
* Tries to determine the case sensitivity of the filesystem corresponding to the | |||
* {@code path}. While doing so, this method might create a temporary file under | |||
* the directory represented by the {@code path}, if it's a directory or in the | |||
* parent directory of the {@code path}, if it's a file. | |||
* <p> | |||
* This method works on a best effort basis and will return an {@link Optional#empty()} | |||
* if it cannot determine the case sensitivity, either due to exception or for any other | |||
* reason. | |||
* </p> | |||
* @param path The path whose filesystem case sensitivity needs to be checked | |||
* @return Returns true if the filesystem corresponding to the passed {@code path} | |||
* is case sensitive. Else returns false. If the case sensitivity | |||
* cannot be determined for whatever reason, this method returns an | |||
* {@link Optional#empty()} | |||
* @throws IllegalArgumentException If the passed path is null | |||
* @since Ant 1.10.6 | |||
*/ | |||
public static Optional<Boolean> isCaseSensitiveFileSystem(final Path path) { | |||
if (path == null) { | |||
throw new IllegalArgumentException("Path cannot be null"); | |||
} | |||
final FileSystem fileSystem = path.getFileSystem(); | |||
final Boolean caseSensitivity = fileSystemCaseSensitivity.get(fileSystem); | |||
if (caseSensitivity != null) { | |||
return Optional.of(caseSensitivity); | |||
} | |||
final String mixedCaseFileNamePrefix = "aNt"; | |||
Path mixedCaseTmpFile = null; | |||
final boolean caseSensitive; | |||
try { | |||
synchronized (fileSystemCaseSensitivity) { | |||
if (fileSystemCaseSensitivity.containsKey(fileSystem)) { | |||
return Optional.of(fileSystemCaseSensitivity.get(fileSystem)); | |||
} | |||
if (Files.isRegularFile(path)) { | |||
mixedCaseTmpFile = Files.createTempFile(path.getParent(), mixedCaseFileNamePrefix, null); | |||
} else if (Files.isDirectory(path)) { | |||
mixedCaseTmpFile = Files.createTempFile(path, mixedCaseFileNamePrefix, null); | |||
} else { | |||
// we can only do our tricks to figure out whether the filesystem is | |||
// case sensitive, only if the path is a directory or a file. | |||
// In other cases (like the path being non-existent), we don't | |||
// have a way to determine that detail | |||
return Optional.empty(); | |||
} | |||
final Path lowerCasePath = Paths.get(mixedCaseTmpFile.toString().toLowerCase(Locale.US)); | |||
caseSensitive = !Files.isSameFile(mixedCaseTmpFile, lowerCasePath); | |||
fileSystemCaseSensitivity.put(fileSystem, caseSensitive); | |||
} | |||
} catch (IOException ioe) { | |||
System.err.println("Could not determine the case sensitivity of the " + | |||
"filesystem for path " + path + " due to " + ioe); | |||
return Optional.empty(); | |||
} finally { | |||
// delete the tmp file | |||
if (mixedCaseTmpFile != null) { | |||
FileUtils.delete(mixedCaseTmpFile.toFile()); | |||
} | |||
} | |||
return Optional.of(caseSensitive); | |||
} | |||
} |
@@ -139,6 +139,21 @@ public class SyncTest { | |||
assertThat(buildRule.getFullLog(), not(containsString("Removing orphan file:"))); | |||
} | |||
/** | |||
* Test for bz-62890 bug fix | |||
*/ | |||
@Test | |||
public void testCaseSensitivityOfDest() { | |||
buildRule.executeTarget("casesensitivity-test"); | |||
final String destDir = buildRule.getProject().getProperty("dest") + "/casecheck"; | |||
assertFileIsPresent(destDir + "/a.txt"); | |||
assertFileIsPresent(destDir + "/A.txt"); | |||
assertFileIsPresent(destDir + "/foo.txt"); | |||
assertFileIsNotPresent(destDir + "/bar.txt"); | |||
} | |||
public void assertFileIsPresent(String f) { | |||
assertTrue("Expected file " + f, buildRule.getProject().resolveFile(f).exists()); | |||
} | |||
@@ -21,11 +21,18 @@ package org.apache.tools.ant.util; | |||
import java.io.File; | |||
import java.io.FileOutputStream; | |||
import java.io.IOException; | |||
import java.nio.file.FileSystem; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.nio.file.Paths; | |||
import java.util.Locale; | |||
import java.util.Optional; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.MagicTestNames; | |||
import org.apache.tools.ant.taskdefs.condition.Os; | |||
import org.junit.After; | |||
import org.junit.Assert; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
@@ -681,6 +688,37 @@ public class FileUtilsTest { | |||
assertTrue(FILE_UTILS.isLeadingPath(new File("c:\\foo"), new File("c:\\foo\\"), false)); | |||
} | |||
/** | |||
* Tests {@link FileUtils#isCaseSensitiveFileSystem(Path)} method | |||
* | |||
* @throws Exception | |||
*/ | |||
@Test | |||
public void testCaseSensitiveFileSystem() throws Exception { | |||
// create a temp file in a fresh directory | |||
final Path tmpDir = Files.createTempDirectory(null); | |||
final Path tmpFile = Files.createTempFile(tmpDir, null, null); | |||
tmpFile.toFile().deleteOnExit(); | |||
tmpDir.toFile().deleteOnExit(); | |||
// now check if a file with that same name but different case is considered to exist | |||
final boolean existsAsLowerCase = Files.exists(Paths.get(tmpDir.toString(), tmpFile.getFileName().toString().toLowerCase(Locale.US))); | |||
final boolean existsAsUpperCase = Files.exists(Paths.get(tmpDir.toString(), tmpFile.getFileName().toString().toUpperCase(Locale.US))); | |||
// if the temp file that we created is found to not exist in a particular "case", then | |||
// the filesystem is case sensitive | |||
final Boolean expectedCaseSensitivity = existsAsLowerCase == false || existsAsUpperCase == false; | |||
// call the method and pass it a directory | |||
Optional<Boolean> actualCaseSensitivity = FileUtils.isCaseSensitiveFileSystem(tmpDir); | |||
Assert.assertTrue("Filesystem case sensitivity was expected to be determined", actualCaseSensitivity.isPresent()); | |||
Assert.assertEquals("Filesystem was expected to be case " + (expectedCaseSensitivity | |||
? "sensitive" : "insensitive"), expectedCaseSensitivity, actualCaseSensitivity.get()); | |||
// now test it out by passing it a file | |||
actualCaseSensitivity = FileUtils.isCaseSensitiveFileSystem(tmpFile); | |||
Assert.assertTrue("Filesystem case sensitivity was expected to be determined", actualCaseSensitivity.isPresent()); | |||
Assert.assertEquals("Filesystem was expected to be case " + (expectedCaseSensitivity | |||
? "sensitive" : "insensitive"), expectedCaseSensitivity, actualCaseSensitivity.get()); | |||
} | |||
/** | |||
* adapt file separators to local conventions | |||
*/ | |||