@Override
  protected void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException {
    LOG.info("Update action. File: " + toFile.getAbsolutePath());

    File source = mandatoryBackup() ? getSource(Objects.requireNonNull(backupDir)) : toFile;
    if (!isMove()) {
      try (InputStream in = Utils.findEntryInputStream(patchFile, getPath())) {
        if (in == null) {
          throw new IOException("Invalid entry " + getPath());
        }

        FileType type = readFileType(in);
        File tempFile = Utils.getTempFile(toFile.getName());
        if (isCritical()) {
          // If the file is critical, we always store the full file in the patch. So, we can just restore it from the patch.
          try (OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile))) {
            applyDiff(in, null, out);
          }
        }
        else {
          try (OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile));
               InputStream oldFileIn = source.exists() ? Utils.newFileInputStream(source) : null) {
            applyDiff(in, oldFileIn, out);
          }
        }

        if (type == FileType.EXECUTABLE_FILE) {
          Utils.setExecutable(tempFile);
        }

        source = tempFile;
      }
    }

    if (Utils.IS_MAC) {
      // on macOS, code signing caches seem to be associated with specific file IDs, so we need to remove the original file
      Utils.delete(toFile);
      Utils.copy(source, toFile, false);
    }
    else {
      Utils.copy(source, toFile, true);
    }
  }
@Override
  protected void doApply(ZipFile patchFile, File backupDir, File toFile)