mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-06-08 06:05:57 -04:00
Cover the structure and link options at >=3 levels and across directories,
asserting each option's specific effect:
links -l keeps a symlink, -L dereferences it, -k follows a
directory symlink -- all on a symlink several levels deep.
dirs -d copies the top layer (file + empty dir) without recursing.
prune-empty-dirs -m drops empty chains and chains emptied by an exclude,
keeps populated ones.
hardlinks-deep -H preserves a hard link whose names live in different
directories at depth; without -H they become separate inodes.
delete-deep --delete removes a deep extraneous file/subtree; the four
delete-timing variants agree; --max-delete caps deletions;
--existing / --ignore-existing select/skip correctly.
relative-implied -R mirrors an implied directory's mode at depth;
--no-implied-dirs does not (proto 30+).
Green on master and under --protocol=29/30 (the --no-implied-dirs sub-case is
gated to protocol >= 30, where multi-component sender paths are accepted).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
46 lines
1.6 KiB
Python
46 lines
1.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Coverage of -m / --prune-empty-dirs at depth.
|
|
|
|
--prune-empty-dirs drops directory chains that would end up empty at the
|
|
destination -- both chains that are empty in the source and chains that become
|
|
empty because a filter excluded their only files. Populated chains are kept.
|
|
"""
|
|
|
|
from rsyncfns import (
|
|
FROMDIR, TODIR,
|
|
assert_not_exists, assert_same, makepath, rmtree, run_rsync,
|
|
)
|
|
|
|
src = FROMDIR
|
|
|
|
|
|
def reseed():
|
|
rmtree(src)
|
|
rmtree(TODIR)
|
|
|
|
|
|
# --- a deep empty chain is pruned; a deep populated chain is kept ------------
|
|
reseed()
|
|
makepath(src / 'empty' / 'e1' / 'e2', src / 'full' / 'd1' / 'd2')
|
|
(src / 'full' / 'd1' / 'd2' / 'file').write_text("data\n")
|
|
|
|
run_rsync('-a', '-m', f'{src}/', f'{TODIR}/')
|
|
assert_not_exists(TODIR / 'empty', label='-m pruned an empty chain')
|
|
assert_same(TODIR / 'full' / 'd1' / 'd2' / 'file',
|
|
src / 'full' / 'd1' / 'd2' / 'file', label='-m kept populated chain')
|
|
|
|
# --- a chain emptied by an exclude filter is also pruned --------------------
|
|
reseed()
|
|
makepath(src / 'mixed' / 'sub', src / 'onlylogs' / 'sub')
|
|
(src / 'mixed' / 'sub' / 'keep.txt').write_text("k\n")
|
|
(src / 'mixed' / 'sub' / 'drop.log').write_text("d\n")
|
|
(src / 'onlylogs' / 'sub' / 'a.log').write_text("a\n")
|
|
|
|
run_rsync('-a', '-m', '--exclude=*.log', f'{src}/', f'{TODIR}/')
|
|
assert_same(TODIR / 'mixed' / 'sub' / 'keep.txt',
|
|
src / 'mixed' / 'sub' / 'keep.txt', label='-m kept non-empty dir')
|
|
assert_not_exists(TODIR / 'onlylogs',
|
|
label='-m pruned a dir emptied by an exclude')
|
|
|
|
print("prune-empty-dirs: empty and filter-emptied chains pruned, populated kept")
|