Browse Source

bz-62890 Make sure the sync task considers the case sensitivity of the destination directory's filesystem while looking for orphan files to delete

master
Jaikiran Pai 6 years ago
parent
commit
52c8c5a079
5 changed files with 150 additions and 0 deletions
  1. +19
    -0
      src/etc/testcases/taskdefs/sync.xml
  2. +9
    -0
      src/main/org/apache/tools/ant/taskdefs/Sync.java
  3. +69
    -0
      src/main/org/apache/tools/ant/util/FileUtils.java
  4. +15
    -0
      src/tests/junit/org/apache/tools/ant/taskdefs/SyncTest.java
  5. +38
    -0
      src/tests/junit/org/apache/tools/ant/util/FileUtilsTest.java

+ 19
- 0
src/etc/testcases/taskdefs/sync.xml View File

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

+ 9
- 0
src/main/org/apache/tools/ant/taskdefs/Sync.java View File

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



+ 69
- 0
src/main/org/apache/tools/ant/util/FileUtils.java View File

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

+ 15
- 0
src/tests/junit/org/apache/tools/ant/taskdefs/SyncTest.java View File

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


+ 38
- 0
src/tests/junit/org/apache/tools/ant/util/FileUtilsTest.java View File

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


Loading…
Cancel
Save