rsync 3.4.1 drop-in parity (#285-#297) + parity completion (protocol 2.26.0) #298
@@ -335,6 +335,24 @@ static bool append_implied_dir_times(const Config* config, ArrayList* dir_entrie
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The delete-walk root scope for a full (non---files-from) transfer: rsync
|
||||||
|
* confines --delete to the directories it actually transferred. A plain
|
||||||
|
* recursive run mirrors the source under the receive root, so "." (the whole
|
||||||
|
* tree) is correct; an -R run transfers only the reconstructed prefix subtree,
|
||||||
|
* so the walk is scoped to that prefix instead. Returns a malloc'd wire path
|
||||||
|
* (or "."), or NULL on allocation failure. */
|
||||||
|
static char* delete_scope_root_marker(const Config* config) {
|
||||||
|
if (config->relative && config->files_from_set == NULL && config->send_directory) {
|
||||||
|
char* prefix = scanner_relative_prefix(config->send_directory);
|
||||||
|
if (!prefix)
|
||||||
|
return NULL;
|
||||||
|
if (prefix[0] != '\0')
|
||||||
|
return prefix;
|
||||||
|
free(prefix);
|
||||||
|
}
|
||||||
|
return str_dup(".");
|
||||||
|
}
|
||||||
|
|
||||||
/* True when some --files-from entry is an ancestor-or-equal directory of
|
/* True when some --files-from entry is an ancestor-or-equal directory of
|
||||||
* `rel` (an empty entry -- the whole tree "." -- counts as the root). */
|
* `rel` (an empty entry -- the whole tree "." -- counts as the root). */
|
||||||
static bool file_list_ancestor_listed(const FileListSet* set, const char* rel) {
|
static bool file_list_ancestor_listed(const FileListSet* set, const char* rel) {
|
||||||
@@ -2561,7 +2579,7 @@ int send_files(Config* config) {
|
|||||||
receive root, so mark the root itself (the "." sentinel) and let the
|
receive root, so mark the root itself (the "." sentinel) and let the
|
||||||
scanner record nothing extra. */
|
scanner record nothing extra. */
|
||||||
if (config->files_from_set == NULL) {
|
if (config->files_from_set == NULL) {
|
||||||
char* root_marker = str_dup(".");
|
char* root_marker = delete_scope_root_marker(config);
|
||||||
if (!root_marker || !array_list_add(synced_dirs, root_marker)) {
|
if (!root_marker || !array_list_add(synced_dirs, root_marker)) {
|
||||||
free(root_marker);
|
free(root_marker);
|
||||||
goto send_fail;
|
goto send_fail;
|
||||||
@@ -2918,7 +2936,7 @@ int send_files_multithreaded(Config** config_ptr) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (config->files_from_set == NULL) {
|
if (config->files_from_set == NULL) {
|
||||||
char* root_marker = str_dup(".");
|
char* root_marker = delete_scope_root_marker(config);
|
||||||
if (!root_marker || !array_list_add(context->synced_dirs, root_marker)) {
|
if (!root_marker || !array_list_add(context->synced_dirs, root_marker)) {
|
||||||
free(root_marker);
|
free(root_marker);
|
||||||
pipeline_context_sender_destroy(context);
|
pipeline_context_sender_destroy(context);
|
||||||
|
|||||||
@@ -141,6 +141,34 @@ class TestDirsOneLevel:
|
|||||||
assert result.returncode == 0, result.stderr[:300]
|
assert result.returncode == 0, result.stderr[:300]
|
||||||
assert _tree(rdst) == _tree(dest)
|
assert _tree(rdst) == _tree(dest)
|
||||||
|
|
||||||
|
@requires_rsync
|
||||||
|
@pytest.mark.ci
|
||||||
|
def test_relative_delete_scope_matches_rsync(self):
|
||||||
|
"""-R --delete must be confined to the transferred prefix subtree so a
|
||||||
|
sibling destination directory survives (rsync parity)."""
|
||||||
|
source = _make_tree(os.path.join(TEST_DATA_DIR, "sel_delscope_src"))
|
||||||
|
dest = os.path.join(TEST_DATA_DIR, "sel_delscope_dst")
|
||||||
|
rdst = os.path.join(TEST_DATA_DIR, "sel_delscope_rdst")
|
||||||
|
spec = source + "/./foo"
|
||||||
|
for root in (dest, rdst):
|
||||||
|
clean_dir(root)
|
||||||
|
os.makedirs(os.path.join(root, "foo"))
|
||||||
|
with open(os.path.join(root, "foo", "extra.txt"), "wb") as fh:
|
||||||
|
fh.write(b"extra\n")
|
||||||
|
os.makedirs(os.path.join(root, "unrelated"))
|
||||||
|
with open(os.path.join(root, "unrelated", "keep.txt"), "wb") as fh:
|
||||||
|
fh.write(b"keep\n")
|
||||||
|
with ServerManager() as server:
|
||||||
|
server.start(extra_args=["--allow-delete"])
|
||||||
|
r = _rsync(["-aR", "--delete", spec, rdst + "/"])
|
||||||
|
assert r.returncode == 0, r.stderr
|
||||||
|
result, _ = run_client(spec, dest, flags=["-a", "-R", "--delete"],
|
||||||
|
port=server.port)
|
||||||
|
assert result.returncode == 0, result.stderr[:300]
|
||||||
|
assert (os.path.isfile(os.path.join(dest, "unrelated", "keep.txt"))
|
||||||
|
== os.path.isfile(os.path.join(rdst, "unrelated", "keep.txt")))
|
||||||
|
assert _tree(dest) == _tree(rdst)
|
||||||
|
|
||||||
|
|
||||||
class TestClientAliases:
|
class TestClientAliases:
|
||||||
"""#5: safe rsync option aliases accepted client-side."""
|
"""#5: safe rsync option aliases accepted client-side."""
|
||||||
|
|||||||
Reference in New Issue
Block a user