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