The man page says that relative basis directory paths are to be relative to the destination directory. This usually works because the receiving rsync chdirs into the destination directory in get_local_name and then follows the paths verbatim.
However, if --dry-run is on and the destination directory does not exist, the receiver skips the chdir and tries to follow basis directory paths from its original working directory, so it doesn't find the correct basis directories. The dry run itemizes transfers where a real run might itemize nothing, local creations, or hard links, violating the principle that rsync should produce the same output with and without --dry-run.
As a fix, I propose that, when --dry-run is on and the destination directory does not exist, the receiver should chdir instead into its parent directory. (The parent has to exist in order for the destination to be created on a real run, so I think it's reasonable to also expect the parent to exist on a dry run.) Then the receiver should remove an initial ../ from each basis directory path that has one and discard basis directory paths that do not begin with ../ .
I checked-in some changes that fix this by turning relative paths in the basis_dir array into absolute paths based on the destination directory when the destination directory does not yet exist and --dry-run was used.
Created attachment 2178 [details]
Test case for this bug
Awesome! You fixed it! Perhaps you'd like a test case?
This isn't completely fixed. fix_basis_dirs calls clean_fname, which will remove all x/../ sequences from each joined basis dir path, not just the one straddling the boundary between destination path and original basis dir path. This might not be correct if x is a symlink. For example:
$ mkdir zone
$ ln -s . zone/me
$ mkdir src basis
$ touch src/foo
$ rsync -a src/ basis/
$ rsync -n -ai --compare-dest=../../basis/ src/ zone/me/dest/
$ rsync -ai --compare-dest=../../basis/ src/ zone/me/dest/
I doubt that a correctly written command line will ever produce incorrect results due to this bug. However, what could happen is that a user writes "../" steps in the basis dir path expecting them to cancel steps in the destination path, not realizing that some of the steps in the destination path are symlinks. The user wisely runs rsync in --dry-run mode first and rsync seems to find the basis dir correctly, so she assumes her command is correct and runs rsync for real--and rsync doesn't find the basis dir.
Another question: why does rsync itemize the top-level destination directory as a creation in --dry-run mode but not in real mode? Maybe this should be a separate bug.
I changed the function to limit the path cleanup that it will do (only allowing a single ../ to be collapsed).
As for the itemizing of the root dir when doing a copy like this:
rsync -ai dir/ newdir/
I decided that I would like to see the "./" dir being itemized as a new creation, so I added code to make that happen.