[updater] reuse diffs from sibling patches

When building several patches for different OS/Arch pairs of IDE distributions, we could reuse diffs since IDE distributions contains a lot of exactly the same files.

Fixes IJI-319

GitOrigin-RevId: 7a74330a8467121106de5b74e7443dc96d0ebfb7
This commit is contained in:
Vladislav Rassokhin
2023-05-15 21:20:01 +02:00
committed by intellij-monorepo-bot
parent 0a2276866a
commit 00c7b23d67
3 changed files with 49 additions and 8 deletions

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.updater;
import java.io.File;
@@ -20,7 +20,7 @@ import static com.intellij.updater.Runner.LOG;
public final class PatchFileCreator {
private static final String PATCH_INFO_FILE_NAME = ".patch-info";
public static Patch create(PatchSpec spec, File patchFile, UpdaterUI ui) throws IOException {
public static Patch create(PatchSpec spec, File patchFile, UpdaterUI ui, Path cacheDir) throws IOException {
LOG.info("Creating the patch file '" + patchFile + "'...");
ui.startProcess("Creating the patch file '" + patchFile + "'...");
@@ -40,11 +40,27 @@ public final class PatchFileCreator {
if (action instanceof UpdateAction && !action.isCritical()) {
int _i = i;
tasks.put(action, executor.submit(() -> {
Path cached = null;
if (cacheDir != null) {
String cachedName = getCachedFileName((UpdateAction)action, newerDir);
if (cachedName != null) {
cached = cacheDir.resolve(cachedName);
if (Files.exists(cached) && Files.isRegularFile(cached)) {
LOG.info("Reusing diff for " + action.getPath() + " : " + cachedName);
return cached;
}
}
}
Path temp = Utils.getTempFile("diff_" + _i).toPath();
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(temp))) {
out.setLevel(0);
action.buildPatchFile(olderDir, newerDir, out);
}
if (cached != null) {
Files.createDirectories(cached.getParent());
Files.copy(temp, cached);
LOG.info("Caching diff for " + action.getPath() + " : " + cached.getFileName());
}
return temp;
}));
}
@@ -93,6 +109,24 @@ public final class PatchFileCreator {
return patchInfo;
}
private static String getCachedFileName(UpdateAction action, File newerDir) {
if (action.isMove()) return null;
if (!Digester.isFile(action.getChecksum())) return null;
// Single file diff is a zip file with single entry corresponding to target file name, so entry name should be part of caching key
// Also both source and target digests are part of key
try {
return "diff-v1-" +
action.getPath().replaceAll("[^A-Za-z0-9_\\-]","_") +
'-' +
Long.toHexString(action.getChecksum()) +
'-' +
Long.toHexString(Digester.digestRegularFile(action.getFile(newerDir), false));
}
catch (IOException ignored) {
return null;
}
}
public static PreparationResult prepareAndValidate(File patchFile,
File toDir,
UpdaterUI ui) throws IOException, OperationCancelledException {

View File

@@ -145,6 +145,8 @@ public final class Runner {
String timeoutStr = getArgument(args, "timeout");
int timeout = timeoutStr != null ? Integer.parseInt(timeoutStr) : 0;
String cacheDir = getArgument(args, "cache-dir");
PatchSpec spec = new PatchSpec()
.setOldVersionDescription(oldVersionDesc)
.setNewVersionDescription(newVersionDesc)
@@ -164,7 +166,7 @@ public final class Runner {
.setWarnings(warnings)
.setTimeout(timeout);
boolean success = create(spec);
boolean success = create(spec, cacheDir != null ? Paths.get(cacheDir) : null);
System.exit(success ? 0 : 1);
}
else if (args.length >= 2 && ("install".equals(args[0]) || "apply".equals(args[0])) ||
@@ -289,6 +291,8 @@ public final class Runner {
" patch will only be applied if it is guaranteed that the patched version will match exactly\n" +
" the source of the patch. This means that unexpected files will be deleted and all existing files\n" +
" will be validated\n" +
" --cache-dir=<dir>: Sets directory where diffs can be cached.\n" +
" Useful when building several patches on folders with common files.\n" +
" --root=<dir>: Sets dir as the root directory of the patch. The root directory is the directory where the patch should be\n" +
" applied to. For example on Mac, you can diff the two .app folders and set Contents as the root.\n" +
" The root directory is relative to <old_folder> and uses forwards-slashes as separators.\n" +
@@ -302,13 +306,13 @@ public final class Runner {
" <folder>: The folder where product was installed. For example: c:/Program Files/JetBrains/IntelliJ IDEA 2017.3.4");
}
private static boolean create(PatchSpec spec) {
private static boolean create(PatchSpec spec, Path cacheDir) {
ConsoleUpdaterUI ui = new ConsoleUpdaterUI();
boolean success = false;
try {
File tempPatchFile = Utils.getTempFile("patch");
PatchFileCreator.create(spec, tempPatchFile, ui);
PatchFileCreator.create(spec, tempPatchFile, ui, cacheDir);
LOG.info("Packing JAR file: " + spec.getPatchFile());
ui.startProcess("Packing JAR file '" + spec.getPatchFile() + "'...");

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.updater;
import com.intellij.openapi.util.io.FileUtil;
@@ -15,7 +15,10 @@ import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.zip.ZipFile;
@@ -664,7 +667,7 @@ public abstract class PatchApplyingRevertingTest extends PatchTestCase {
@Override
protected Patch createPatch() throws IOException {
assertFalse(myFile.exists());
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI, null);
assertTrue(myFile.exists());
return patch;
}