rollBack method

void rollBack()

Puts every file and folder back as it was, and removes the journal.

Nothing once the change has committed. Safe to run again after it was cut short: a folder moved in is only removed while the one it replaced still waits aside, and a file already put back (_restoredLog) is left as it is now. When a step fails — a file in use, a backup that is missing — the others are still taken; the first failure is rethrown and the journal kept, so recover finishes the job (or, for a missing backup, says where the others are).

Implementation

void rollBack() {
  if (_state == _committed || _state == _rolledBack) return;
  _state = _rollingBack;
  try {
    _save();
  } on Object {
    // The journal still says started: rolled back all the same.
  }
  Object? failure;
  StackTrace? trace;
  void step(void Function() action) {
    try {
      action();
    } on Object catch (e, s) {
      failure ??= e;
      trace ??= s;
    }
  }

  for (final path in _movedIn.reversed) {
    step(() {
      final d = project.dir(path);
      if (!d.existsSync()) return;
      // Once the folder it replaced is back, [path] holds the original.
      final aside = _asideOf(path);
      if (aside != null && !aside.existsSync()) return;
      retryOnLock(() => d.deleteSync(recursive: true));
    });
  }
  for (final e in _aside.entries) {
    step(() {
      final aside = _asideDir(e.value);
      if (!aside.existsSync()) return; // Never moved, or back already.
      final back = project.dir(e.key);
      if (back.existsSync()) {
        retryOnLock(() => back.deleteSync(recursive: true));
      }
      retryOnLock(() => aside.renameSync(back.path));
    });
  }
  // Newest first: of two spellings of one file, the one backed up first —
  // the file as it was — is put back last.
  for (final e in _backups.entries.toList().reversed) {
    if (_restored.contains(e.key)) continue;
    step(() {
      // Never put back through a folder that now leads out of the project.
      _inside(e.key);
      final path = project.path(e.key);
      final type = FileSystemEntity.typeSync(path, followLinks: false);
      final link = _links[e.key];
      final name = e.value;
      if (link != null) {
        // A symbolic link: made again, leading where it did.
        if (type != FileSystemEntityType.link ||
            Link(path).targetSync() != link) {
          _removeFile(path, type);
          Link(path).createSync(link, recursive: true);
        }
      } else if (name == null) {
        _removeFile(path, type);
      } else {
        final backup = File('${dir.path}/backup/$name');
        if (!backup.existsSync()) {
          throw FileSystemException(
            'The copy of ${e.key} made before the change is missing, so it '
            'was not put back. The rest of the change is in '
            '${normalPath(dir.path)}: put back what you need, then delete '
            'that folder',
            backup.path,
          );
        }
        // It was a file: a link made since goes, not what it leads to.
        if (type == FileSystemEntityType.link) {
          retryOnLock(Link(path).deleteSync);
        }
        // A copy, so the file gets its permissions back too.
        copyAtomic(backup, File(path));
      }
      _noteRestored(e.key);
    });
  }
  if (failure != null) Error.throwWithStackTrace(failure!, trace!);
  _state = _rolledBack;
  var marked = true;
  try {
    File('${dir.path}/$_rolledBackMarker').writeAsStringSync('', flush: true);
  } on FileSystemException {
    marked = false;
  }
  if (!_cleanUp() && !marked) {
    throw FileSystemException(
      'The change was undone, but its journal could be neither removed nor '
      'marked as undone. Delete ${normalPath(dir.path)} before the next '
      'change, or that change would undo it again',
      _journal.path,
    );
  }
}