Make dry-run genuinely non-mutating (#23)
* Make dry-run genuinely non-mutating fs.rsync() now takes a real dry_run code path: every mutating call (copy, delete, chmod, chown, utime) is skipped, and a missing dst_dir is never created - the same comparison logic still runs so the yielded actions match what a real run would do. backup.initiate_backup() diffs each source directly against the latest completed snapshot on dry-run, instead of building and discarding a throwaway staging directory with real hardlinks. cleanup_old_backups() no longer creates the repo-id file as a side effect of a dry-run. cli.py skips lock acquisition entirely on dry-run (set_backups_lock() writes PID/timestamp metadata, itself a mutation) and exposes the retention policy via --keep-all/--keep-daily/--keep-weekly/ --keep-monthly/--keep-yearly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Address Copilot review: guard restore lock release, reject negative retention values - Guard restore --dry-run's finally block the same way backup's is guarded, for symmetry (release_backups_lock() was already a safe no-op when unheld, but this makes the dry-run contract explicit rather than relying on that implementation detail). - Reject negative --keep-* values via a dedicated argparse type: a negative threshold shifts retention comparisons into the future and can make cleanup treat every existing backup as deletable. - Add a restore --dry-run lock-skip regression test, mirroring the existing backup one. - Fix _snapshot_all_stats() in the new dry-run non-mutation test to also record the backups_dir root's own stat, not just its children. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Maxim Shulga <maxim.shulga@alludo.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
8 files changed
+478
-51
No files matched your search
@@ -71,6 +71,17 @@ Optional Arguments:
|
||||
-v, --verbose Enable debug logging
|
||||
--external-rsync Use external rsync instead of Python implementation
|
||||
--external-hardlink Use cp/gcp command for hardlinking
|
||||
--keep-all DAYS Keep every backup up to this many days in the past
|
||||
(default: 7)
|
||||
--keep-daily DAYS Keep one backup per day up to this many days in
|
||||
the past (default: 30)
|
||||
--keep-weekly WEEKS Keep one backup per week up to this many weeks in
|
||||
the past (default: 52)
|
||||
--keep-monthly MONTHS
|
||||
Keep one backup per month up to this many months
|
||||
in the past (default: 12)
|
||||
--keep-yearly YEARS Keep one backup per year up to this many years in
|
||||
the past (default: 5)
|
||||
```
|
||||
|
||||
### Examples
|
||||
@@ -159,7 +170,7 @@ cura-te-ipsum creates complete directory snapshots, but files that haven't chang
|
||||
|
||||
### Retention Policy
|
||||
|
||||
Default retention (configurable in code):
|
||||
Default retention (configurable via the `--keep-*` CLI options above):
|
||||
|
||||
- **7 days**: Keep all backups
|
||||
- **30 days**: Keep one backup per day
|
||||
@@ -232,13 +243,14 @@ backups/
|
||||
backend's hardlinker cannot carry forward into the next backup. Pick
|
||||
one of `--external-rsync`/`--external-hardlink` or the Python
|
||||
defaults and use it consistently for a given backup destination.
|
||||
- **`--dry-run` behaves differently per backend, though neither leaves
|
||||
a lasting change.** With `--external-rsync`, rsync itself avoids
|
||||
writing to the destination (it still reads and stats files to
|
||||
compute what would change). With the Python backend, files are still
|
||||
fully copied into a throwaway staging directory, which is then
|
||||
deleted - correct, but slower and more I/O-heavy than the external
|
||||
backend's dry run.
|
||||
- **`--dry-run` never writes anything, with either backend.** Neither
|
||||
backend creates a staging directory, hardlink, lock, or any other
|
||||
file or metadata update - each source is compared directly against
|
||||
the latest completed snapshot (never a copy of it), and the proposed
|
||||
create/rewrite/delete/exclude operations are logged. `--external-rsync`
|
||||
runs real rsync with its own `--dry-run` flag (it still reads and
|
||||
stats files to compute what would change); the Python backend performs
|
||||
the same read-only comparison itself.
|
||||
|
||||
## Development
|
||||
|
||||
|
||||
Reference in new issue
Block a user