diff --git a/updater/src/com/intellij/updater/Patch.java b/updater/src/com/intellij/updater/Patch.java index e934f46a1445..5389e1ca4cd5 100644 --- a/updater/src/com/intellij/updater/Patch.java +++ b/updater/src/com/intellij/updater/Patch.java @@ -1,4 +1,4 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.updater; import java.io.*; @@ -220,8 +220,7 @@ public class Patch { File toDir = toBaseDir(rootDir); boolean checkWarnings = true; while (checkWarnings) { - // always collect files and folders - to avoid cases such as IDEA-152249 - files = Utils.collectRelativePaths(toDir); + files = Utils.collectRelativePaths(toDir.toPath()); checkWarnings = false; for (String file : files) { String warning = myWarnings.get(file); @@ -416,8 +415,7 @@ public class Patch { public Map digestFiles(File dir, List ignoredFiles, boolean normalize) throws IOException { Map result = new LinkedHashMap<>(); - //always collect files and folders to avoid cases such as IDEA-152249 - LinkedHashSet paths = Utils.collectRelativePaths(dir); + LinkedHashSet paths = Utils.collectRelativePaths(dir.toPath()); for (String each : paths) { if (!ignoredFiles.contains(each)) { result.put(each, digestFile(new File(dir, each), normalize)); diff --git a/updater/src/com/intellij/updater/Utils.java b/updater/src/com/intellij/updater/Utils.java index 706dd56a23a1..beabbb30077b 100644 --- a/updater/src/com/intellij/updater/Utils.java +++ b/updater/src/com/intellij/updater/Utils.java @@ -1,4 +1,4 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.updater; import java.io.*; @@ -272,28 +272,29 @@ public class Utils { return new BufferedInputStream(zipFile.getInputStream(entry)); } - public static LinkedHashSet collectRelativePaths(File dir) { + // always collect files and folders - to avoid cases such as IDEA-152249 + public static LinkedHashSet collectRelativePaths(Path root) throws IOException { LinkedHashSet result = new LinkedHashSet<>(); - collectRelativePaths(dir, result, null); + + Files.walkFileTree(root, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + if (dir != root) { + result.add(root.relativize(dir).toString() + '/'); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + result.add(root.relativize(file).toString()); + return FileVisitResult.CONTINUE; + } + }); + return result; } - private static void collectRelativePaths(File dir, LinkedHashSet result, String parentPath) { - File[] children = dir.listFiles(); - if (children == null) return; - - for (File each : children) { - String relativePath = (parentPath == null ? "" : parentPath + '/') + each.getName(); - if (each.isDirectory()) { - result.add(relativePath + '/'); // the trailing slash is used by .zip to determine whether it is a directory - collectRelativePaths(each, result, relativePath); - } - else { - result.add(relativePath); - } - } - } - public static InputStream newFileInputStream(File file, boolean normalize) throws IOException { return normalize && isZipFile(file.getName()) ? new NormalizedZipInputStream(file) : new FileInputStream(file); } diff --git a/updater/testSrc/com/intellij/updater/SymlinkPatchTest.java b/updater/testSrc/com/intellij/updater/SymlinkPatchTest.java index 23f7a79b7ec2..b64eba06bd6c 100644 --- a/updater/testSrc/com/intellij/updater/SymlinkPatchTest.java +++ b/updater/testSrc/com/intellij/updater/SymlinkPatchTest.java @@ -1,10 +1,16 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.updater; import com.intellij.openapi.util.io.FileUtil; import org.junit.Test; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Random; +import java.util.zip.CRC32; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assume.assumeFalse; @@ -77,4 +83,44 @@ public class SymlinkPatchTest extends PatchTestCase { new DeleteAction(patch, "Readme.lnk", CHECKSUMS.LINK_TO_DOT_README_TXT), new CreateAction(patch, "Readme.link")); } + + @Test + public void multipleDirectorySymlinks() throws Exception { + long l1 = randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Libraries/lib1.dylib")); + long l2 = randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Libraries/lib2.dylib")); + long r1 = randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Resources/r1.bin")); + long r2 = randomFile(myOlderDir.toPath().resolve("A.framework/Versions/A/Resources/r2.bin")); + Files.createSymbolicLink(myOlderDir.toPath().resolve("A.framework/Versions/Current"), Paths.get("A")); + Files.createSymbolicLink(myOlderDir.toPath().resolve("A.framework/Libraries"), Paths.get("Versions/Current/Libraries")); + Files.createSymbolicLink(myOlderDir.toPath().resolve("A.framework/Resources"), Paths.get("Versions/Current/Resources")); + + randomFile(myNewerDir.toPath().resolve("A.framework/Versions/A/Libraries/lib1.dylib")); + randomFile(myNewerDir.toPath().resolve("A.framework/Versions/A/Libraries/lib2.dylib")); + randomFile(myNewerDir.toPath().resolve("A.framework/Versions/A/Resources/r1.bin")); + randomFile(myNewerDir.toPath().resolve("A.framework/Versions/A/Resources/r2.bin")); + Files.createSymbolicLink(myNewerDir.toPath().resolve("A.framework/Versions/Current"), Paths.get("A")); + Files.createSymbolicLink(myNewerDir.toPath().resolve("A.framework/Libraries"), Paths.get("Versions/Current/Libraries")); + Files.createSymbolicLink(myNewerDir.toPath().resolve("A.framework/Resources"), Paths.get("Versions/Current/Resources")); + + Patch patch = createPatch(); + assertThat(sortActions(patch.getActions())).containsExactly( + new UpdateAction(patch, "A.framework/Versions/A/Libraries/lib1.dylib", l1), + new UpdateAction(patch, "A.framework/Versions/A/Libraries/lib2.dylib", l2), + new UpdateAction(patch, "A.framework/Versions/A/Resources/r1.bin", r1), + new UpdateAction(patch, "A.framework/Versions/A/Resources/r2.bin", r2)); + } + + private static long randomFile(Path file) throws IOException { + Random rnd = new Random(); + int size = (1 + rnd.nextInt(1023)) * 1024; + byte[] data = new byte[size]; + rnd.nextBytes(data); + + Files.createDirectories(file.getParent()); + Files.write(file, data); + + CRC32 crc32 = new CRC32(); + crc32.update(data); + return crc32.getValue(); + } } \ No newline at end of file