mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-07-30 15:26:48 -04:00
Fill the highest-restructure-risk gap: options that do two-directory / rename /
outside-tree work, asserted at >=3 levels deep with the aux tree kept outside
the main tree, and asserting the option's specific property rather than just
tree equality (which the ported tests already cover).
alt-dest-deep --link-dest hardlinks unchanged files (same inode), --copy-dest
copies (never links), --compare-dest omits unchanged files;
ref tree outside both src and dest.
temp-dir cross-dir temp->final rename at depth; temp dir left clean; a
missing --temp-dir fails (so the option is proven consulted).
partial --partial keeps the partial in the dest file; relative
--partial-dir stages per-directory at depth (pre-seed +
interrupt/resume); absolute --partial-dir writes the partial
outside the tree.
inplace --inplace keeps the destination inode across a delta update;
the default temp+rename path replaces it.
append --append completes truncated files tail-only; --append-verify
repairs a corrupted prefix (protocol >= 30).
backup-deep --suffix saves <name>S beside the new file; --backup-dir
relocates old files to a parallel deep tree outside the dest
and captures deletions under --delete.
All green on master and under --protocol=29/30.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
72 lines
2.4 KiB
Python
72 lines
2.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Coverage of --inplace at depth.
|
|
|
|
--inplace updates the destination file directly instead of writing a temp copy
|
|
and renaming it over the original, so across a delta update the destination
|
|
keeps the SAME inode. Without --inplace the receiver creates a fresh temp file
|
|
and renames it, giving the destination a NEW inode. Both behaviours hinge on
|
|
how the receiver resolves the destination directory and (for the default mode)
|
|
performs the temp->final rename, which the path restructure rewrites; verify
|
|
them on a file >=3 levels deep.
|
|
"""
|
|
|
|
import os
|
|
|
|
from rsyncfns import (
|
|
FROMDIR, TODIR,
|
|
assert_same, make_tree, rmtree, run_rsync, test_fail,
|
|
)
|
|
|
|
src = FROMDIR
|
|
deep = os.path.join('d1', 'd2', 'd3', 'f3')
|
|
|
|
|
|
def seed():
|
|
rmtree(src)
|
|
rmtree(TODIR)
|
|
make_tree(src, depth=3, data=True, data_size=200000)
|
|
|
|
|
|
def inode(path):
|
|
return os.stat(path).st_ino
|
|
|
|
|
|
def modify_deep():
|
|
# Rewrite a chunk in the middle so it is a genuine delta, not just a tail
|
|
# append. Bump mtime by a clear margin (the whole test runs inside one
|
|
# second, so a "now" touch would collide with the destination's mtime and
|
|
# the size-unchanged file would be skipped by the quick check).
|
|
p = src / deep
|
|
data = bytearray(p.read_bytes())
|
|
data[1000:1100] = bytes((b ^ 0xFF) for b in data[1000:1100])
|
|
p.write_bytes(bytes(data))
|
|
st = os.stat(p)
|
|
os.utime(p, (st.st_atime, st.st_mtime + 100))
|
|
|
|
|
|
# --- --inplace keeps the destination inode across a delta update ------------
|
|
seed()
|
|
run_rsync('-a', f'{src}/', f'{TODIR}/')
|
|
ino_before = inode(TODIR / deep)
|
|
|
|
modify_deep()
|
|
run_rsync('-a', '--inplace', '--no-whole-file', f'{src}/', f'{TODIR}/')
|
|
assert_same(TODIR / deep, src / deep, label='inplace content')
|
|
if inode(TODIR / deep) != ino_before:
|
|
test_fail("--inplace changed the destination inode at depth "
|
|
f"({ino_before} -> {inode(TODIR / deep)})")
|
|
|
|
# --- control: the default (temp+rename) path replaces the inode -------------
|
|
seed()
|
|
run_rsync('-a', f'{src}/', f'{TODIR}/')
|
|
ino_before = inode(TODIR / deep)
|
|
|
|
modify_deep()
|
|
run_rsync('-a', '--no-whole-file', f'{src}/', f'{TODIR}/')
|
|
assert_same(TODIR / deep, src / deep, label='default content')
|
|
if inode(TODIR / deep) == ino_before:
|
|
test_fail("default (non-inplace) delta update unexpectedly kept the "
|
|
"destination inode at depth -- temp+rename did not run")
|
|
|
|
print("inplace: same-inode update at depth verified; default replaces inode")
|