Skip to content

Commit f2ad5e1

Browse files
authored
Detect whether --dry-run was used and add it as a command line flag (#113)
1 parent 6a401db commit f2ad5e1

File tree

3 files changed

+55
-9
lines changed

3 files changed

+55
-9
lines changed

README.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ usage: rsync-time-machine [-h] [-p PORT] [-i ID_RSA] [--rsync-get-flags]
7171
[--rsync-set-flags RSYNC_SET_FLAGS]
7272
[--rsync-append-flags RSYNC_APPEND_FLAGS]
7373
[--log-dir LOG_DIR] [--strategy STRATEGY]
74-
[--no-auto-expire] [--allow-host-only]
74+
[--no-auto-expire] [--allow-host-only] [--dry-run]
7575
[--exclude-from EXCLUDE_FROM] [-v]
7676
src_folder dest_folder [exclusion_file]
7777

@@ -86,9 +86,8 @@ positional arguments:
8686

8787
options:
8888
-h, --help show this help message and exit
89-
-p PORT, --port PORT SSH port.
90-
-i ID_RSA, --id_rsa ID_RSA
91-
Specify the private ssh key to use.
89+
-p, --port PORT SSH port.
90+
-i, --id_rsa ID_RSA Specify the private ssh key to use.
9291
--rsync-get-flags Display the default rsync flags that are used for
9392
backup. If using remote drive over SSH, --compress
9493
will be added.
@@ -117,6 +116,8 @@ options:
117116
the current username. Note: this option will not
118117
enforce SSH usage, it only broadens the accepted input
119118
formats.
119+
--dry-run Simulate the backup process without making any
120+
persistent changes.
120121
--exclude-from EXCLUDE_FROM
121122
Path to the file containing exclude patterns.
122123
Alternative to the positional `exclusion_file`. Not to

rsync_time_machine.py

+28-4
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ def parse_arguments() -> argparse.Namespace: # pragma: no cover
145145
" This is useful if you want to use configurations from the `.ssh/config` file or rely on the current username."
146146
" Note: this option will not enforce SSH usage, it only broadens the accepted input formats.",
147147
)
148+
parser.add_argument(
149+
"--dry-run",
150+
action="store_true",
151+
help="Simulate the backup process without making any persistent changes.",
152+
)
148153

149154
parser.add_argument(
150155
"src_folder",
@@ -803,6 +808,7 @@ def backup(
803808
rsync_append_flags: str,
804809
rsync_get_flags: bool,
805810
allow_host_only: bool,
811+
dry_run: bool,
806812
) -> None:
807813
"""Perform backup of src_folder to dest_folder."""
808814
(
@@ -852,16 +858,25 @@ def backup(
852858
ssh,
853859
)
854860

861+
if "-n" in rsync_flags or "--dry-run" in rsync_flags:
862+
dry_run = True
863+
log_info(
864+
f"Dry-run detected in rsync flags - setting {style('--dry-run', 'green')}.",
865+
)
866+
elif dry_run:
867+
rsync_flags.append("--dry-run")
868+
if dry_run:
869+
log_info(
870+
f"Dry-run mode enabled: {style('no changes will be persisted', 'orange')}.",
871+
)
872+
855873
if rsync_get_flags:
856874
flags = " ".join(rsync_flags)
857875
log_info(f"Rsync flags:\n{style(flags, 'yellow', bold=True)}")
858876
sys.exit(0)
859877

860878
for _ in range(100): # max 100 retries when no space left
861-
link_dest_option = get_link_dest_option(
862-
previous_dest,
863-
ssh,
864-
)
879+
link_dest_option = get_link_dest_option(previous_dest, ssh)
865880

866881
if not find(dest, ssh, maxdepth=0):
867882
_full_dest = style(f"{ssh.cmd if ssh else ''}{dest}", bold=True)
@@ -898,6 +913,14 @@ def backup(
898913

899914
check_rsync_errors(log_file, auto_delete_log)
900915

916+
if dry_run:
917+
# In dry-run mode, clean up any temporary artifacts
918+
# and exit without updating the "latest" symlink.
919+
rm_dir(dest, ssh)
920+
rm_file(inprogress_file, ssh)
921+
log_info("Dry run complete - no backup was saved.")
922+
return
923+
901924
rm_file(os.path.join(dest_folder, "latest"), dest_is_ssh(ssh))
902925
ln(
903926
os.path.basename(dest),
@@ -928,6 +951,7 @@ def main() -> None:
928951
rsync_append_flags=args.rsync_append_flags,
929952
rsync_get_flags=args.rsync_get_flags,
930953
allow_host_only=args.allow_host_only,
954+
dry_run=args.dry_run,
931955
)
932956

933957

tests/test_app.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ def assert_n_backups(dest_folder: str | Path, n_expected: int) -> None:
345345
assert len(find_backups(str(dest_folder), None)) == n_expected
346346

347347

348-
def test_backup(tmp_path: Path, capsys: pytest.CaptureFixture) -> None:
348+
def test_backup(tmp_path: Path, capsys: pytest.CaptureFixture) -> None: # noqa: PLR0915
349349
"""Test the backup function."""
350350
src_folder = tmp_path / "src"
351351
dest_folder = tmp_path / "dest"
@@ -367,6 +367,7 @@ def test_backup(tmp_path: Path, capsys: pytest.CaptureFixture) -> None:
367367
"rsync_append_flags": "",
368368
"rsync_get_flags": False,
369369
"allow_host_only": False,
370+
"dry_run": False,
370371
}
371372
# Tests backup with no backup.marker file
372373
with pytest.raises(SystemExit):
@@ -442,6 +443,25 @@ def test_backup(tmp_path: Path, capsys: pytest.CaptureFixture) -> None:
442443
assert (dest_folder / "latest" / "file3.txt").exists()
443444
assert_n_backups(dest_folder, 2)
444445

446+
# Now test dry-run mode:
447+
# When dry_run is True, no new backup should be persisted.
448+
with patch_now_str(seconds=1):
449+
backup(**dict(kw, dry_run=True)) # type: ignore[arg-type]
450+
# The number of backups should remain unchanged.
451+
assert_n_backups(dest_folder, 2)
452+
captured = capsys.readouterr()
453+
assert "Dry-run detected" not in captured.out
454+
assert "Dry-run mode enabled" in captured.out
455+
456+
# Dry-run inside `rsync_flags`
457+
with patch_now_str(seconds=2):
458+
backup(**dict(kw, rsync_append_flags="-n")) # type: ignore[arg-type]
459+
# The number of backups should remain unchanged.
460+
assert_n_backups(dest_folder, 2)
461+
captured = capsys.readouterr()
462+
assert "Dry-run detected" in captured.out
463+
assert "Dry-run mode enabled" in captured.out
464+
445465

446466
def test_backup_with_non_utf8_filename(tmp_path: Path) -> None:
447467
"""Test the backup function with a non-UTF8 filename.
@@ -478,6 +498,7 @@ def test_backup_with_non_utf8_filename(tmp_path: Path) -> None:
478498
"rsync_append_flags": "",
479499
"rsync_get_flags": False,
480500
"allow_host_only": False,
501+
"dry_run": False,
481502
}
482503

483504
# Create a backup.marker file

0 commit comments

Comments
 (0)