From 4bd597a38d4c28095ff1939dd1e473bf85665e17 Mon Sep 17 00:00:00 2001 From: "Stiliyan Tonev (Bark)" Date: Wed, 3 Jun 2026 11:12:10 +0300 Subject: [PATCH 1/2] fix: Error thrown during dry-run `get_local_name` refers to 2 "modes", when the destination is a folder and when its a file. It seems that `change_dir` should take into account if we are dry-running in both cases, but it was considering the option only when dest was a folder --- main.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/main.c b/main.c index c54fd79bc..aa9ba0c16 100644 --- a/main.c +++ b/main.c @@ -781,6 +781,11 @@ static char *get_local_name(struct file_list *flist, char *dest_path) exit_cleanup(RERR_FILESELECT); } + if (dry_run) { + /* Indicate that dest dir doesn't really exist. */ + dry_run++; + } + /* If we need a destination directory because the transfer is not * of a single non-directory or the user has requested one via a * destination path ending in a slash, create one and use mode 1. */ @@ -807,11 +812,6 @@ static char *get_local_name(struct file_list *flist, char *dest_path) if (INFO_GTE(NAME, 1) || stdout_format_has_i) rprintf(FINFO, "created directory %s\n", dest_path); - if (dry_run) { - /* Indicate that dest dir doesn't really exist. */ - dry_run++; - } - if (!change_dir(dest_path, dry_run > 1 ? CD_SKIP_CHDIR : CD_NORMAL)) { rsyserr(FERROR, errno, "change_dir#2 %s failed", full_fname(dest_path)); @@ -832,7 +832,7 @@ static char *get_local_name(struct file_list *flist, char *dest_path) dest_path = "/"; *cp = '\0'; - if (!change_dir(dest_path, CD_NORMAL)) { + if (!change_dir(dest_path, dry_run > 1 ? CD_SKIP_CHDIR : CD_NORMAL)) { rsyserr(FERROR, errno, "change_dir#3 %s failed", full_fname(dest_path)); exit_cleanup(RERR_FILESELECT); From 91e3f103d9670a40bf926b54cfddb50731071180 Mon Sep 17 00:00:00 2001 From: "Stiliyan Tonev (Bark)" Date: Wed, 3 Jun 2026 20:05:46 +0300 Subject: [PATCH 2/2] Add test for --mkpath + --dry-run on file-to-file copy --- testsuite/file-to-file-mkpath-dry-run_test.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 testsuite/file-to-file-mkpath-dry-run_test.py diff --git a/testsuite/file-to-file-mkpath-dry-run_test.py b/testsuite/file-to-file-mkpath-dry-run_test.py new file mode 100644 index 000000000..2e1028652 --- /dev/null +++ b/testsuite/file-to-file-mkpath-dry-run_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# Copying file-to-file with --mkpath and --dry-run had different behavior than to-directory. +# The --dry-run would fail while the actual command would succeed. + +import os + +from rsyncfns import ( + TMPDIR, rsync_argv, assert_same, assert_exists +) +import subprocess + +os.chdir(TMPDIR) + +os.mkdir("from") +with open("from/source_file", "w") as f: + f.write("payload\n") + +# Check if dry-run works +dry_run_result = subprocess.run(rsync_argv('--dry-run', '--recursive', '--mkpath', '--links', '--perms', '--times', '--omit-dir-times', 'from/source_file', 'dest_dir/dest_file'), capture_output=True, text=True) +actual_run = subprocess.run(rsync_argv('--recursive', '--mkpath', '--links', '--perms', '--times', '--omit-dir-times', 'from/source_file', 'dest_dir/dest_file'), capture_output=True, text=True) +(TMPDIR / 'dry_run.out').write_text(dry_run_result.stdout + dry_run_result.stderr) +(TMPDIR / 'actual_run.out').write_text(actual_run.stdout + actual_run.stderr) +print(dry_run_result.stdout) +print(actual_run.stdout) +assert_same(TMPDIR / 'dry_run.out', TMPDIR / 'actual_run.out', label='dry-run vs actual run output') +assert_exists("dest_dir/dest_file")