feat(parity): general -R/--relative path semantics and --no-implied-dirs

Reconstruct the destination-relative prefix from the source spec outside
--files-from: cut at rsync's first '/./' (or a leading './'), normalize
later '.' components and trailing slashes.  Apply it as each File's
send_path in the sequential and parallel scanners (root and worker paths,
files, one-file-system mount entries and directory-time capture).

Transmit the metadata of implied parent directories (prefix components
above the source root), suppressed by --no-implied-dirs, so parent attrs
match rsync in both the single-threaded and -m pipelines.
This commit is contained in:
2026-09-16 22:41:28 +02:00
parent 9883757190
commit 583d3c8edb
4 changed files with 317 additions and 15 deletions
+54
View File
@@ -1069,6 +1069,59 @@ static void test_scanner_path_relative() {
EXPECT_NULL(scanner_path_relative("/tmp/foo", "/tmp/foobar"));
}
/* -R/--relative destination prefix: the '/./' cut point and normalization. */
static void test_scanner_relative_prefix() {
char* p = NULL;
/* No cut: the whole spec with leading/trailing slashes removed. */
p = scanner_relative_prefix("/tmp/src/foo/");
EXPECT_NOT_NULL(p);
EXPECT_EQ_STR(p, "tmp/src/foo");
free(p);
p = scanner_relative_prefix("src/foo");
EXPECT_NOT_NULL(p);
EXPECT_EQ_STR(p, "src/foo");
free(p);
/* Trailing "/." is the directory itself, not a cut. */
p = scanner_relative_prefix("src/foo/.");
EXPECT_NOT_NULL(p);
EXPECT_EQ_STR(p, "src/foo");
free(p);
/* The first "/./" cuts everything before it. */
p = scanner_relative_prefix("/a/./b/c");
EXPECT_NOT_NULL(p);
EXPECT_EQ_STR(p, "b/c");
free(p);
p = scanner_relative_prefix("src/./");
EXPECT_NOT_NULL(p);
EXPECT_EQ_STR(p, "");
free(p);
/* A later "." component is normalized away. */
p = scanner_relative_prefix("a/./b/./c");
EXPECT_NOT_NULL(p);
EXPECT_EQ_STR(p, "b/c");
free(p);
/* A leading "./" is the cut at the start. */
p = scanner_relative_prefix("./s2");
EXPECT_NOT_NULL(p);
EXPECT_EQ_STR(p, "s2");
free(p);
p = scanner_relative_prefix(".");
EXPECT_NOT_NULL(p);
EXPECT_EQ_STR(p, "");
free(p);
EXPECT_NULL(scanner_relative_prefix(NULL));
EXPECT_NULL(scanner_relative_prefix(""));
}
/* rsync precedence: a deeper .rsync-filter overrides a shallower one, so an
* inner "+ *.tmp" re-includes what the outer "- *.tmp" excluded. */
static void test_per_dir_filter_override(bool parallel) {
@@ -1607,6 +1660,7 @@ void test_scanner() {
test_per_dir_filter(false);
test_per_dir_filter(true);
test_scanner_path_relative();
test_scanner_relative_prefix();
test_per_dir_filter_override(false);
test_per_dir_filter_override(true);
test_dirs_no_descent();