rollbackMaterialization function

RecoveryReport rollbackMaterialization({
  1. required ChangePlan plan,
  2. required String canonicalDestination,
  3. ApplyFileSystem fileSystem = const ApplyFileSystem(),
  4. int maxAttempts = 3,
  5. Duration initialBackoff = const Duration(milliseconds: 50),
  6. void sleep(
    1. Duration delay
    )?,
})

Rolls back 0.1's materialize-only Apply into a fresh destination.

Every materialized target is newly created, so FR-117–FR-121 recovery only deletes and verifies those targets; there are no original bytes to restore.

Implementation

RecoveryReport rollbackMaterialization({
  required ChangePlan plan,
  required String canonicalDestination,
  ApplyFileSystem fileSystem = const ApplyFileSystem(),
  int maxAttempts = 3,
  Duration initialBackoff = const Duration(milliseconds: 50),
  void Function(Duration delay)? sleep,
}) {
  if (maxAttempts < 1) {
    throw ArgumentError.value(maxAttempts, 'maxAttempts', 'must be at least 1');
  }
  final pause = sleep ?? io.sleep;
  final removed = <String>[];
  final unresolved = <String>[];
  final actions = <String>[];
  final operations =
      plan.operations
          .where((operation) => operation.ownershipClass.isMaterialized)
          .toList()
        ..sort((a, b) => a.logicalPath.compareTo(b.logicalPath));

  for (final operation in operations) {
    final target = applyPath(canonicalDestination, operation.logicalPath);
    if (!fileSystem.fileExists(target)) continue;
    var delay = initialBackoff;
    var deleted = false;
    for (var attempt = 0; attempt < maxAttempts; attempt++) {
      try {
        fileSystem.deleteFile(target);
      } on io.FileSystemException {
        // Verification below decides the outcome; deletion errors are retryable.
      }
      if (!fileSystem.fileExists(target)) {
        removed.add(operation.logicalPath);
        deleted = true;
        break;
      }
      if (attempt + 1 < maxAttempts) {
        pause(delay);
        delay *= 2;
      }
    }
    if (!deleted) {
      unresolved.add(operation.logicalPath);
      actions.add('Manually remove $target.');
    }
  }

  return buildRecoveryReport(
    restoredPaths: const [],
    removedCreatedPaths: removed,
    unresolvedPaths: unresolved,
    manualActions: actions,
  );
}