+++ /dev/null
-package com.intellij.updater;
-
-import java.io.DataInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipOutputStream;
-
-public abstract class BaseDeleteAction extends PatchAction {
- public BaseDeleteAction(String path, long checksum) {
- super(path, checksum);
- }
-
- public BaseDeleteAction(DataInputStream in) throws IOException {
- super(in);
- }
-
- @Override
- public void doBuildPatchFile(File olderFile, File newerFile, ZipOutputStream patchOutput) throws IOException {
- // do nothing
- }
-
- @Override
- protected ValidationResult doValidate(File toFile) throws IOException {
- ValidationResult result = doValidateAccess(toFile, ValidationResult.Action.DELETE);
- if (result != null) return result;
-
- if (toFile.exists() && isModified(toFile)) {
- return new ValidationResult(ValidationResult.Kind.CONFLICT,
- myPath,
- ValidationResult.Action.DELETE,
- "Modified",
- ValidationResult.Option.DELETE,
- ValidationResult.Option.KEEP);
- }
- return null;
- }
-
- @Override
- protected boolean shouldApplyOn(File toFile) {
- return toFile.exists();
- }
-
- @Override
- protected void doApply(ZipFile patchFile, File toFile) throws IOException {
- Utils.delete(toFile);
- }
-
- protected void doBackup(File toFile, File backupFile) throws IOException {
- Utils.copy(toFile, backupFile);
- }
-
- protected void doRevert(File toFile, File backupFile) throws IOException {
- if (!toFile.exists() || toFile.isDirectory() || isModified(toFile)) {
- Utils.delete(toFile); // make sure there is no directory remained on this path (may remain from previous 'create' actions
- Utils.copy(backupFile, toFile);
- }
- }
-}
import java.util.zip.ZipOutputStream;
public abstract class BaseUpdateAction extends PatchAction {
- public BaseUpdateAction(String path, long checksum) {
- super(path, checksum);
+ private final String mySource;
+ protected final boolean myIsMove;
+
+ public BaseUpdateAction(Patch patch, String path, String source, long checksum, boolean move) {
+ super(patch, path, checksum);
+ myIsMove = move;
+ mySource = source;
}
- public BaseUpdateAction(DataInputStream in) throws IOException {
- super(in);
+ public BaseUpdateAction(Patch patch, DataInputStream in) throws IOException {
+ super(patch, in);
+ mySource = in.readUTF();
+ myIsMove = in.readBoolean();
}
@Override
- protected ValidationResult doValidate(File toFile) throws IOException {
- ValidationResult result = doValidateAccess(toFile, ValidationResult.Action.UPDATE);
- if (result != null) return result;
- return doValidateNotChanged(toFile, ValidationResult.Kind.ERROR, ValidationResult.Action.UPDATE);
+ public void write(DataOutputStream out) throws IOException {
+ super.write(out);
+ out.writeUTF(mySource);
+ out.writeBoolean(myIsMove);
+ }
+
+ protected File getSource(File toDir) {
+ return new File(toDir, mySource);
+ }
+
+ public String getSourcePath() {
+ return mySource;
}
@Override
- protected boolean shouldApplyOn(File toFile) {
+ protected boolean doShouldApply(File toDir) {
// if the file is optional in may not exist
- return toFile.exists();
+ return getSource(toDir).exists();
+ }
+
+ @Override
+ public void buildPatchFile(File olderDir, File newerDir, ZipOutputStream patchOutput) throws IOException {
+ doBuildPatchFile(getSource(olderDir), getFile(newerDir), patchOutput);
+ }
+
+ @Override
+ public ValidationResult validate(File toDir) throws IOException {
+ File fromFile = getSource(toDir);
+ ValidationResult result = doValidateAccess(fromFile, ValidationResult.Action.UPDATE);
+ if (result != null) return result;
+ if (!mySource.isEmpty()) {
+ result = doValidateAccess(getFile(toDir), ValidationResult.Action.UPDATE);
+ if (result != null) return result;
+ }
+ return doValidateNotChanged(fromFile, ValidationResult.Kind.ERROR, ValidationResult.Action.UPDATE);
}
@Override
protected void doBackup(File toFile, File backupFile) throws IOException {
- Utils.copy(toFile, backupFile);
+ Utils.mirror(toFile, backupFile);
}
protected void replaceUpdated(File from, File dest) throws IOException {
// on OS X code signing caches seem to be associated with specific file ids, so we need to remove the original file.
- if (!dest.delete()) throw new IOException("Cannot delete file " + dest);
+ if (dest.exists() && !dest.delete()) throw new IOException("Cannot delete file " + dest);
Utils.copy(from, dest);
}
@Override
protected void doRevert(File toFile, File backupFile) throws IOException {
if (!toFile.exists() || isModified(toFile)) {
- Utils.copy(backupFile, toFile);
+ Utils.mirror(backupFile, toFile);
}
}
- protected void writeDiff(File olderFile, File newerFile, ZipOutputStream patchOutput) throws IOException {
- BufferedInputStream olderFileIn = new BufferedInputStream(new FileInputStream(olderFile));
+ protected void writeDiff(File olderFile, File newerFile, OutputStream patchOutput) throws IOException {
+ BufferedInputStream olderFileIn = new BufferedInputStream(Utils.newFileInputStream(olderFile, myPatch.isNormalized()));
BufferedInputStream newerFileIn = new BufferedInputStream(new FileInputStream(newerFile));
try {
writeDiff(olderFileIn, newerFileIn, patchOutput);
}
}
- protected void writeDiff(InputStream olderFileIn, InputStream newerFileIn, ZipOutputStream patchOutput)
+ protected void writeDiff(InputStream olderFileIn, InputStream newerFileIn, OutputStream patchOutput)
throws IOException {
Runner.logger.info("writing diff");
ByteArrayOutputStream diffOutput = new ByteArrayOutputStream();
byte[] newerFileBuffer = JBDiff.bsdiff(olderFileIn, newerFileIn, diffOutput);
diffOutput.close();
- if (diffOutput.size() < newerFileBuffer.length) {
+ if (!isCritical() && diffOutput.size() < newerFileBuffer.length) {
patchOutput.write(1);
Utils.copyBytesToStream(diffOutput, patchOutput);
}
Utils.copyStream(patchInput, toFileOut);
}
}
+
+ @Override
+ public String toString() {
+ String moveInfo = "";
+ if (!mySource.equals(myPath)) {
+ moveInfo = "[" + (myIsMove ? "= " : "~ ") + mySource + "]";
+ }
+ return super.toString() + moveInfo;
+ }
+
+ public boolean isMove() {
+ return myIsMove;
+ }
}
public void checkCancelled() throws OperationCancelledException {
}
+ @Override
+ public void setDescription(String oldBuildDesc, String newBuildDesc) {
+ System.out.println("From " + oldBuildDesc + " to " + newBuildDesc);
+ }
+
+ @Override
+ public boolean showWarning(String message) {
+ System.out.println("Warning: " + message);
+ return false;
+ }
+
public Map<String, ValidationResult.Option> askUser(List<ValidationResult> validationResults) {
return Collections.emptyMap();
}
import java.util.zip.ZipOutputStream;
public class CreateAction extends PatchAction {
- public CreateAction(String path) {
- super(path, -1);
+ public CreateAction(Patch patch, String path) {
+ super(patch, path, Digester.INVALID);
}
- public CreateAction(DataInputStream in) throws IOException {
- super(in);
+ public CreateAction(Patch patch, DataInputStream in) throws IOException {
+ super(patch, in);
}
+ @Override
protected void doBuildPatchFile(File olderFile, File newerFile, ZipOutputStream patchOutput) throws IOException {
Runner.logger.info("building PatchFile");
patchOutput.putNextEntry(new ZipEntry(myPath));
-
- writeExecutableFlag(patchOutput, newerFile);
- Utils.copyFileToStream(newerFile, patchOutput);
+ if (!newerFile.isDirectory()) {
+ writeExecutableFlag(patchOutput, newerFile);
+ Utils.copyFileToStream(newerFile, patchOutput);
+ }
patchOutput.closeEntry();
}
@Override
- protected ValidationResult doValidate(File toFile) {
+ public ValidationResult validate(File toDir) {
+ File toFile = getFile(toDir);
ValidationResult result = doValidateAccess(toFile, ValidationResult.Action.CREATE);
if (result != null) return result;
if (toFile.exists()) {
- return new ValidationResult(ValidationResult.Kind.CONFLICT,
- myPath,
+ ValidationResult.Option[] options = myPatch.isStrict()
+ ? new ValidationResult.Option[]{ValidationResult.Option.REPLACE}
+ : new ValidationResult.Option[]{ValidationResult.Option.REPLACE, ValidationResult.Option.KEEP};
+ return new ValidationResult(ValidationResult.Kind.CONFLICT, myPath,
ValidationResult.Action.CREATE,
ValidationResult.ALREADY_EXISTS_MESSAGE,
- ValidationResult.Option.REPLACE, ValidationResult.Option.KEEP);
+ options);
}
return null;
}
}
@Override
- protected void doApply(ZipFile patchFile, File toFile) throws IOException {
+ protected void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException {
prepareToWriteFile(toFile);
- InputStream in = Utils.getEntryInputStream(patchFile, myPath);
- try {
- boolean executable = readExecutableFlag(in);
- Utils.copyStreamToFile(in, toFile);
- Utils.setExecutable(toFile, executable);
- }
- finally {
- in.close();
+ ZipEntry entry = Utils.getZipEntry(patchFile, myPath);
+ if (entry.isDirectory()) {
+ if (!toFile.mkdir()) {
+ throw new IOException("Unable to create directory " + myPath);
+ }
+ } else {
+ InputStream in = Utils.findEntryInputStreamForEntry(patchFile, entry);
+ try {
+ boolean executable = readExecutableFlag(in);
+ Utils.copyStreamToFile(in, toFile);
+ Utils.setExecutable(toFile, executable);
+ }
+ finally {
+ in.close();
+ }
}
}
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
-public class DeleteAction extends BaseDeleteAction {
- public DeleteAction(String path, long checksum) {
- super(path, checksum);
+public class DeleteAction extends PatchAction {
+ public DeleteAction(Patch patch, String path, long checksum) {
+ super(patch, path, checksum);
}
- public DeleteAction(DataInputStream in) throws IOException {
- super(in);
+ public DeleteAction(Patch patch, DataInputStream in) throws IOException {
+ super(patch, in);
}
@Override
- protected boolean isModified(File toFile) throws IOException {
- return myChecksum != Digester.digestRegularFile(toFile);
+ public void doBuildPatchFile(File olderDir, File newerFile, ZipOutputStream patchOutput) throws IOException {
+ // do nothing
+ }
+
+ @Override
+ public ValidationResult validate(File toDir) throws IOException {
+ File toFile = getFile(toDir);
+ ValidationResult result = doValidateAccess(toFile, ValidationResult.Action.DELETE);
+ if (result != null) return result;
+
+ if (myPatch.validateDeletion(myPath) && toFile.exists() && isModified(toFile)) {
+ ValidationResult.Option[] options = myPatch.isStrict()
+ ? new ValidationResult.Option[]{ValidationResult.Option.DELETE}
+ : new ValidationResult.Option[]{ValidationResult.Option.DELETE, ValidationResult.Option.KEEP};
+ ValidationResult.Action action = myChecksum == Digester.INVALID ? ValidationResult.Action.VALIDATE : ValidationResult.Action.DELETE;
+ String message = myChecksum == Digester.INVALID ? "Unexpected file" : "Modified";
+ return new ValidationResult(ValidationResult.Kind.CONFLICT, myPath, action, message, options);
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean doShouldApply(File toDir) {
+ return getFile(toDir).exists();
+ }
+
+ @Override
+ protected void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException {
+ Utils.delete(toFile);
+ }
+
+ @Override
+ protected void doBackup(File toFile, File backupFile) throws IOException {
+ Utils.copy(toFile, backupFile);
+ }
+
+ @Override
+ protected void doRevert(File toFile, File backupFile) throws IOException {
+ if (!toFile.exists() || toFile.isDirectory() || isModified(toFile)) {
+ Utils.delete(toFile); // make sure there is no directory remained on this path (may remain from previous 'create' actions
+ Utils.copy(backupFile, toFile);
+ }
}
}
+++ /dev/null
-package com.intellij.updater;
-
-import java.io.DataInputStream;
-import java.io.File;
-import java.io.IOException;
-
-public class DeleteZipAction extends BaseDeleteAction {
- public DeleteZipAction(String path, long checksum) {
- super(path, checksum);
- }
-
- public DeleteZipAction(DataInputStream in) throws IOException {
- super(in);
- }
-
- @Override
- protected boolean isModified(File toFile) throws IOException {
- return myChecksum != Digester.digestFile(toFile);
- }
-}
package com.intellij.updater;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
+import java.io.File;
+import java.util.*;
public class DiffCalculator {
- public static Result calculate(Map<String, Long> oldChecksums, Map<String, Long> newChecksums) {
+ public static Result calculate(Map<String, Long> oldChecksums, Map<String, Long> newChecksums, List<String> critical, boolean move) {
Result result = new Result();
+ result.commonFiles = collect(oldChecksums, newChecksums, critical, true);
result.filesToDelete = withAllRemoved(oldChecksums, newChecksums);
- result.filesToCreate = withAllRemoved(newChecksums, oldChecksums).keySet();
- result.filesToUpdate = collect(oldChecksums, newChecksums, false);
+
+ Map<String, Long> toUpdate = collect(oldChecksums, newChecksums, critical, false);
+ Map<String, Long> toCreate = withAllRemoved(newChecksums, oldChecksums);
+
+ // Some creates become updates if found in different directories.
+ result.filesToCreate = new LinkedHashMap<String, Long>();
+ result.filesToUpdate = new LinkedHashMap<String, Update>();
+
+ for (Map.Entry<String, Long> update : toUpdate.entrySet()) {
+ result.filesToUpdate.put(update.getKey(), new Update(update.getKey(), update.getValue(), false));
+ }
+
+ if (move) {
+ Map<Long, String> byContent = inverse(result.filesToDelete);
+ Map<String, List<String>> byName = groupFilesByName(result.filesToDelete);
+
+ // Find first by content
+ for (Map.Entry<String, Long> create : toCreate.entrySet()) {
+ boolean isDir = create.getKey().endsWith("/");
+ String source = byContent.get(create.getValue());
+ boolean found = false;
+ if (source != null && !isDir) {
+ // Found a file with the same content use it, unless it's critical
+ if (!critical.contains(source)) {
+ result.filesToUpdate.put(create.getKey(), new Update(source, result.filesToDelete.get(source), true));
+ found = true;
+ }
+ }
+ else {
+ File fileToCreate = new File(create.getKey());
+ List<String> sameName = byName.get(fileToCreate.getName());
+ if (sameName != null && !isDir) {
+ String best = findBestCandidateForMove(sameName, create.getKey());
+ // Found a file with the same name, if it's not critical use it, worst case as big as a create.
+ if (!critical.contains(best)) {
+ result.filesToUpdate.put(create.getKey(), new Update(best, result.filesToDelete.get(best), false));
+ found = true;
+ }
+ }
+ }
+ if (!found) {
+ // Fine, just create it.
+ result.filesToCreate.put(create.getKey(), create.getValue());
+ }
+ }
+ } else {
+ result.filesToCreate = toCreate;
+ }
+
return result;
}
+ private static String findBestCandidateForMove(List<String> paths, String path) {
+ int common = 0;
+ String best = "";
+ String[] dirs = path.split("/");
+ for (String other : paths) {
+ String[] others = other.split("/");
+ for (int i = 0; i < dirs.length && i < others.length; i++) {
+ if (dirs[dirs.length - i - 1].equals(others[others.length - i - 1])) {
+ if (i + 1 > common) {
+ best = other;
+ common = i + 1;
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ return best;
+ }
+
+ private static Map<String, List<String>> groupFilesByName(Map<String, Long> toDelete) {
+ Map<String, List<String>> result = new HashMap<String, List<String>>();
+ for (String path : toDelete.keySet()) {
+ if (!path.endsWith("/")) {
+ String name = new File(path).getName();
+ List<String> paths = result.get(name);
+ if (paths == null) {
+ paths = new LinkedList<String>();
+ result.put(name, paths);
+ }
+ paths.add(path);
+ }
+ }
+ return result;
+ }
+
+ public static Map<Long,String> inverse(Map<String, Long> map) {
+ Map<Long, String> inv = new LinkedHashMap<Long, String>();
+ for (Map.Entry<String, Long> entry : map.entrySet()) {
+ inv.put(entry.getValue(), entry.getKey());
+ }
+ return inv;
+ }
+
private static Map<String, Long> withAllRemoved(Map<String, Long> from, Map<String, Long> toRemove) {
- Map<String, Long> result = new HashMap<String, Long>(from);
+ Map<String, Long> result = new LinkedHashMap<String, Long>(from);
for (String each : toRemove.keySet()) {
result.remove(each);
}
return result;
}
- private static Map<String, Long> collect(Map<String, Long> older, Map<String, Long> newer, boolean equal) {
- Map<String, Long> result = new HashMap<String, Long>();
+ private static Map<String, Long> collect(Map<String, Long> older, Map<String, Long> newer, List<String> critical, boolean equal) {
+ Map<String, Long> result = new LinkedHashMap<String, Long>();
for (Map.Entry<String, Long> each : newer.entrySet()) {
String file = each.getKey();
Long oldChecksum = older.get(file);
Long newChecksum = newer.get(file);
- if (oldChecksum != null && newChecksum != null && oldChecksum.equals(newChecksum) == equal) {
- result.put(file, oldChecksum);
+ if (oldChecksum != null && newChecksum != null) {
+ if ((oldChecksum.equals(newChecksum) && !critical.contains(file)) == equal) {
+ result.put(file, oldChecksum);
+ }
}
}
return result;
}
+ public static class Update {
+ public final String source;
+ public final long checksum;
+ public final boolean move;
+
+ public Update(String source, long checksum, boolean move) {
+ this.checksum = checksum;
+ this.source = source;
+ this.move = move;
+ }
+ }
+
public static class Result {
public Map<String, Long> filesToDelete;
- public Set<String> filesToCreate;
- public Map<String, Long> filesToUpdate;
+ public Map<String, Long> filesToCreate;
+ public Map<String, Update> filesToUpdate;
+ public Map<String, Long> commonFiles;
}
}
import java.util.*;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
import java.util.zip.ZipFile;
public class Digester {
- public static Map<String, Long> digestFiles(File dir, List<String> ignoredFiles, UpdaterUI ui)
- throws IOException, OperationCancelledException {
- Map<String, Long> result = new HashMap<String, Long>();
+ // CRC32 will only use the lower 32bits of long, never returning negative values.
+ public static long INVALID = -1;
+ public static long DIRECTORY = -2;
- LinkedHashSet<String> paths = Utils.collectRelativePaths(dir);
- for (String each : paths) {
- if (ignoredFiles.contains(each)) continue;
- ui.setStatus(each);
- ui.checkCancelled();
- result.put(each, digestFile(new File(dir, each)));
+ public static long digestRegularFile(File file, boolean normalize) throws IOException {
+ if (file.isDirectory()) {
+ return DIRECTORY;
}
- return result;
- }
-
- public static long digestFile(File file) throws IOException {
- if (!Runner.ZIP_AS_BINARY && Utils.isZipFile(file.getName())) {
- ZipFile zipFile;
- try {
- zipFile = new ZipFile(file);
- }
- catch (IOException e) {
- Runner.printStackTrace(e);
- return digestRegularFile(file);
- }
-
- try {
- return doDigestZipFile(zipFile);
- }
- finally {
- zipFile.close();
- }
- }
- return digestRegularFile(file);
- }
-
- public static long digestRegularFile(File file) throws IOException {
- InputStream in = new BufferedInputStream(new FileInputStream(file));
+ InputStream in = new BufferedInputStream(Utils.newFileInputStream(file, normalize));
try {
return digestStream(in);
}
}
}
- private static long doDigestZipFile(ZipFile zipFile) throws IOException {
- List<ZipEntry> sorted = new ArrayList<ZipEntry>();
-
- Enumeration<? extends ZipEntry> temp = zipFile.entries();
- while (temp.hasMoreElements()) {
- ZipEntry each = temp.nextElement();
- if (!each.isDirectory()) sorted.add(each);
+ public static long digestZipFile(File file) throws IOException {
+ ZipFile zipFile;
+ try {
+ zipFile = new ZipFile(file);
+ } catch (ZipException e) {
+ // This was not a zip file...
+ return digestRegularFile(file, false);
}
+ try {
+ List<ZipEntry> sorted = new ArrayList<ZipEntry>();
- Collections.sort(sorted, new Comparator<ZipEntry>() {
- public int compare(ZipEntry o1, ZipEntry o2) {
- return o1.getName().compareTo(o2.getName());
+ Enumeration<? extends ZipEntry> temp = zipFile.entries();
+ while (temp.hasMoreElements()) {
+ ZipEntry each = temp.nextElement();
+ if (!each.isDirectory()) sorted.add(each);
}
- });
- CRC32 crc = new CRC32();
- for (ZipEntry each : sorted) {
- InputStream in = zipFile.getInputStream(each);
- try {
- doDigestStream(in, crc);
- }
- finally {
- in.close();
+ Collections.sort(sorted, new Comparator<ZipEntry>() {
+ public int compare(ZipEntry o1, ZipEntry o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ });
+
+ CRC32 crc = new CRC32();
+ for (ZipEntry each : sorted) {
+ InputStream in = zipFile.getInputStream(each);
+ try {
+ doDigestStream(in, crc);
+ }
+ finally {
+ in.close();
+ }
}
+ return crc.getValue();
+ } finally {
+ zipFile.close();
}
- return crc.getValue();
}
public static long digestStream(InputStream in) throws IOException {
public class Patch {
private List<PatchAction> myActions = new ArrayList<PatchAction>();
+ private boolean myIsBinary;
+ private boolean myIsStrict;
+ private boolean myIsNormalized;
+ private String myOldBuild;
+ private String myNewBuild;
+ private String myRoot;
+ private Map<String, String> myWarnings;
+ private List<String> myDeleteFiles;
private static final int CREATE_ACTION_KEY = 1;
private static final int UPDATE_ACTION_KEY = 2;
private static final int UPDATE_ZIP_ACTION_KEY = 3;
private static final int DELETE_ACTION_KEY = 4;
- private static final int DELETE_ZIP_ACTION_KEY = 5;
-
- public Patch(File olderDir,
- File newerDir,
- List<String> ignoredFiles,
- List<String> criticalFiles,
- List<String> optionalFiles,
- UpdaterUI ui) throws IOException, OperationCancelledException {
- calculateActions(olderDir, newerDir, ignoredFiles, criticalFiles, optionalFiles, ui);
+ private static final int VALIDATE_ACTION_KEY = 5;
+
+ public Patch(PatchSpec spec, UpdaterUI ui) throws IOException, OperationCancelledException {
+ myIsBinary = spec.isBinary();
+ myIsStrict = spec.isStrict();
+ myIsNormalized = spec.isNormalized();
+ myOldBuild = spec.getOldVersionDescription();
+ myNewBuild = spec.getNewVersionDescription();
+ myWarnings = spec.getWarnings();
+ myDeleteFiles = spec.getDeleteFiles();
+ myRoot = spec.getRoot();
+
+ calculateActions(spec, ui);
}
public Patch(InputStream patchIn) throws IOException {
read(patchIn);
}
- private void calculateActions(File olderDir,
- File newerDir,
- List<String> ignoredFiles,
- List<String> criticalFiles,
- List<String> optionalFiles,
- UpdaterUI ui)
- throws IOException, OperationCancelledException {
- DiffCalculator.Result diff;
-
+ private void calculateActions(PatchSpec spec, UpdaterUI ui) throws IOException, OperationCancelledException {
Runner.logger.info("Calculating difference...");
ui.startProcess("Calculating difference...");
ui.checkCancelled();
- diff = DiffCalculator.calculate(Digester.digestFiles(olderDir, ignoredFiles, ui),
- Digester.digestFiles(newerDir, ignoredFiles, ui));
+ File olderDir = new File(spec.getOldFolder());
+ File newerDir = new File(spec.getNewFolder());
+ DiffCalculator.Result diff;
+ diff = DiffCalculator.calculate(digestFiles(olderDir, spec.getIgnoredFiles(), isNormalized(), ui),
+ digestFiles(newerDir, spec.getIgnoredFiles(), false, ui),
+ spec.getCriticalFiles(), true);
List<PatchAction> tempActions = new ArrayList<PatchAction>();
// 'delete' actions before 'create' actions to prevent newly created files to be deleted if the names differ only on case.
for (Map.Entry<String, Long> each : diff.filesToDelete.entrySet()) {
- if (!Runner.ZIP_AS_BINARY && Utils.isZipFile(each.getKey())) {
- tempActions.add(new DeleteZipAction(each.getKey(), each.getValue()));
- } else
- {
- tempActions.add(new DeleteAction(each.getKey(), each.getValue()));
- }
+ // Add them in reverse order so directory structures start deleting the files before the directory itself.
+ tempActions.add(0, new DeleteAction(this, each.getKey(), each.getValue()));
}
- for (String each : diff.filesToCreate) {
- tempActions.add(new CreateAction(each));
+ for (String each : diff.filesToCreate.keySet()) {
+ tempActions.add(new CreateAction(this, each));
}
- for (Map.Entry<String, Long> each : diff.filesToUpdate.entrySet()) {
- if (!Runner.ZIP_AS_BINARY && Utils.isZipFile(each.getKey())) {
- tempActions.add(new UpdateZipAction(each.getKey(), each.getValue()));
+ for (Map.Entry<String, DiffCalculator.Update> each : diff.filesToUpdate.entrySet()) {
+ DiffCalculator.Update update = each.getValue();
+ if (!spec.isBinary() && Utils.isZipFile(each.getKey())) {
+ tempActions.add(new UpdateZipAction(this, each.getKey(), update.source, update.checksum, update.move));
}
else {
- tempActions.add(new UpdateAction(each.getKey(), each.getValue()));
+ tempActions.add(new UpdateAction(this, each.getKey(), update.source, update.checksum, update.move));
+ }
+ }
+
+ if (spec.isStrict()) {
+ for (Map.Entry<String, Long> each : diff.commonFiles.entrySet()) {
+ tempActions.add(new ValidateAction(this, each.getKey(), each.getValue()));
}
}
if (!each.calculate(olderDir, newerDir)) continue;
myActions.add(each);
- each.setCritical(criticalFiles.contains(each.getPath()));
- each.setOptional(optionalFiles.contains(each.getPath()));
+ each.setCritical(spec.getCriticalFiles().contains(each.getPath()));
+ each.setOptional(spec.getOptionalFiles().contains(each.getPath()));
}
}
public void write(OutputStream out) throws IOException {
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed") DataOutputStream dataOut = new DataOutputStream(out);
try {
- dataOut.writeInt(myActions.size());
-
- for (PatchAction each : myActions) {
- int key;
- Class clazz = each.getClass();
-
- if (clazz == CreateAction.class) {
- key = CREATE_ACTION_KEY;
- }
- else if (clazz == UpdateAction.class) {
- key = UPDATE_ACTION_KEY;
- }
- else if (clazz == UpdateZipAction.class) {
- key = UPDATE_ZIP_ACTION_KEY;
- }
- else if (clazz == DeleteZipAction.class) {
- key = DELETE_ZIP_ACTION_KEY;
- }
- else if (clazz == DeleteAction.class) {
- key = DELETE_ACTION_KEY;
- }
- else {
- throw new RuntimeException("Unknown action " + each);
- }
- dataOut.writeInt(key);
- each.write(dataOut);
- }
+ dataOut.writeUTF(myOldBuild);
+ dataOut.writeUTF(myNewBuild);
+ dataOut.writeUTF(myRoot);
+ dataOut.writeBoolean(myIsBinary);
+ dataOut.writeBoolean(myIsStrict);
+ dataOut.writeBoolean(myIsNormalized);
+ writeMap(dataOut, myWarnings);
+ writeList(dataOut, myDeleteFiles);
+ writeActions(dataOut, myActions);
}
finally {
dataOut.flush();
}
}
+ private static void writeList(DataOutputStream dataOut, List<String> list) throws IOException {
+ dataOut.writeInt(list.size());
+ for (String string : list) {
+ dataOut.writeUTF(string);
+ }
+ }
+
+ private static void writeMap(DataOutputStream dataOut, Map<String, String> map) throws IOException {
+ dataOut.writeInt(map.size());
+ for (Map.Entry<String, String> entry : map.entrySet()) {
+ dataOut.writeUTF(entry.getKey());
+ dataOut.writeUTF(entry.getValue());
+ }
+ }
+
+ private void writeActions(DataOutputStream dataOut, List<PatchAction> actions) throws IOException {
+ dataOut.writeInt(actions.size());
+
+ for (PatchAction each : actions) {
+ int key;
+ Class clazz = each.getClass();
+
+ if (clazz == CreateAction.class) {
+ key = CREATE_ACTION_KEY;
+ }
+ else if (clazz == UpdateAction.class) {
+ key = UPDATE_ACTION_KEY;
+ }
+ else if (clazz == UpdateZipAction.class) {
+ key = UPDATE_ZIP_ACTION_KEY;
+ }
+ else if (clazz == DeleteAction.class) {
+ key = DELETE_ACTION_KEY;
+ }
+ else if (clazz == ValidateAction.class) {
+ key = VALIDATE_ACTION_KEY;
+ }
+ else {
+ throw new RuntimeException("Unknown action " + each);
+ }
+ dataOut.writeInt(key);
+ each.write(dataOut);
+ }
+ }
+
private void read(InputStream patchIn) throws IOException {
- List<PatchAction> newActions = new ArrayList<PatchAction>();
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed") DataInputStream in = new DataInputStream(patchIn);
+
+ myOldBuild = in.readUTF();
+ myNewBuild = in.readUTF();
+ myRoot = in.readUTF();
+ myIsBinary = in.readBoolean();
+ myIsStrict = in.readBoolean();
+ myIsNormalized = in.readBoolean();
+ myWarnings = readMap(in);
+ myDeleteFiles = readList(in);
+ myActions = readActions(in);
+ }
+
+ private static List<String> readList(DataInputStream in) throws IOException {
int size = in.readInt();
+ List<String> list = new ArrayList<String>(size);
+ for (int i = 0; i < size; i++) {
+ list.add(in.readUTF());
+ }
+ return list;
+ }
+ private static Map<String, String> readMap(DataInputStream in) throws IOException {
+ int size = in.readInt();
+ Map<String, String> map = new HashMap<String, String>();
+ for (int i = 0; i < size; i++) {
+ String key = in.readUTF();
+ map.put(key, in.readUTF());
+ }
+ return map;
+ }
+
+ private List<PatchAction> readActions(DataInputStream in) throws IOException {
+ List<PatchAction> actions = new ArrayList<PatchAction>();
+ int size = in.readInt();
while (size-- > 0) {
int key = in.readInt();
PatchAction a;
switch (key) {
case CREATE_ACTION_KEY:
- a = new CreateAction(in);
+ a = new CreateAction(this, in);
break;
case UPDATE_ACTION_KEY:
- a = new UpdateAction(in);
+ a = new UpdateAction(this, in);
break;
case UPDATE_ZIP_ACTION_KEY:
- a = new UpdateZipAction(in);
+ a = new UpdateZipAction(this, in);
break;
case DELETE_ACTION_KEY:
- a = new DeleteAction(in);
+ a = new DeleteAction(this, in);
break;
- case DELETE_ZIP_ACTION_KEY:
- a = new DeleteZipAction(in);
+ case VALIDATE_ACTION_KEY:
+ a = new ValidateAction(this, in);
break;
default:
throw new RuntimeException("Unknown action type " + key);
}
- newActions.add(a);
+ actions.add(a);
}
+ return actions;
+ }
- myActions = newActions;
+ private File toBaseDir(File toDir) throws IOException {
+ // This removes myRoot from the end of toDir. myRoot is expressed with '/' so converting to URI to normalize separators.
+ String path = toDir.toURI().getPath();
+ if (!path.endsWith(myRoot)) {
+ throw new IOException("The patch must be applied to the root folder " + myRoot);
+ }
+ return new File(path.substring(0, path.length() - myRoot.length()));
}
- public List<ValidationResult> validate(final File toDir, UpdaterUI ui) throws IOException, OperationCancelledException {
- final LinkedHashSet<String> files = Utils.collectRelativePaths(toDir);
+ public List<ValidationResult> validate(final File rootDir, UpdaterUI ui) throws IOException, OperationCancelledException {
+ LinkedHashSet<String> files = null;
+ final File toDir = toBaseDir(rootDir);
+ boolean checkWarnings = true;
+ while (checkWarnings) {
+ files = Utils.collectRelativePaths(toDir, myIsStrict);
+ checkWarnings = false;
+ for (String file : files) {
+ String warning = myWarnings.get(file);
+ if (warning != null) {
+ if (!ui.showWarning(warning)) {
+ throw new OperationCancelledException();
+ }
+ checkWarnings = true;
+ break;
+ }
+ }
+ }
+
final List<ValidationResult> result = new ArrayList<ValidationResult>();
+ if (myIsStrict) {
+ // In strict mode add delete actions for unknown files.
+ for (PatchAction action : myActions) {
+ files.remove(action.getPath());
+ }
+ for (String file : files) {
+ myActions.add(0, new DeleteAction(this, file, Digester.INVALID));
+ }
+ }
Runner.logger.info("Validating installation...");
forEach(myActions, "Validating installation...", ui, true,
new ActionsProcessor() {
+ @Override
public void forEach(PatchAction each) throws IOException {
ValidationResult validationResult = each.validate(toDir);
if (validationResult != null) result.add(validationResult);
- files.remove(each.getPath());
}
});
- //for (String each : files) {
- // result.add(new ValidationResult(ValidationResult.Kind.INFO,
- // each,
- // ValidationResult.Action.NO_ACTION,
- // ValidationResult.MANUALLY_ADDED_MESSAGE,
- // ValidationResult.Option.KEEP, ValidationResult.Option.DELETE));
- //}
-
return result;
}
public ApplicationResult apply(final ZipFile patchFile,
- final File toDir,
+ final File rootDir,
final File backupDir,
final Map<String, ValidationResult.Option> options,
UpdaterUI ui) throws IOException, OperationCancelledException {
+ final File toDir = toBaseDir(rootDir);
List<PatchAction> actionsToProcess = new ArrayList<PatchAction>();
for (PatchAction each : myActions) {
if (each.shouldApply(toDir, options)) actionsToProcess.add(each);
forEach(actionsToProcess, "Backing up files...", ui, true,
new ActionsProcessor() {
+ @Override
public void forEach(PatchAction each) throws IOException {
each.backup(toDir, backupDir);
}
try {
forEach(actionsToProcess, "Applying patch...", ui, true,
new ActionsProcessor() {
+ @Override
public void forEach(PatchAction each) throws IOException {
appliedActions.add(each);
- each.apply(patchFile, toDir);
+ each.apply(patchFile, backupDir, toDir);
}
});
}
}
if (shouldRevert) {
- revert(appliedActions, backupDir, toDir, ui);
+ revert(appliedActions, backupDir, rootDir, ui);
appliedActions.clear();
if (cancelled) throw new OperationCancelledException();
return new ApplicationResult(appliedActions);
}
- public void revert(List<PatchAction> actions, final File backupDir, final File toDir, UpdaterUI ui)
+ public void revert(List<PatchAction> actions, final File backupDir, final File rootDir, UpdaterUI ui)
throws OperationCancelledException, IOException {
Collections.reverse(actions);
+ final File toDir = toBaseDir(rootDir);
forEach(actions, "Reverting...", ui, false,
new ActionsProcessor() {
+ @Override
public void forEach(PatchAction each) throws IOException {
each.revert(toDir, backupDir);
}
}
}
+ public long digestFile(File toFile, boolean normalize) throws IOException {
+ if (!myIsBinary && Utils.isZipFile(toFile.getName())) {
+ return Digester.digestZipFile(toFile);
+ }
+ else {
+ return Digester.digestRegularFile(toFile, normalize);
+ }
+ }
+
+ public Map<String, Long> digestFiles(File dir, List<String> ignoredFiles, boolean normalize, UpdaterUI ui)
+ throws IOException, OperationCancelledException {
+ Map<String, Long> result = new LinkedHashMap<String, Long>();
+
+ LinkedHashSet<String> paths = Utils.collectRelativePaths(dir, myIsStrict);
+ for (String each : paths) {
+ if (ignoredFiles.contains(each)) continue;
+ ui.setStatus(each);
+ ui.checkCancelled();
+ result.put(each, digestFile(new File(dir, each), normalize));
+ }
+ return result;
+ }
+
+ public String getOldBuild() {
+ return myOldBuild;
+ }
+
+ public String getNewBuild() {
+ return myNewBuild;
+ }
+
+ public boolean isStrict() {
+ return myIsStrict;
+ }
+
+ public boolean isNormalized() {
+ return myIsNormalized;
+ }
+
+ public boolean validateDeletion(String path) {
+ for (String delete : myDeleteFiles) {
+ if (path.matches(delete)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public interface ActionsProcessor {
void forEach(PatchAction each) throws IOException;
}
protected long myChecksum;
private boolean isCritical;
private boolean isOptional;
+ protected transient Patch myPatch;
- public PatchAction(String path, long checksum) {
- myPath = path;
+ public PatchAction(Patch patch, String path, long checksum) {
+ myPatch = patch;
myChecksum = checksum;
+ myPath = path;
}
- public PatchAction(DataInputStream in) throws IOException {
+ public PatchAction(Patch patch, DataInputStream in) throws IOException {
+ myPatch = patch;
myPath = in.readUTF();
myChecksum = in.readLong();
isCritical = in.readBoolean();
process.terminate();
}
}
- return shouldApplyOn(file);
+ return doShouldApply(toDir);
}
- protected boolean shouldApplyOn(File toFile) {
+ protected boolean doShouldApply(File toDir) {
return true;
}
- public ValidationResult validate(File toDir) throws IOException {
- return doValidate(getFile(toDir));
- }
-
- protected abstract ValidationResult doValidate(final File toFile) throws IOException;
+ protected abstract ValidationResult validate(File toDir) throws IOException;
protected ValidationResult doValidateAccess(File toFile, ValidationResult.Action action) {
if (!toFile.exists()) return null;
myPath,
action,
ValidationResult.ACCESS_DENIED_MESSAGE,
- ValidationResult.Option.IGNORE);
+ myPatch.isStrict() ? ValidationResult.Option.NONE : ValidationResult.Option.IGNORE);
}
private boolean isWritable(File toFile) {
throws IOException {
if (toFile.exists()) {
if (isModified(toFile)) {
+ ValidationResult.Option[] options;
+ if (myPatch.isStrict()) {
+ if (isCritical) {
+ options = new ValidationResult.Option[]{ ValidationResult.Option.REPLACE };
+ }
+ else {
+ options = new ValidationResult.Option[]{ ValidationResult.Option.NONE };
+ }
+ } else {
+ if (isCritical) {
+ options = new ValidationResult.Option[]{ ValidationResult.Option.REPLACE, ValidationResult.Option.IGNORE };
+ }
+ else {
+ options = new ValidationResult.Option[]{ ValidationResult.Option.IGNORE };
+ }
+ }
return new ValidationResult(kind,
myPath,
action,
ValidationResult.MODIFIED_MESSAGE,
- ValidationResult.Option.IGNORE);
+ options);
}
}
else if (!isOptional) {
myPath,
action,
ValidationResult.ABSENT_MESSAGE,
- ValidationResult.Option.IGNORE);
+ myPatch.isStrict() ? ValidationResult.Option.NONE : ValidationResult.Option.IGNORE);
}
return null;
}
- abstract protected boolean isModified(File toFile) throws IOException;
+ protected boolean isModified(File toFile) throws IOException {
+ return myChecksum == Digester.INVALID || myChecksum != myPatch.digestFile(toFile, myPatch.isNormalized());
+ }
- public void apply(ZipFile patchFile, File toDir) throws IOException {
- doApply(patchFile, getFile(toDir));
+ public void apply(ZipFile patchFile, File backupDir, File toDir) throws IOException {
+ doApply(patchFile, backupDir, getFile(toDir));
}
- protected abstract void doApply(ZipFile patchFile, File toFile) throws IOException;
+ protected abstract void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException;
public void backup(File toDir, File backupDir) throws IOException {
doBackup(getFile(toDir), getFile(backupDir));
protected abstract void doRevert(File toFile, File backupFile) throws IOException;
- private File getFile(File baseDir) {
+ protected File getFile(File baseDir) {
return new File(baseDir, myPath);
}
package com.intellij.updater;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
+import java.io.*;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
public class PatchFileCreator {
private static final String PATCH_INFO_FILE_NAME = ".patch-info";
- public static void create(File olderDir,
- File newerDir,
- File patchFile,
- List<String> ignoredFiles,
- List<String> criticalFiles,
- List<String> optionalFiles,
- UpdaterUI ui) throws IOException, OperationCancelledException {
+ public static Patch create(PatchSpec spec, File patchFile, UpdaterUI ui) throws IOException, OperationCancelledException {
- Patch patchInfo = new Patch(olderDir, newerDir, ignoredFiles, criticalFiles, optionalFiles, ui);
+ Patch patchInfo = new Patch(spec, ui);
Runner.logger.info("Creating the patch file '" + patchFile + "'...");
ui.startProcess("Creating the patch file '" + patchFile + "'...");
ui.checkCancelled();
patchInfo.write(out);
out.closeEntry();
+ File olderDir = new File(spec.getOldFolder());
+ File newerDir = new File(spec.getNewFolder());
List<PatchAction> actions = patchInfo.getActions();
for (PatchAction each : actions) {
finally {
out.close();
}
+
+ return patchInfo;
}
public static PreparationResult prepareAndValidate(File patchFile,
ZipFile zipFile = new ZipFile(patchFile);
try {
+
InputStream in = Utils.getEntryInputStream(zipFile, PATCH_INFO_FILE_NAME);
try {
patch = new Patch(in);
zipFile.close();
}
+ ui.setDescription(patch.getOldBuild(), patch.getNewBuild());
+
List<ValidationResult> validationResults = patch.validate(toDir, ui);
return new PreparationResult(patch, patchFile, toDir, validationResults);
}
--- /dev/null
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.updater;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class PatchSpec {
+ private String myOldVersionDescription = "";
+ private String myNewVersionDescription = "";
+ private String myOldFolder;
+ private String myNewFolder;
+ private String myPatchFile;
+ private String myJarFile;
+ private boolean myIsBinary;
+ private boolean myIsStrict;
+ private List<String> myIgnoredFiles = Collections.emptyList();
+ private List<String> myCriticalFiles = Collections.emptyList();
+ private List<String> myOptionalFiles = Collections.emptyList();
+ private boolean myIsNormalized;
+ private Map<String, String> myWarnings = Collections.emptyMap();
+ private List<String> myDeleteFiles = Collections.emptyList();
+ private String myRoot = "";
+
+ public String getOldVersionDescription() {
+ return myOldVersionDescription;
+ }
+
+ public PatchSpec setOldVersionDescription(String oldVersionDescription) {
+ myOldVersionDescription = oldVersionDescription;
+ return this;
+ }
+
+ public String getNewVersionDescription() {
+ return myNewVersionDescription;
+ }
+
+ public PatchSpec setNewVersionDescription(String newVersionDescription) {
+ myNewVersionDescription = newVersionDescription;
+ return this;
+ }
+
+ public String getOldFolder() {
+ return myOldFolder;
+ }
+
+ public PatchSpec setOldFolder(String oldFolder) {
+ myOldFolder = oldFolder;
+ return this;
+ }
+
+ public String getNewFolder() {
+ return myNewFolder;
+ }
+
+ public PatchSpec setNewFolder(String newFolder) {
+ myNewFolder = newFolder;
+ return this;
+ }
+
+ public String getPatchFile() {
+ return myPatchFile;
+ }
+
+ public PatchSpec setPatchFile(String patchFile) {
+ myPatchFile = patchFile;
+ return this;
+ }
+
+ public String getJarFile() {
+ return myJarFile;
+ }
+
+ public PatchSpec setJarFile(String jarFile) {
+ myJarFile = jarFile;
+ return this;
+ }
+
+ public boolean isStrict() {
+ return myIsStrict;
+ }
+
+ public PatchSpec setStrict(boolean strict) {
+ myIsStrict = strict;
+ return this;
+ }
+
+ public List<String> getIgnoredFiles() {
+ return myIgnoredFiles;
+ }
+
+ public PatchSpec setIgnoredFiles(List<String> ignoredFiles) {
+ myIgnoredFiles = ignoredFiles;
+ return this;
+ }
+
+ public List<String> getCriticalFiles() {
+ return myCriticalFiles;
+ }
+
+ public PatchSpec setCriticalFiles(List<String> criticalFiles) {
+ myCriticalFiles = criticalFiles;
+ return this;
+ }
+
+ public List<String> getOptionalFiles() {
+ return myOptionalFiles;
+ }
+
+ public PatchSpec setOptionalFiles(List<String> optionalFiles) {
+ myOptionalFiles = optionalFiles;
+ return this;
+ }
+
+ public PatchSpec setBinary(boolean binary) {
+ myIsBinary = binary;
+ return this;
+ }
+
+ public boolean isBinary() {
+ return myIsBinary;
+ }
+
+ public boolean isNormalized() {
+ return myIsNormalized;
+ }
+
+ public PatchSpec setNormalized(boolean normalized) {
+ myIsNormalized = normalized;
+ return this;
+ }
+
+ public PatchSpec setWarnings(Map<String, String> warnings) {
+ myWarnings = warnings;
+ return this;
+ }
+
+ public Map<String, String> getWarnings() {
+ return myWarnings;
+ }
+
+ public PatchSpec setDeleteFiles(List<String> deleteFiles) {
+ myDeleteFiles = deleteFiles;
+ return this;
+ }
+
+ public List<String> getDeleteFiles() {
+ return myDeleteFiles;
+ }
+
+ public PatchSpec setRoot(String root) {
+ myRoot = root;
+ return this;
+ }
+
+ public String getRoot() {
+ return myRoot;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.updater;
+
+import java.io.IOException;
+
+/**
+ * Exception thrown when an IOException arises when performing a patch
+ * action and it's likely that retrying will be successful.
+ */
+public class RetryException extends IOException {
+ public RetryException() {
+ }
+
+ public RetryException(String message) {
+ super(message);
+ }
+
+ public RetryException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public RetryException(Throwable cause) {
+ super(cause);
+ }
+}
public class Runner {
public static Logger logger = null;
- /**
- * Treats zip files as regular binary files. When false, zip/jar files are unzipped and diffed file by file.
- * When true, the entire zip file is diffed as a single file. Set to true if preserving the timestamps of
- * the files inside the zip is important. This variable can change via a command line option.
- */
- public static boolean ZIP_AS_BINARY = false;
-
private static final String PATCH_FILE_NAME = "patch-file.zip";
- private static final String PATCH_PROPERTIES_ENTRY = "patch.properties";
- private static final String OLD_BUILD_DESCRIPTION = "old.build.description";
- private static final String NEW_BUILD_DESCRIPTION = "new.build.description";
public static void main(String[] args) throws Exception {
+
+ String jarFile = getArgument(args, "jar");
+ jarFile = jarFile == null ? resolveJarFile() : jarFile;
+
if (args.length >= 6 && "create".equals(args[0])) {
String oldVersionDesc = args[1];
String newVersionDesc = args[2];
String patchFile = args[5];
initLogger();
- ZIP_AS_BINARY = Arrays.asList(args).contains("--zip_as_binary");
-
- List<String> ignoredFiles = extractFiles(args, "ignored");
- List<String> criticalFiles = extractFiles(args, "critical");
- List<String> optionalFiles = extractFiles(args, "optional");
- create(oldVersionDesc, newVersionDesc, oldFolder, newFolder, patchFile, ignoredFiles, criticalFiles, optionalFiles);
+ // See usage for an explanation of these flags
+ boolean binary = Arrays.asList(args).contains("--zip_as_binary");
+ boolean strict = Arrays.asList(args).contains("--strict");
+ boolean normalized = Arrays.asList(args).contains("--normalized");
+
+ String root = getArgument(args, "root");
+ root = root == null ? "" : (root.endsWith("/") ? root : root + "/");
+
+ List<String> ignoredFiles = extractArguments(args, "ignored");
+ List<String> criticalFiles = extractArguments(args, "critical");
+ List<String> optionalFiles = extractArguments(args, "optional");
+ List<String> deleteFiles = extractArguments(args, "delete");
+ Map<String, String> warnings = buildWarningMap(extractArguments(args, "warning"));
+
+ PatchSpec spec = new PatchSpec()
+ .setOldVersionDescription(oldVersionDesc)
+ .setNewVersionDescription(newVersionDesc)
+ .setRoot(root)
+ .setOldFolder(oldFolder)
+ .setNewFolder(newFolder)
+ .setPatchFile(patchFile)
+ .setJarFile(jarFile)
+ .setStrict(strict)
+ .setBinary(binary)
+ .setNormalized(normalized)
+ .setIgnoredFiles(ignoredFiles)
+ .setCriticalFiles(criticalFiles)
+ .setOptionalFiles(optionalFiles)
+ .setDeleteFiles(deleteFiles)
+ .setWarnings(warnings);
+
+ create(spec);
}
else if (args.length >= 2 && "install".equals(args[0])) {
String destFolder = args[1];
initLogger();
logger.info("destFolder: " + destFolder);
- install(destFolder);
+ install(jarFile, destFolder);
}
else {
printUsage();
}
}
+ private static Map<String, String> buildWarningMap(List<String> warnings) {
+ Map<String, String> map = new HashMap<String, String>();
+ for (String warning : warnings) {
+ int ix = warning.indexOf(":");
+ if (ix != -1) {
+ String path = warning.substring(0, ix);
+ String message = warning.substring(ix + 1).replace("\\n","\n");
+ map.put(path, message);
+ }
+ }
+ return map;
+ }
+
// checks that log directory 1)exists 2)has write perm. and 3)has 1MB+ free space
private static boolean isValidLogDir(String logFolder) {
File fileLogDir = new File(logFolder);
logger.error(e.getMessage(), e);
}
- public static List<String> extractFiles(String[] args, String paramName) {
+ public static String getArgument(String[] args, String name) {
+ String flag = "--" + name + "=";
+ for (String param : args) {
+ if (param.startsWith(flag)) {
+ return param.substring(flag.length());
+ }
+ }
+ return null;
+ }
+
+ public static List<String> extractArguments(String[] args, String paramName) {
List<String> result = new ArrayList<String>();
for (String param : args) {
if (param.startsWith(paramName + "=")) {
@SuppressWarnings("UseOfSystemOutOrSystemErr")
private static void printUsage() {
- System.err.println("Usage:\n" +
- "create <old_version_description> <new_version_description> <old_version_folder> <new_version_folder>" +
- " <patch_file_name> [ignored=file1;file2;...] [critical=file1;file2;...] [optional=file1;file2;...]\n" +
- "install <destination_folder>\n");
+ System.err.println(
+ "Usage:\n" +
+ " Runner create <old_version> <new_version> <old_folder> <new_folder> <patch_file> [<file_set>=file1;file2;...] [<flags>]\n" +
+ " Runner install <folder>\n" +
+ "\n" +
+ "Where:\n" +
+ " <old_version>: A description of the version to generate the patch from.\n" +
+ " <new_version>: A description of the version to generate the patch to.\n" +
+ " <old_folder>: The folder where to find the old version.\n" +
+ " <new_folder>: The folder where to find the new version.\n" +
+ " <patch_file>: The .jar patch file to create which contains the patch and the patcher.\n" +
+ " <file_set>: Can be one of:\n" +
+ " ignored: The set of files that will not be included in the patch.\n" +
+ " critical: Fully included in the patch, so they can be replaced at destination even if they have changed.\n" +
+ " optional: A set of files that is ok for them no to exist when applying the patch.\n" +
+ " delete: A set of regular expressions for paths that is safe to delete without user confirmation.\n" +
+ " <flags>: Can be:\n" +
+ " --zip_as_binary: Zip and jar files will be treated as binary files and not inspected internally.\n" +
+ " --strict: The created patch will contain extra information to fully validate an installation. A strict\n" +
+ " 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" +
+ " --root=<dir>: Sets dir as the root directory of the patch. The root directory is the directory where the patch should be" +
+ " applied to. For example on Mac, you can diff the two .app folders and set Contents as the root." +
+ " The root directory is relative to <old_folder> and uses forwards-slashes as separators." +
+ " --normalized: This creates a normalized patch. This flag only makes sense in addition to --zip_as_binary\n" +
+ " A normalized patch must be used to move from an installation that was patched\n" +
+ " in a non-binary way to a fully binary patch. This will yield a larger patch, but the\n" +
+ " generated patch can be applied on versions where non-binary patches have been applied to and it\n" +
+ " guarantees that the patched version will match exactly the original one.\n");
}
- private static void create(String oldBuildDesc,
- String newBuildDesc,
- String oldFolder,
- String newFolder,
- String patchFile,
- List<String> ignoredFiles,
- List<String> criticalFiles,
- List<String> optionalFiles) throws IOException, OperationCancelledException {
+ private static void create(PatchSpec spec) throws IOException, OperationCancelledException {
UpdaterUI ui = new ConsoleUpdaterUI();
try {
File tempPatchFile = Utils.createTempFile();
- PatchFileCreator.create(new File(oldFolder),
- new File(newFolder),
- tempPatchFile,
- ignoredFiles,
- criticalFiles,
- optionalFiles,
- ui);
-
- logger.info("Packing JAR file: " + patchFile );
- ui.startProcess("Packing JAR file '" + patchFile + "'...");
-
- FileOutputStream fileOut = new FileOutputStream(patchFile);
+ PatchFileCreator.create(spec, tempPatchFile, ui);
+
+ logger.info("Packing JAR file: " + spec.getPatchFile() );
+ ui.startProcess("Packing JAR file '" + spec.getPatchFile() + "'...");
+
+ FileOutputStream fileOut = new FileOutputStream(spec.getPatchFile());
try {
ZipOutputWrapper out = new ZipOutputWrapper(fileOut);
- ZipInputStream in = new ZipInputStream(new FileInputStream(resolveJarFile()));
+ ZipInputStream in = new ZipInputStream(new FileInputStream(new File(spec.getJarFile())));
try {
ZipEntry e;
while ((e = in.getNextEntry()) != null) {
in.close();
}
- ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
- try {
- Properties props = new Properties();
- props.setProperty(OLD_BUILD_DESCRIPTION, oldBuildDesc);
- props.setProperty(NEW_BUILD_DESCRIPTION, newBuildDesc);
- props.store(byteOut, "");
- }
- finally {
- byteOut.close();
- }
-
- out.zipBytes(PATCH_PROPERTIES_ENTRY, byteOut);
out.zipFile(PATCH_FILE_NAME, tempPatchFile);
out.finish();
}
Utils.cleanup();
}
- private static void install(final String destFolder) throws Exception {
- InputStream in = Runner.class.getResourceAsStream("/" + PATCH_PROPERTIES_ENTRY);
- Properties props = new Properties();
- if (in != null) {
- try {
- props.load(in);
- }
- finally {
- in.close();
- }
- }
-
+ private static void install(final String jarFile, final String destFolder) throws Exception {
// todo[r.sh] to delete in IDEA 14 (after a full circle of platform updates)
if (System.getProperty("swing.defaultlaf") == null) {
SwingUtilities.invokeAndWait(new Runnable() {
});
}
- new SwingUpdaterUI(props.getProperty(OLD_BUILD_DESCRIPTION),
- props.getProperty(NEW_BUILD_DESCRIPTION),
- new SwingUpdaterUI.InstallOperation() {
- public boolean execute(UpdaterUI ui) throws OperationCancelledException {
- logger.info("installing patch to the " + destFolder);
- return doInstall(ui, destFolder);
- }
- });
+ new SwingUpdaterUI(new SwingUpdaterUI.InstallOperation() {
+ public boolean execute(UpdaterUI ui) throws OperationCancelledException {
+ logger.info("installing patch to the " + destFolder);
+ return doInstall(jarFile, ui, destFolder);
+ }
+ });
}
- private static boolean doInstall(UpdaterUI ui, String destFolder) throws OperationCancelledException {
+ private static boolean doInstall(String jarFile, UpdaterUI ui, String destFolder) throws OperationCancelledException {
try {
try {
File patchFile = Utils.createTempFile();
- ZipFile jarFile = new ZipFile(resolveJarFile());
+ ZipFile zipFile = new ZipFile(jarFile);
logger.info("Extracting patch file...");
ui.startProcess("Extracting patch file...");
ui.setProgressIndeterminate();
try {
- InputStream in = Utils.getEntryInputStream(jarFile, PATCH_FILE_NAME);
+ InputStream in = Utils.getEntryInputStream(zipFile, PATCH_FILE_NAME);
OutputStream out = new BufferedOutputStream(new FileOutputStream(patchFile));
try {
Utils.copyStream(in, out);
}
}
finally {
- jarFile.close();
+ zipFile.close();
}
ui.checkCancelled();
}
finally {
try {
+ System.gc();
cleanup(ui);
}
catch (IOException e) {
return false;
}
- private static File resolveJarFile() throws IOException {
- String jar = System.getProperty("JAR_FILE");
- if (jar != null) {
- return new File(jar);
- }
+ private static String resolveJarFile() throws IOException {
URL url = Runner.class.getResource("");
if (url == null) throw new IOException("Cannot resolve JAR file path");
if (!"jar".equals(url.getProtocol())) throw new IOException("Patch file is not a JAR file");
String jarFileUrl = path.substring(start, end);
try {
- return new File(new URI(jarFileUrl));
+ return new File(new URI(jarFileUrl)).getAbsolutePath();
}
catch (URISyntaxException e) {
printStackTrace(e);
private final JFrame myFrame;
private boolean myApplied;
- public SwingUpdaterUI(String oldBuildDesc, String newBuildDesc, InstallOperation operation) {
+ public SwingUpdaterUI(InstallOperation operation) {
myOperation = operation;
myProcessTitle = new JLabel(" ");
buttonsPanel.add(Box.createHorizontalGlue());
buttonsPanel.add(myCancelButton);
- myProcessTitle.setText("<html>Updating " + oldBuildDesc + " to " + newBuildDesc + "...");
-
myFrame.add(processPanel, BorderLayout.CENTER);
myFrame.add(buttonsPanel, BorderLayout.SOUTH);
startRequestDispatching();
}
+ @Override
+ public void setDescription(String oldBuildDesc, String newBuildDesc) {
+ myProcessTitle.setText("<html>Updating " + oldBuildDesc + " to " + newBuildDesc + "...");
+ }
+
+ @Override
+ public boolean showWarning(String message) {
+ Object[] choices = new Object[] { "Retry", "Exit" };
+ int choice = JOptionPane.showOptionDialog(null, message, "Warning", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, choices, choices[0]);
+ return choice == 0;
+ }
+
private void startRequestDispatching() {
new Thread(new Runnable() {
public void run() {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
+ boolean proceed = true;
+ for (ValidationResult result : validationResults) {
+ if (result.options.contains(ValidationResult.Option.NONE)) {
+ proceed = false;
+ break;
+ }
+ }
+
final JDialog dialog = new JDialog(myFrame, TITLE, true);
dialog.setLayout(new BorderLayout());
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
buttonsPanel.setBorder(BUTTONS_BORDER);
buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.X_AXIS));
buttonsPanel.add(Box.createHorizontalGlue());
- JButton proceedButton = new JButton(PROCEED_BUTTON_TITLE);
- proceedButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- dialog.setVisible(false);
- }
- });
JButton cancelButton = new JButton(CANCEL_BUTTON_TITLE);
cancelButton.addActionListener(new ActionListener() {
dialog.setVisible(false);
}
});
-
- buttonsPanel.add(proceedButton);
buttonsPanel.add(cancelButton);
- dialog.getRootPane().setDefaultButton(proceedButton);
+ if (proceed) {
+ JButton proceedButton = new JButton(PROCEED_BUTTON_TITLE);
+ proceedButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ dialog.setVisible(false);
+ }
+ });
+ buttonsPanel.add(proceedButton);
+ dialog.getRootPane().setDefaultButton(proceedButton);
+ } else {
+ dialog.getRootPane().setDefaultButton(cancelButton);
+ }
JTable table = new JTable();
each.setPreferredWidth(MyTableModel.getColumnWidth(i, new Dimension(600, 400).width));
}
- String message = "<html>There are some conflicts found in the installation.<br><br>" +
- "Please select desired solutions from the " + MyTableModel.COLUMNS[MyTableModel.OPTIONS_COLUMN_INDEX] +
- " column and press " + PROCEED_BUTTON_TITLE + ".<br>" +
- "If you do not want to proceed with the update, please press " + CANCEL_BUTTON_TITLE + ".</html>";
+ String message = "<html>Some conflicts were found in the installation area.<br><br>";
+
+ if (proceed) {
+ message += "Please select desired solutions from the " + MyTableModel.COLUMNS[MyTableModel.OPTIONS_COLUMN_INDEX] +
+ " column and press " + PROCEED_BUTTON_TITLE + ".<br>" +
+ "If you do not want to proceed with the update, please press " + CANCEL_BUTTON_TITLE + ".</html>";
+ } else {
+ message += "Some of the conflicts below do not have a solution, so the patch cannot be applied.<br>" +
+ "Press " + CANCEL_BUTTON_TITLE + " to exit.</html>";
+ }
JLabel label = new JLabel(message);
label.setBorder(LABEL_BORDER);
}
public static void main(String[] args) {
- new SwingUpdaterUI("xxx", "yyy", new InstallOperation() {
+ new SwingUpdaterUI(new InstallOperation() {
public boolean execute(UpdaterUI ui) throws OperationCancelledException {
ui.startProcess("Process1");
ui.checkCancelled();
import java.util.zip.ZipOutputStream;
public class UpdateAction extends BaseUpdateAction {
- public UpdateAction(String path, long checksum) {
- super(path, checksum);
+ public UpdateAction(Patch patch, String path, String source, long checksum, boolean move) {
+ super(patch, path, source, checksum, move);
}
- public UpdateAction(DataInputStream in) throws IOException {
- super(in);
+ public UpdateAction(Patch patch, String path, long checksum) {
+ this(patch, path, path, checksum, false);
}
- @Override
- protected void doBuildPatchFile(File olderFile, File newerFile, ZipOutputStream patchOutput) throws IOException {
- patchOutput.putNextEntry(new ZipEntry(myPath));
- writeExecutableFlag(patchOutput, newerFile);
- writeDiff(olderFile, newerFile, patchOutput);
- patchOutput.closeEntry();
+ public UpdateAction(Patch patch, DataInputStream in) throws IOException {
+ super(patch, in);
}
@Override
- protected boolean isModified(File toFile) throws IOException {
- return myChecksum != Digester.digestRegularFile(toFile);
+ protected void doBuildPatchFile(File olderFile, File newerFile, ZipOutputStream patchOutput) throws IOException {
+ if (!myIsMove) {
+ patchOutput.putNextEntry(new ZipEntry(myPath));
+ writeExecutableFlag(patchOutput, newerFile);
+ writeDiff(olderFile, newerFile, patchOutput);
+ patchOutput.closeEntry();
+ }
}
@Override
- protected void doApply(ZipFile patchFile, File toFile) throws IOException {
- InputStream in = Utils.findEntryInputStream(patchFile, myPath);
- boolean executable = readExecutableFlag(in);
-
- File temp = Utils.createTempFile();
- OutputStream out = new BufferedOutputStream(new FileOutputStream(temp));
- try {
- InputStream oldFileIn = new FileInputStream(toFile);
+ protected void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException {
+ File source = getSource(backupDir);
+ File updated;
+ if (!myIsMove) {
+ updated = Utils.createTempFile();
+ InputStream in = Utils.findEntryInputStream(patchFile, myPath);
+ boolean executable = readExecutableFlag(in);
+
+ OutputStream out = new BufferedOutputStream(new FileOutputStream(updated));
try {
- applyDiff(in, oldFileIn, out);
+ InputStream oldFileIn = Utils.newFileInputStream(source, myPatch.isNormalized());
+ try {
+ applyDiff(in, oldFileIn, out);
+ }
+ finally {
+ oldFileIn.close();
+ }
}
finally {
- oldFileIn.close();
+ out.close();
}
+ Utils.setExecutable(updated, executable);
+ } else {
+ updated = source;
}
- finally {
- out.close();
- }
-
- replaceUpdated(temp, toFile);
- Utils.setExecutable(toFile, executable);
+ replaceUpdated(updated, toFile);
}
}
Set<String> myFilesToUpdate;
Set<String> myFilesToDelete;
- public UpdateZipAction(String path, long checksum) {
- super(path, checksum);
+ public UpdateZipAction(Patch patch, String path, String source, long checksum, boolean move) {
+ super(patch, path, source, checksum, move);
}
// test support
- public UpdateZipAction(String path,
+ public UpdateZipAction(Patch patch, String path,
Collection<String> filesToCreate,
Collection<String> filesToUpdate,
Collection<String> filesToDelete,
long checksum) {
- super(path, checksum);
+ super(patch, path, path, checksum, false);
myFilesToCreate = new HashSet<String>(filesToCreate);
myFilesToUpdate = new HashSet<String>(filesToUpdate);
myFilesToDelete = new HashSet<String>(filesToDelete);
}
- public UpdateZipAction(DataInputStream in) throws IOException {
- super(in);
+ public UpdateZipAction(Patch patch, DataInputStream in) throws IOException {
+ super(patch, in);
int count = in.readInt();
myFilesToCreate = new HashSet<String>(count);
}
});
- DiffCalculator.Result diff = DiffCalculator.calculate(oldCheckSums, newCheckSums);
+ DiffCalculator.Result diff = DiffCalculator.calculate(oldCheckSums, newCheckSums, new LinkedList<String>(), false);
- myFilesToCreate = diff.filesToCreate;
+ myFilesToCreate = diff.filesToCreate.keySet();
myFilesToUpdate = diff.filesToUpdate.keySet();
myFilesToDelete = diff.filesToDelete.keySet();
}
@Override
- protected boolean isModified(File toFile) throws IOException {
- return myChecksum != Digester.digestFile(toFile);
- }
-
- protected void doApply(final ZipFile patchFile, File toFile) throws IOException {
+ protected void doApply(final ZipFile patchFile, File backupDir, File toFile) throws IOException {
File temp = Utils.createTempFile();
FileOutputStream fileOut = new FileOutputStream(temp);
try {
final ZipOutputWrapper out = new ZipOutputWrapper(fileOut);
out.setCompressionLevel(0);
- processZipFile(toFile, new Processor() {
+ processZipFile(getSource(backupDir), new Processor() {
+ @Override
public void process(ZipEntry entry, InputStream in) throws IOException {
String path = entry.getName();
if (myFilesToDelete.contains(path)) return;
void checkCancelled() throws OperationCancelledException;
+ void setDescription(String oldBuildDesc, String newBuildDesc);
+
+ /**
+ * Shows a warning associated with the pretense of a file and asks the user if the validation needs be retried.
+ * This function will return true iff the user wants to retry.
+ * @param message The warning message to display.
+ * @return true if the validation needs to be retried or false if te updater should quit.
+ */
+ boolean showWarning(String message);
+
Map<String, ValidationResult.Option> askUser(List<ValidationResult> validationResults) throws OperationCancelledException;
}
\ No newline at end of file
package com.intellij.updater;
import java.io.*;
-import java.util.LinkedHashSet;
+import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public static void copy(File from, File to) throws IOException {
Runner.logger.info("from " + from.getPath() + " to " + to.getPath());
if (from.isDirectory()) {
+ to.mkdirs();
File[] files = from.listFiles();
if (files == null) throw new IOException("Cannot get directory's content: " + from);
for (File each : files) {
}
}
+
+ public static void mirror(File from, File to) throws IOException {
+ if (from.exists()) {
+ copy(from, to);
+ } else {
+ delete(to);
+ }
+ }
+
public static void copyFileToStream(File from, OutputStream out) throws IOException {
InputStream in = new BufferedInputStream(new FileInputStream(from));
try {
}
public static InputStream getEntryInputStream(ZipFile zipFile, String entryPath) throws IOException {
- InputStream result = findEntryInputStream(zipFile, entryPath);
- if (result == null) throw new IOException("Entry " + entryPath + " not found");
- Runner.logger.info("entryPath: " + entryPath);
- return result;
+ ZipEntry entry = getZipEntry(zipFile, entryPath);
+ return findEntryInputStreamForEntry(zipFile, entry);
}
public static InputStream findEntryInputStream(ZipFile zipFile, String entryPath) throws IOException {
ZipEntry entry = zipFile.getEntry(entryPath);
- if (entry == null || entry.isDirectory()) return null;
+ if (entry == null) return null;
+ return findEntryInputStreamForEntry(zipFile, entry);
+ }
+
+ public static ZipEntry getZipEntry(ZipFile zipFile, String entryPath) throws IOException {
+ ZipEntry entry = zipFile.getEntry(entryPath);
+ if (entry == null) throw new IOException("Entry " + entryPath + " not found");
+ Runner.logger.info("entryPath: " + entryPath);
+ return entry;
+ }
- // if isDirectory check failed, check presence of 'file/' manually
- if (!entryPath.endsWith("/") && zipFile.getEntry(entryPath + "/") != null) return null;
+ public static InputStream findEntryInputStreamForEntry(ZipFile zipFile, ZipEntry entry) throws IOException {
+ if (entry.isDirectory()) return null;
+ // There is a bug in some JVM implementations where for a directory "X/" in a zipfile, if we do
+ // "zip.getEntry("X/").isDirectory()" returns true, but if we do "zip.getEntry("X").isDirectory()" is false.
+ // getEntry for "name" falls back to finding "X/", so here we make sure that didn't happen.
+ if (zipFile.getEntry(entry.getName() + "/") != null) return null;
return new BufferedInputStream(zipFile.getInputStream(entry));
}
- public static LinkedHashSet<String> collectRelativePaths(File dir) {
+ public static LinkedHashSet<String> collectRelativePaths(File dir, boolean includeDirectories) {
LinkedHashSet<String> result = new LinkedHashSet<String>();
- collectRelativePaths(dir, result, null);
+ collectRelativePaths(dir, result, null, includeDirectories);
return result;
}
- private static void collectRelativePaths(File dir, LinkedHashSet<String> result, String parentPath) {
+ private static void collectRelativePaths(File dir, LinkedHashSet<String> result, String parentPath, boolean includeDirectories) {
File[] children = dir.listFiles();
if (children == null) return;
for (File each : children) {
String relativePath = (parentPath == null ? "" : parentPath + "/") + each.getName();
if (each.isDirectory()) {
- collectRelativePaths(each, result, relativePath);
+ if (includeDirectories) {
+ // The trailing slash is very important, as it's used by zip to determine whether it is a directory.
+ result.add(relativePath + "/");
+ }
+ collectRelativePaths(each, result, relativePath, includeDirectories);
}
else {
result.add(relativePath);
}
}
}
+
+ public static InputStream newFileInputStream(File file, boolean normalize) throws IOException {
+ if (!normalize || !isZipFile(file.getName())) {
+ return new FileInputStream(file);
+ }
+ return new NormalizedZipInputStream(file);
+ }
+
+ static class NormalizedZipInputStream extends InputStream {
+
+ private ArrayList<? extends ZipEntry> myEntries;
+ private InputStream myStream = null;
+ private int myNextEntry = 0;
+ private final ZipFile myZip;
+ private byte[] myByte = new byte[1];
+
+ NormalizedZipInputStream(File file) throws IOException {
+ myZip = new ZipFile(file);
+ myEntries = Collections.list(myZip.entries());
+ Collections.sort(myEntries, new Comparator<ZipEntry>() {
+ @Override
+ public int compare(ZipEntry a, ZipEntry b) {
+ return a.getName().compareTo(b.getName());
+ }
+ });
+
+ loadNextEntry();
+ }
+
+ private void loadNextEntry() throws IOException {
+ if (myStream != null) {
+ myStream.close();
+ }
+ myStream = null;
+ while (myNextEntry < myEntries.size() && myStream == null) {
+ myStream = findEntryInputStreamForEntry(myZip, myEntries.get(myNextEntry++));
+ }
+ }
+
+ @Override
+ public int read(byte[] bytes, int off, int len) throws IOException {
+ if (myStream == null) {
+ return -1;
+ }
+ int b = myStream.read(bytes, off, len);
+ if (b == -1) {
+ loadNextEntry();
+ return read(bytes, off, len);
+ }
+ return b;
+ }
+
+ @Override
+ public int read() throws IOException {
+ int b = read(myByte, 0, 1);
+ return b == -1 ? -1 : myByte[0];
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (myStream != null) {
+ myStream.close();
+ }
+ myZip.close();
+ }
+ }
}
--- /dev/null
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.updater;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+public class ValidateAction extends PatchAction {
+ // Only used on patch creation
+ protected transient File myOlderDir;
+
+ public ValidateAction(Patch patch, String path, long checksum) {
+ super(patch, path, checksum);
+ }
+
+ public ValidateAction(Patch patch, DataInputStream in) throws IOException {
+ super(patch, in);
+ }
+
+ @Override
+ protected void doBuildPatchFile(File olderFile, File newerFile, ZipOutputStream patchOutput) throws IOException {
+ }
+
+ @Override
+ public ValidationResult validate(File toDir) throws IOException {
+ return doValidateNotChanged(getFile(toDir), ValidationResult.Kind.ERROR, ValidationResult.Action.VALIDATE);
+ }
+
+ @Override
+ protected void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException {
+ }
+
+ @Override
+ protected void doBackup(File toFile, File backupFile) throws IOException {
+ }
+
+ @Override
+ protected void doRevert(File toFile, File backupFile) throws IOException {
+ }
+}
}
public enum Action {
- CREATE("Create"), UPDATE("Update"), DELETE("Delete"), NO_ACTION("");
+ CREATE("Create"), UPDATE("Update"), DELETE("Delete"), NO_ACTION(""), VALIDATE("Validate");
private final String myDisplayString;
}
public enum Option {
- IGNORE, KEEP, REPLACE, DELETE, KILL_PROCESS
+ NONE, IGNORE, KEEP, REPLACE, DELETE, KILL_PROCESS
}
public static final String ABSENT_MESSAGE = "Absent";
Licensing & pricing
==========================
- Licensing and pricing information can be found at https://www.jetbrains.com/idea/buy/index.html.
+ Licensing and pricing information can be found at http://www.jetbrains.com/idea/buy/index.html.
IntelliJ IDEA Overview
==========================
For general info and facts on IntelliJ IDEA, you can refer to IntelliJ IDEA Info Kit at
- https://www.jetbrains.com/idea/documentation/product_info_kit.html.
+ http://www.jetbrains.com/idea/documentation/product_info_kit.html.
IDEA Development Package
* StarTeam, Perforce, Subversion, Visual SourceSafe integration
* Tomcat, Weblogic, WebSphere, Geronimo, JBoss, GlassFish, JSR45 integration
- Download page: https://www.jetbrains.com/idea/download/index.html
+ Download page: http://www.jetbrains.com/idea/download/index.html
Source code of additional open source plugins shipped with IntelliJ IDEA is available in the Subversion
repository at:
Support
=======
For technical support and assistance, you may find necessary information at the Support page
- (https://www.jetbrains.com/support/index.html) or contact us at support@jetbrains.com.
+ (http://www.jetbrains.com/support/index.html) or contact us at support@jetbrains.com.
Bug Reporting:
=============
-You are encouraged to visit our IntelliJ IDEA web site at https://www.jetbrains.com/idea/
+You are encouraged to visit our IntelliJ IDEA web site at http://www.jetbrains.com/idea/
or to contact us via e-mail at feedback@jetbrains.com if you have any comments about
this release. In particular, we are very interested in any ease-of-use, user
interface suggestions that you may have. We will be posting regular updates
import org.junit.Test;
-import java.util.Collections;
-import java.util.Map;
+import java.io.File;
import static org.junit.Assert.assertEquals;
public class DigesterTest extends UpdaterTestCase {
@Test
public void testBasics() throws Exception {
- Runner.initLogger();
- Map<String, Long> checkSums = Digester.digestFiles(getDataDir(), Collections.<String>emptyList(), TEST_UI);
- assertEquals(12, checkSums.size());
+ assertEquals(CHECKSUMS.README_TXT, Digester.digestRegularFile(new File(getDataDir(), "Readme.txt"), false));
+ assertEquals(CHECKSUMS.FOCUSKILLER_DLL, Digester.digestRegularFile(new File(getDataDir(), "/bin/focuskiller.dll"), false));
+ assertEquals(CHECKSUMS.BOOTSTRAP_JAR, Digester.digestZipFile(new File(getDataDir(), "/lib/bootstrap.jar")));
+ assertEquals(CHECKSUMS.BOOTSTRAP_JAR_BINARY, Digester.digestRegularFile(new File(getDataDir(), "/lib/bootstrap.jar"), false));
- assertEquals(CHECKSUMS.README_TXT, (long)checkSums.get("Readme.txt"));
- assertEquals(CHECKSUMS.FOCUSKILLER_DLL, (long)checkSums.get("bin/focuskiller.dll"));
- assertEquals(CHECKSUMS.BOOTSTRAP_JAR, (long)checkSums.get("lib/bootstrap.jar"));
+ assertEquals(CHECKSUMS.ANNOTATIONS_JAR_NORM,
+ Digester.digestRegularFile(new File(getDataDir(), "/lib/annotations.jar"), true));
+ assertEquals(CHECKSUMS.ANNOTATIONS_CHANGED_JAR_NORM,
+ Digester.digestRegularFile(new File(getDataDir(), "/lib/annotations_changed.jar"), true));
+ assertEquals(CHECKSUMS.BOOT_JAR_NORM,
+ Digester.digestRegularFile(new File(getDataDir(), "/lib/boot.jar"), true));
+ assertEquals(CHECKSUMS.BOOT2_JAR_NORM,
+ Digester.digestRegularFile(new File(getDataDir(), "/lib/boot2.jar"), true));
+ assertEquals(CHECKSUMS.BOOT2_CHANGED_WITH_UNCHANGED_CONTENT_JAR_NORM,
+ Digester.digestRegularFile(new File(getDataDir(), "/lib/boot2_changed_with_unchanged_content.jar"), true));
+ assertEquals(CHECKSUMS.BOOT_WITH_DIRECTORY_BECOMES_FILE_JAR_NORM,
+ Digester.digestRegularFile(new File(getDataDir(), "/lib/boot_with_directory_becomes_file.jar"), true));
+ assertEquals(CHECKSUMS.BOOTSTRAP_JAR_NORM,
+ Digester.digestRegularFile(new File(getDataDir(), "/lib/bootstrap.jar"), true));
+ assertEquals(CHECKSUMS.BOOTSTRAP_DELETED_JAR_NORM,
+ Digester.digestRegularFile(new File(getDataDir(), "/lib/bootstrap_deleted.jar"), true));
}
}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.updater;
+
+public class PatchFileCreatorBinaryTest extends PatchFileCreatorTest {
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ myPatchSpec.setBinary(true);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.updater;
+
+import com.intellij.openapi.util.io.FileUtil;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class PatchFileCreatorNotBinaryTest extends PatchFileCreatorTest {
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Test
+ public void failOnEmptyTargetJar() throws Exception {
+ final File sourceJar = new File(myOlderDir, "lib/empty.jar");
+ FileUtil.copy(new File(myOlderDir, "lib/annotations.jar"), sourceJar);
+
+ try {
+ final File targetJar = new File(myNewerDir, "lib/empty.jar");
+ if (targetJar.exists()) targetJar.delete();
+ assertTrue(targetJar.createNewFile());
+
+ try {
+ createPatch();
+ fail("Should have failed to create a patch against empty .jar");
+ }
+ catch (IOException e) {
+ final String reason = e.getMessage();
+ assertEquals("Corrupted target file: " + targetJar, reason);
+ }
+ finally {
+ targetJar.delete();
+ }
+ }
+ finally {
+ sourceJar.delete();
+ }
+ }
+
+ @Test
+ public void failOnEmptySourceJar() throws Exception {
+ final File sourceJar = new File(myOlderDir, "lib/empty.jar");
+ if (sourceJar.exists()) sourceJar.delete();
+ assertTrue(sourceJar.createNewFile());
+
+ try {
+ final File targetJar = new File(myNewerDir, "lib/empty.jar");
+ FileUtil.copy(new File(myNewerDir, "lib/annotations.jar"), targetJar);
+
+ try {
+ createPatch();
+ fail("Should have failed to create a patch from empty .jar");
+ }
+ catch (IOException e) {
+ final String reason = e.getMessage();
+ assertEquals("Corrupted source file: " + sourceJar, reason);
+ }
+ finally {
+ targetJar.delete();
+ }
+ }
+ finally {
+ sourceJar.delete();
+ }
+ }
+}
import org.junit.Before;
import org.junit.Test;
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
+import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static org.junit.Assert.*;
@SuppressWarnings("ResultOfMethodCallIgnored")
-public class PatchFileCreatorTest extends PatchTestCase {
+public abstract class PatchFileCreatorTest extends PatchTestCase {
private File myFile;
+ protected PatchSpec myPatchSpec;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
myFile = getTempFile("patch.zip");
+ myPatchSpec = new PatchSpec()
+ .setOldFolder(myOlderDir.getAbsolutePath())
+ .setNewFolder(myNewerDir.getAbsolutePath());
}
@Test
public void testCreatingAndApplying() throws Exception {
- createPatch();
+ Patch patch = createPatch();
- assertAppliedAndRevertedCorrectly(PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
+ assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
}
@Test
- public void failOnEmptySourceJar() throws Exception {
- final File sourceJar = new File(myOlderDir, "lib/empty.jar");
- if (sourceJar.exists()) sourceJar.delete();
- assertTrue(sourceJar.createNewFile());
+ public void testCreatingAndApplyingStrict() throws Exception {
+ myPatchSpec.setStrict(true);
+ Patch patch = createPatch();
+ assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
+ }
- try {
- final File targetJar = new File(myNewerDir, "lib/empty.jar");
- FileUtil.copy(new File(myNewerDir, "lib/annotations.jar"), targetJar);
+ @Test
+ public void testCreatingAndApplyingOnADifferentRoot() throws Exception {
+ myPatchSpec.setRoot("bin/");
+ myPatchSpec.setStrict(true);
- try {
- createPatch();
- fail("Should have failed to create a patch from empty .jar");
- }
- catch (IOException e) {
- final String reason = e.getMessage();
- assertEquals("Corrupted source file: " + sourceJar, reason);
- }
- finally {
- targetJar.delete();
- }
- }
- finally {
- sourceJar.delete();
- }
+ Patch patch = createPatch();
+
+ File target = new File(myOlderDir, "bin");
+ assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, target, TEST_UI));
}
@Test
- public void failOnEmptyTargetJar() throws Exception {
- final File sourceJar = new File(myOlderDir, "lib/empty.jar");
- FileUtil.copy(new File(myOlderDir, "lib/annotations.jar"), sourceJar);
+ public void testCreatingAndFailingOnADifferentRoot() throws Exception {
+ myPatchSpec.setRoot("bin/");
+ myPatchSpec.setStrict(true);
- try {
- final File targetJar = new File(myNewerDir, "lib/empty.jar");
- if (targetJar.exists()) targetJar.delete();
- assertTrue(targetJar.createNewFile());
+ Patch patch = createPatch();
- try {
- createPatch();
- fail("Should have failed to create a patch against empty .jar");
- }
- catch (IOException e) {
- final String reason = e.getMessage();
- assertEquals("Corrupted target file: " + targetJar, reason);
- }
- finally {
- targetJar.delete();
- }
- }
- finally {
- sourceJar.delete();
- }
+ File target = new File(myOlderDir, "bin");
+ PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, target, TEST_UI);
+ preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction(patch));
+ assertNothingHasChanged(patch, preparationResult, new HashMap<String, ValidationResult.Option>());
}
@Test
public void testReverting() throws Exception {
- createPatch();
+ Patch patch = createPatch();
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
- preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction());
- assertNothingHasChanged(preparationResult, new HashMap<String, ValidationResult.Option>());
+ preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction(patch));
+ assertNothingHasChanged(patch, preparationResult, new HashMap<String, ValidationResult.Option>());
}
@Test
public void testRevertedWhenFileToDeleteIsProcessLocked() throws Exception {
if (!UtilsTest.mIsWindows) return;
- PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
- Collections.<String>emptyList(), TEST_UI);
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
RandomAccessFile raf = new RandomAccessFile(new File(myOlderDir, "bin/idea.bat"),"rw");
- // Lock the file. FileLock is not good here, because we need to prevent deletion.
- int b = raf.read();
- raf.seek(0);
- raf.write(b);
-
try {
+ // Lock the file. FileLock is not good here, because we need to prevent deletion.
+ int b = raf.read();
+ raf.seek(0);
+ raf.write(b);
+
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
- Map<String, Long> original = Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI);
+ Map<String, Long> original = patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, TEST_UI);
File backup = getTempFile("backup");
PatchFileCreator.apply(preparationResult, new HashMap<String, ValidationResult.Option>(), backup, TEST_UI);
- assertEquals(original, Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI));
+ assertEquals(original, patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, TEST_UI));
}
finally {
raf.close();
@Test
public void testApplyingWithAbsentFileToDelete() throws Exception {
- PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
- Collections.<String>emptyList(), TEST_UI);
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
new File(myOlderDir, "bin/idea.bat").delete();
- assertAppliedAndRevertedCorrectly(PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
+ assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
+ }
+
+ @Test
+ public void testApplyingWithAbsentFileToUpdateStrict() throws Exception {
+ myPatchSpec.setStrict(true);
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
+
+ new File(myOlderDir, "lib/annotations.jar").delete();
+
+ PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
+ assertEquals(1, preparationResult.validationResults.size());
+ assertEquals(new ValidationResult(ValidationResult.Kind.ERROR,
+ "lib/annotations.jar",
+ ValidationResult.Action.UPDATE,
+ ValidationResult.ABSENT_MESSAGE,
+ ValidationResult.Option.NONE), preparationResult.validationResults.get(0));
}
@Test
public void testApplyingWithAbsentOptionalFile() throws Exception {
FileUtil.writeToFile(new File(myNewerDir, "bin/idea.bat"), "new content".getBytes());
- PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
- Collections.singletonList("bin/idea.bat"), TEST_UI);
+ myPatchSpec.setOptionalFiles(Collections.singletonList("bin/idea.bat"));
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
new File(myOlderDir, "bin/idea.bat").delete();
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
assertTrue(preparationResult.validationResults.isEmpty());
- assertAppliedAndRevertedCorrectly(preparationResult);
+ assertAppliedAndRevertedCorrectly(patch, preparationResult);
}
@Test
public void testRevertingWithAbsentFileToDelete() throws Exception {
- PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
- Collections.<String>emptyList(), TEST_UI);
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
new File(myOlderDir, "bin/idea.bat").delete();
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
- preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction());
- assertNothingHasChanged(preparationResult, new HashMap<String, ValidationResult.Option>());
+ preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction(patch));
+ assertNothingHasChanged(patch, preparationResult, new HashMap<String, ValidationResult.Option>());
}
@Test
public void testApplyingWithoutCriticalFiles() throws Exception {
- PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
- Collections.<String>emptyList(), TEST_UI);
+ PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
assertTrue(PatchFileCreator.apply(preparationResult, new HashMap<String, ValidationResult.Option>(), TEST_UI));
@Test
public void testApplyingWithCriticalFiles() throws Exception {
- PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Arrays.asList("lib/annotations.jar"),
- Collections.<String>emptyList(), TEST_UI);
+ myPatchSpec.setCriticalFiles(Arrays.asList("lib/annotations.jar"));
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
- PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
+ assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
+ }
- assertTrue(PatchFileCreator.apply(preparationResult, new HashMap<String, ValidationResult.Option>(), TEST_UI));
- assertAppliedCorrectly();
+ @Test
+ public void testApplyingWithModifiedCriticalFiles() throws Exception {
+ myPatchSpec.setStrict(true);
+ myPatchSpec.setCriticalFiles(Arrays.asList("lib/annotations.jar"));
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
+
+ RandomAccessFile raf = new RandomAccessFile(new File(myOlderDir, "lib/annotations.jar"), "rw");
+ raf.seek(20);
+ raf.write(42);
+ raf.close();
+
+ assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
+ }
+
+ @Test
+ public void testApplyingWithModifiedCriticalFilesAndDifferentRoot() throws Exception {
+ myPatchSpec.setStrict(true);
+ myPatchSpec.setRoot("lib/");
+ myPatchSpec.setCriticalFiles(Arrays.asList("lib/annotations.jar"));
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
+
+ RandomAccessFile raf = new RandomAccessFile(new File(myOlderDir, "lib/annotations.jar"), "rw");
+ raf.seek(20);
+ raf.write(42);
+ raf.close();
+
+ File toDir = new File(myOlderDir, "lib/");
+ assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, toDir, TEST_UI));
}
@Test
public void testApplyingWithCaseChangedNames() throws Exception {
- FileUtil.rename(new File(myOlderDir, "Readme.txt"),
- new File(myOlderDir, "README.txt"));
+ FileUtil.rename(new File(myOlderDir, "Readme.txt"), new File(myOlderDir, "README.txt"));
- PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
- Collections.<String>emptyList(), TEST_UI);
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
- assertAppliedAndRevertedCorrectly(PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
+ assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
}
@Test
new File(file, "subDir").mkdir();
new File(file, "subDir/subFile.txt").createNewFile();
- FileUtil.copy(new File(myOlderDir, "lib/boot.jar"),
- new File(myOlderDir, "lib/boot_with_directory_becomes_file.jar"));
+ FileUtil.copy(new File(myOlderDir, "lib/boot.jar"), new File(myOlderDir, "lib/boot_with_directory_becomes_file.jar"));
- PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
- Collections.<String>emptyList(), TEST_UI);
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
- assertAppliedAndRevertedCorrectly(PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
+ assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
}
@Test
FileUtil.copy(new File(myOlderDir, "lib/boot_with_directory_becomes_file.jar"),
new File(myOlderDir, "lib/boot.jar"));
- PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
- Collections.<String>emptyList(), TEST_UI);
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
- assertAppliedAndRevertedCorrectly(PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
+ assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
}
@Test
public void testConsideringOptions() throws Exception {
- createPatch();
+ Patch patch = createPatch();
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
Map<String, ValidationResult.Option> options = new HashMap<String, ValidationResult.Option>();
options.put(each.getPath(), ValidationResult.Option.IGNORE);
}
- assertNothingHasChanged(preparationResult, options);
+ assertNothingHasChanged(patch, preparationResult, options);
+ }
+
+ @Test
+ public void testApplyWhenCommonFileChanges() throws Exception {
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
+
+ FileUtil.copy(new File(myOlderDir, "/lib/bootstrap.jar"),
+ new File(myOlderDir, "/lib/boot.jar"));
+
+ PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
+ assertTrue(preparationResult.validationResults.isEmpty());
+ assertAppliedAndRevertedCorrectly(patch, preparationResult);
+ }
+
+ @Test
+ public void testApplyWhenCommonFileChangesStrict() throws Exception {
+ myPatchSpec.setStrict(true);
+ PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
+
+ FileUtil.copy(new File(myOlderDir, "/lib/bootstrap.jar"), new File(myOlderDir, "/lib/boot.jar"));
+
+ PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
+ assertEquals(1, preparationResult.validationResults.size());
+ assertEquals(
+ new ValidationResult(ValidationResult.Kind.ERROR, "lib/boot.jar", ValidationResult.Action.VALIDATE, ValidationResult.MODIFIED_MESSAGE,
+ ValidationResult.Option.NONE), preparationResult.validationResults.get(0));
+ }
+
+ @Test
+ public void testApplyWhenNewFileExists() throws Exception {
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
+
+ FileUtil.writeToFile(new File(myOlderDir, "newfile.txt"), "hello");
+
+ PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
+ assertTrue(preparationResult.validationResults.isEmpty());
+ assertAppliedAndRevertedCorrectly(patch, preparationResult);
}
- private void createPatch() throws IOException, OperationCancelledException {
- PatchFileCreator.create(myOlderDir, myNewerDir, myFile,
- Collections.<String>emptyList(), Collections.<String>emptyList(), Collections.<String>emptyList(), TEST_UI);
+ @Test
+ public void testApplyWhenNewFileExistsStrict() throws Exception {
+ myPatchSpec.setStrict(true);
+ myPatchSpec.setDeleteFiles(Collections.singletonList("lib/java_pid.*\\.hprof"));
+
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
+
+ FileUtil.writeToFile(new File(myOlderDir, "newfile.txt"), "hello");
+ FileUtil.writeToFile(new File(myOlderDir, "lib/java_pid1234.hprof"), "bye!");
+
+ PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
+ assertEquals(1, preparationResult.validationResults.size());
+ assertEquals(new ValidationResult(ValidationResult.Kind.CONFLICT, "newfile.txt", ValidationResult.Action.VALIDATE, "Unexpected file",
+ ValidationResult.Option.DELETE), preparationResult.validationResults.get(0));
+ assertAppliedAndRevertedCorrectly(patch, preparationResult);
+ }
+
+ @Test
+ public void testApplyWhenNewDeletableFileExistsStrict() throws Exception {
+ myPatchSpec.setStrict(true);
+ myPatchSpec.setDeleteFiles(Collections.singletonList("lib/java_pid.*\\.hprof"));
+
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
+
+ FileUtil.writeToFile(new File(myOlderDir, "lib/java_pid1234.hprof"), "bye!");
+
+ PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
+ assertEquals(0, preparationResult.validationResults.size());
+ assertAppliedAndRevertedCorrectly(patch, preparationResult);
+ }
+
+ @Test
+ public void testApplyWhenNewDirectoryExistsStrict() throws Exception {
+ myPatchSpec.setStrict(true);
+ new File(myOlderDir, "delete").mkdirs();
+ FileUtil.writeToFile(new File(myOlderDir, "delete/deleteme.txt"), "bye!");
+
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
+
+ new File(myOlderDir, "unexpected_newdir").mkdirs();
+ FileUtil.writeToFile(new File(myOlderDir, "unexpected_newdir/unexpected.txt"), "bye!");
+
+ new File(myOlderDir, "newDir").mkdir();
+
+ PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
+ assertEquals(3, preparationResult.validationResults.size());
+ assertEquals(new ValidationResult(ValidationResult.Kind.CONFLICT,
+ "unexpected_newdir/unexpected.txt",
+ ValidationResult.Action.VALIDATE,
+ "Unexpected file",
+ ValidationResult.Option.DELETE), preparationResult.validationResults.get(0));
+ assertEquals(new ValidationResult(ValidationResult.Kind.CONFLICT,
+ "unexpected_newdir/",
+ ValidationResult.Action.VALIDATE,
+ "Unexpected file",
+ ValidationResult.Option.DELETE), preparationResult.validationResults.get(1));
+ assertEquals(new ValidationResult(ValidationResult.Kind.CONFLICT,
+ "newDir/",
+ ValidationResult.Action.CREATE,
+ ValidationResult.ALREADY_EXISTS_MESSAGE,
+ ValidationResult.Option.REPLACE), preparationResult.validationResults.get(2));
+ new File(myOlderDir, "newDir").delete();
+ assertAppliedAndRevertedCorrectly(patch, preparationResult);
+ }
+
+ @Test
+ public void testMoveFileByContent() throws IOException, OperationCancelledException {
+ myPatchSpec.setStrict(true);
+ FileUtil.writeToFile(new File(myOlderDir, "move/from/this/directory/move.me"), "oldcontent");
+ FileUtil.writeToFile(new File(myOlderDir, "a/deleted/file/that/is/a/copy/move.me"), "newcontent");
+ FileUtil.writeToFile(new File(myNewerDir, "move/to/this/directory/move.me"), "newcontent");
+
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
+ PatchAction action = getAction(patch, "move/to/this/directory/move.me");
+ assertTrue(action instanceof UpdateAction);
+ UpdateAction update = (UpdateAction)action;
+ assertTrue(update.isMove());
+ assertEquals("a/deleted/file/that/is/a/copy/move.me", update.getSourcePath());
+
+ PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
+ assertAppliedAndRevertedCorrectly(patch, preparationResult);
+ }
+
+ @Test
+ public void testMoveCriticalFileByContent() throws IOException, OperationCancelledException {
+ myPatchSpec.setStrict(true);
+ myPatchSpec.setCriticalFiles(Collections.singletonList("a/deleted/file/that/is/a/copy/move.me"));
+
+ FileUtil.writeToFile(new File(myOlderDir, "move/from/this/directory/move.me"), "oldcontent");
+ FileUtil.writeToFile(new File(myOlderDir, "a/deleted/file/that/is/a/copy/move.me"), "newcontent");
+ FileUtil.writeToFile(new File(myNewerDir, "move/to/this/directory/move.me"), "newcontent");
+
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
+ PatchAction action = getAction(patch, "move/to/this/directory/move.me");
+ assertTrue(action instanceof CreateAction);
+
+ PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
+ assertAppliedAndRevertedCorrectly(patch, preparationResult);
+ }
+
+ @Test
+ public void testDontMoveFromDirectoryToFile() throws IOException, OperationCancelledException {
+ myPatchSpec.setStrict(true);
+ new File(myOlderDir, "from/move.me").mkdirs();
+ FileUtil.writeToFile(new File(myNewerDir, "move/to/move.me"), "different");
+
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
+ // Creating a patch would have crashed if the directory had been chosen.
+ PatchAction action = getAction(patch, "move/to/move.me");
+ assertTrue(action instanceof CreateAction);
+ action = getAction(patch, "from/move.me/");
+ assertTrue(action instanceof DeleteAction);
+ PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
+ assertEquals(0, preparationResult.validationResults.size());
+ assertAppliedAndRevertedCorrectly(patch, preparationResult);
+ }
+
+ @Test
+ public void testMoveFileByLocation() throws IOException, OperationCancelledException {
+ myPatchSpec.setStrict(true);
+ FileUtil.writeToFile(new File(myOlderDir, "move/from/this/directory/move.me"), "they");
+ FileUtil.writeToFile(new File(myOlderDir, "not/from/this/one/move.me"), "are");
+ FileUtil.writeToFile(new File(myNewerDir, "move/to/this/directory/move.me"), "different");
+
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
+ PatchAction action = getAction(patch, "move/to/this/directory/move.me");
+ assertTrue(action instanceof UpdateAction);
+ UpdateAction update = (UpdateAction)action;
+ assertTrue(!update.isMove());
+ assertEquals("move/from/this/directory/move.me", update.getSourcePath());
+
+ PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
+ assertAppliedAndRevertedCorrectly(patch, preparationResult);
+ }
+
+ protected PatchAction getAction(Patch patch, String path) {
+ for (PatchAction action : patch.getActions()) {
+ if (action.getPath().equals(path)) {
+ return action;
+ }
+ }
+ return null;
+ }
+
+ protected Patch createPatch() throws IOException, OperationCancelledException {
+ Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
assertTrue(myFile.exists());
+ return patch;
}
- private void assertNothingHasChanged(PatchFileCreator.PreparationResult preparationResult, Map<String, ValidationResult.Option> options)
+ private void assertNothingHasChanged(Patch patch, PatchFileCreator.PreparationResult preparationResult, Map<String, ValidationResult.Option> options)
throws Exception {
- Map<String, Long> before = Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI);
+ Map<String, Long> before = patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, TEST_UI);
PatchFileCreator.apply(preparationResult, options, TEST_UI);
- Map<String, Long> after = Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI);
+ Map<String, Long> after = patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, TEST_UI);
- DiffCalculator.Result diff = DiffCalculator.calculate(before, after);
+ DiffCalculator.Result diff = DiffCalculator.calculate(before, after, new LinkedList<String>(), false);
assertTrue(diff.filesToCreate.isEmpty());
assertTrue(diff.filesToDelete.isEmpty());
assertTrue(diff.filesToUpdate.isEmpty());
}
- private void assertAppliedAndRevertedCorrectly(PatchFileCreator.PreparationResult preparationResult)
+ private void assertAppliedAndRevertedCorrectly(Patch patch, PatchFileCreator.PreparationResult preparationResult)
throws IOException, OperationCancelledException {
- Map<String, Long> original = Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI);
-
+ Map<String, Long> original = patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, TEST_UI);
+ Map<String, Long> target = patch.digestFiles(myNewerDir, Collections.<String>emptyList(), false, TEST_UI);
File backup = getTempFile("backup");
+ HashMap<String, ValidationResult.Option> options = new HashMap<String, ValidationResult.Option>();
for (ValidationResult each : preparationResult.validationResults) {
- assertTrue(each.toString(), each.kind != ValidationResult.Kind.ERROR);
+ if (patch.isStrict()) {
+ assertFalse(each.options.contains(ValidationResult.Option.NONE));
+ assertTrue(each.options.size() > 0);
+ options.put(each.path, each.options.get(0));
+ } else {
+ assertTrue(each.toString(), each.kind != ValidationResult.Kind.ERROR);
+ }
}
List<PatchAction> appliedActions =
- PatchFileCreator.apply(preparationResult, new HashMap<String, ValidationResult.Option>(), backup, TEST_UI).appliedActions;
- assertAppliedCorrectly();
+ PatchFileCreator.apply(preparationResult, options, backup, TEST_UI).appliedActions;
+ Map<String, Long> patched = patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, TEST_UI);
+
+ if (patch.isStrict()) {
+ assertEquals(patched, target);
+ } else {
+ assertAppliedCorrectly();
+ }
- assertFalse(original.equals(Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI)));
+ assertNotEquals(original, patched);
PatchFileCreator.revert(preparationResult, appliedActions, backup, TEST_UI);
-
- assertEquals(original, Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI));
+ Map<String, Long> reverted = patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, TEST_UI);
+ assertEquals(original, reverted);
}
protected void assertAppliedCorrectly() throws IOException {
}
private static class MyFailOnApplyPatchAction extends PatchAction {
- public MyFailOnApplyPatchAction() {
- super("_dummy_file_", -1);
+ // Only used on patch creation
+ protected transient File myOlderDir;
+
+ public MyFailOnApplyPatchAction(Patch patch) {
+ super(patch, "_dummy_file_", Digester.INVALID);
}
@Override
}
@Override
- protected ValidationResult doValidate(File toFile) throws IOException {
+ protected ValidationResult validate(File toDir) throws IOException {
throw new UnsupportedOperationException();
}
@Override
- protected void doApply(ZipFile patchFile, File toFile) throws IOException {
+ protected void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException {
throw new IOException("dummy exception");
}
@Before
public void setUp() throws Exception {
super.setUp();
- myPatch = new Patch(myOlderDir, myNewerDir, Collections.<String>emptyList(), Collections.<String>emptyList(),
- Collections.<String>emptyList(), TEST_UI);
+ PatchSpec spec = new PatchSpec()
+ .setOldFolder(myOlderDir.getAbsolutePath())
+ .setNewFolder(myNewerDir.getAbsolutePath());
+ myPatch = new Patch(spec, TEST_UI);
+ }
+
+ @Test
+ public void testDigestFiles() throws Exception {
+ Map<String, Long> checkSums = myPatch.digestFiles(getDataDir(), Collections.<String>emptyList(), false, TEST_UI);
+ assertEquals(9, checkSums.size());
}
@Test
public void testBasics() throws Exception {
List<PatchAction> expectedActions = Arrays.asList(
- new CreateAction("newDir/newFile.txt"),
- new UpdateAction("Readme.txt", CHECKSUMS.README_TXT),
- new UpdateZipAction("lib/annotations.jar",
+ new CreateAction(myPatch, "newDir/newFile.txt"),
+ new UpdateAction(myPatch, "Readme.txt", CHECKSUMS.README_TXT),
+ new UpdateZipAction(myPatch, "lib/annotations.jar",
Arrays.asList("org/jetbrains/annotations/NewClass.class"),
Arrays.asList("org/jetbrains/annotations/Nullable.class"),
Arrays.asList("org/jetbrains/annotations/TestOnly.class"),
CHECKSUMS.ANNOTATIONS_JAR),
- new UpdateZipAction("lib/bootstrap.jar",
+ new UpdateZipAction(myPatch, "lib/bootstrap.jar",
Collections.<String>emptyList(),
Collections.<String>emptyList(),
Arrays.asList("com/intellij/ide/ClassloaderUtil.class"),
CHECKSUMS.BOOTSTRAP_JAR),
- new DeleteAction("bin/idea.bat", CHECKSUMS.IDEA_BAT));
+ new DeleteAction(myPatch, "bin/idea.bat", CHECKSUMS.IDEA_BAT));
List<PatchAction> actualActions = new ArrayList<PatchAction>(myPatch.getActions());
Collections.sort(expectedActions, COMPARATOR);
Collections.sort(actualActions, COMPARATOR);
@Test
public void testCreatingWithIgnoredFiles() throws Exception {
- myPatch = new Patch(myOlderDir,
- myNewerDir,
- Arrays.asList("Readme.txt", "bin/idea.bat"),
- Collections.<String>emptyList(),
- Collections.<String>emptyList(),
+ PatchSpec spec = new PatchSpec()
+ .setOldFolder(myOlderDir.getAbsolutePath())
+ .setNewFolder(myNewerDir.getAbsolutePath())
+ .setIgnoredFiles(Arrays.asList("Readme.txt", "bin/idea.bat"));
+ myPatch = new Patch(spec,
TEST_UI);
List<PatchAction> expectedActions = Arrays.asList(
- new CreateAction("newDir/newFile.txt"),
- new UpdateZipAction("lib/annotations.jar",
+ new CreateAction(myPatch, "newDir/newFile.txt"),
+ new UpdateZipAction(myPatch, "lib/annotations.jar",
Arrays.asList("org/jetbrains/annotations/NewClass.class"),
Arrays.asList("org/jetbrains/annotations/Nullable.class"),
Arrays.asList("org/jetbrains/annotations/TestOnly.class"),
CHECKSUMS.ANNOTATIONS_JAR),
- new UpdateZipAction("lib/bootstrap.jar",
+ new UpdateZipAction(myPatch, "lib/bootstrap.jar",
Collections.<String>emptyList(),
Collections.<String>emptyList(),
Arrays.asList("com/intellij/ide/ClassloaderUtil.class"),
ValidationResult.Option.IGNORE))),
new HashSet<ValidationResult>(myPatch.validate(myOlderDir, TEST_UI)));
- myPatch = new Patch(myOlderDir, myNewerDir, Collections.<String>emptyList(), Collections.<String>emptyList(),
- Arrays.asList("lib/annotations.jar"), TEST_UI);
+ PatchSpec spec = new PatchSpec()
+ .setOldFolder(myOlderDir.getAbsolutePath())
+ .setNewFolder(myNewerDir.getAbsolutePath())
+ .setOptionalFiles(Arrays.asList("lib/annotations.jar"));
+ myPatch = new Patch(spec, TEST_UI);
FileUtil.delete(new File(myOlderDir, "lib/annotations.jar"));
assertEquals(Collections.<ValidationResult>emptyList(),
myPatch.validate(myOlderDir, TEST_UI));
Runner.initLogger();
assertEquals(Arrays.asList("xxx", "yyy", "zzz/zzz", "aaa"),
- Runner.extractFiles(args, "ignored"));
+ Runner.extractArguments(args, "ignored"));
assertEquals(Arrays.asList("ccc"),
- Runner.extractFiles(args, "critical"));
+ Runner.extractArguments(args, "critical"));
assertEquals(Collections.<String>emptyList(),
- Runner.extractFiles(args, "unknown"));
+ Runner.extractArguments(args, "unknown"));
}
}
@Override
public void setStatus(String status) {
}
+
+ @Override
+ public void setDescription(String oldBuildDesc, String newBuildDesc) {
+ }
+
+ @Override
+ public boolean showWarning(String message) {
+ return false;
+ }
};
protected CheckSums CHECKSUMS;
public final long IDEA_BAT;
public final long ANNOTATIONS_JAR;
public final long BOOTSTRAP_JAR;
+ public final long BOOTSTRAP_JAR_BINARY;
public final long FOCUSKILLER_DLL;
+ public final long ANNOTATIONS_JAR_NORM;
+ public final long ANNOTATIONS_CHANGED_JAR_NORM;
+ public final long BOOT_JAR_NORM;
+ public final long BOOT2_JAR_NORM;
+ public final long BOOT2_CHANGED_WITH_UNCHANGED_CONTENT_JAR_NORM;
+ public final long BOOT_WITH_DIRECTORY_BECOMES_FILE_JAR_NORM;
+ public final long BOOTSTRAP_JAR_NORM;
+ public final long BOOTSTRAP_DELETED_JAR_NORM;
public CheckSums(boolean windowsLineEnds) {
if (windowsLineEnds) {
ANNOTATIONS_JAR = 2119442657L;
BOOTSTRAP_JAR = 2082851308L;
FOCUSKILLER_DLL = 1991212227L;
+ BOOTSTRAP_JAR_BINARY = 2745721972L;
+
+ ANNOTATIONS_JAR_NORM = 2119442657L;
+ ANNOTATIONS_CHANGED_JAR_NORM = 4088078858L;
+ BOOT_JAR_NORM = 3018038682L;
+ BOOT2_JAR_NORM = 2406818996L;
+ BOOT2_CHANGED_WITH_UNCHANGED_CONTENT_JAR_NORM = 2406818996L;
+ BOOT_WITH_DIRECTORY_BECOMES_FILE_JAR_NORM = 1972168924;
+ BOOTSTRAP_JAR_NORM = 2082851308;
+ BOOTSTRAP_DELETED_JAR_NORM = 544883981L;
}
}
}
\ No newline at end of file