rsync 3.4.1 drop-in parity (#285-#297) + parity completion (protocol 2.26.0) #298

Merged
TapTap merged 77 commits from feat/rsync-parity into dev 2026-09-17 20:33:17 +02:00
2 changed files with 53 additions and 3 deletions
Showing only changes of commit 695b5c8c25 - Show all commits
+23 -3
View File
@@ -1289,9 +1289,17 @@ Chunk* directory_scanner_next(DirectoryScanner* scanner) {
wire path, not its source path (which would not match the destination wire path, not its source path (which would not match the destination
layout and would leave the mirror deletable). */ layout and would leave the mirror deletable). */
if (inspected.excluded) { if (inspected.excluded) {
char* protected_path = scanner->relative_mode char* protected_path;
? child_rel_path(scanner->current_rel, entry->d_name) if (scanner->relative_mode) {
: path_cat(scanner->current_path, entry->d_name); protected_path = child_rel_path(scanner->current_rel, entry->d_name);
} else if (scanner->options.relative_prefix) {
char* relc = child_rel_path(scanner->current_rel, entry->d_name);
protected_path =
relc ? scanner_prefix_send_path(scanner->options.relative_prefix, relc) : NULL;
free(relc);
} else {
protected_path = path_cat(scanner->current_path, entry->d_name);
}
if (!protected_path) { if (!protected_path) {
scanner->failed = true; scanner->failed = true;
break; break;
@@ -1753,8 +1761,20 @@ static void scan_root_entry(const ScannerOptions* options, const FilterNode* roo
bool files_from_prune = options->file_list && !file_list_affects(options->file_list, rel); bool files_from_prune = options->file_list && !file_list_affects(options->file_list, rel);
if (!files_from_prune && !use_rel && options->excluded_paths) { if (!files_from_prune && !use_rel && options->excluded_paths) {
const char* rel_path = *cur_path == '/' ? cur_path + 1 : cur_path; const char* rel_path = *cur_path == '/' ? cur_path + 1 : cur_path;
char* prefixed = NULL;
if (options->relative_prefix) {
prefixed = scanner_prefix_send_path(options->relative_prefix, entry->d_name);
if (!prefixed) {
free(rel);
free(cur_path);
ps->failed = true;
return;
}
rel_path = prefixed;
}
if (!excluded_sink_append(options->excluded_paths, options->excluded_mutex, rel_path)) if (!excluded_sink_append(options->excluded_paths, options->excluded_mutex, rel_path))
ps->failed = true; ps->failed = true;
free(prefixed);
} }
free(rel); free(rel);
free(cur_path); free(cur_path);
@@ -169,6 +169,36 @@ class TestDirsOneLevel:
== os.path.isfile(os.path.join(rdst, "unrelated", "keep.txt"))) == os.path.isfile(os.path.join(rdst, "unrelated", "keep.txt")))
assert _tree(dest) == _tree(rdst) assert _tree(dest) == _tree(rdst)
@requires_rsync
@pytest.mark.ci
@pytest.mark.parametrize("mt", [False, True])
def test_relative_delete_protects_excluded_mirror(self, mt):
"""-R --delete with --exclude must protect the destination mirror of an
excluded source path (recorded as a prefix-relative wire path)."""
source = _make_tree(os.path.join(TEST_DATA_DIR, "sel_delexc_src"))
with open(os.path.join(source, "foo", "secret.tmp"), "wb") as fh:
fh.write(b"secret\n")
dest = os.path.join(TEST_DATA_DIR, "sel_delexc_dst")
rdst = os.path.join(TEST_DATA_DIR, "sel_delexc_rdst")
for root in (dest, rdst):
clean_dir(root)
os.makedirs(os.path.join(root, "foo"))
with open(os.path.join(root, "foo", "secret.tmp"), "wb") as fh:
fh.write(b"secret\n")
with open(os.path.join(root, "foo", "extra.txt"), "wb") as fh:
fh.write(b"extra\n")
spec = source + "/./foo"
with ServerManager() as server:
server.start(extra_args=["--allow-delete"])
r = _rsync(["-aR", "--delete", "--exclude=*.tmp", spec, rdst + "/"])
assert r.returncode == 0, r.stderr
flags = ["-a", "-R", "--delete", "--exclude=*.tmp"] + (["--threads"] if mt else [])
result, _ = run_client(spec, dest, flags=flags, port=server.port)
assert result.returncode == 0, result.stderr[:300]
assert _tree(dest) == _tree(rdst)
assert os.path.isfile(os.path.join(dest, "foo", "secret.tmp"))
assert not os.path.exists(os.path.join(dest, "foo", "extra.txt"))
class TestClientAliases: class TestClientAliases:
"""#5: safe rsync option aliases accepted client-side.""" """#5: safe rsync option aliases accepted client-side."""