From ca19ee1c4dcdecbdf6aaddbfccceb7d0c65bac67 Mon Sep 17 00:00:00 2001 From: Zen Dodd Date: Sat, 6 Jun 2026 13:39:48 +1000 Subject: [PATCH 1/3] docs: describe files-from comments --- rsync.1.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rsync.1.md b/rsync.1.md index 3306dab0f..9e0f34469 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -2390,7 +2390,9 @@ expand it. The filenames that are read from the FILE are all relative to the source dir -- any leading slashes are removed and no ".." references are allowed - to go higher than the source dir. For example, take this command: + to go higher than the source dir. Blank lines are ignored, as are + whole-line comments that start with '`;`' or '`#`' (unless [`--from0`](#opt) + is specified). For example, take this command: > rsync -a --files-from=/tmp/foo /usr remote:/backup From 372f659835cb48e3450d596ce0277bc7e6210df2 Mon Sep 17 00:00:00 2001 From: Zen Dodd Date: Sat, 6 Jun 2026 14:35:27 +1000 Subject: [PATCH 2/3] testsuite: cover files-from comments --- testsuite/files-from-depth_test.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/testsuite/files-from-depth_test.py b/testsuite/files-from-depth_test.py index beaa85752..5f6c0cce1 100644 --- a/testsuite/files-from-depth_test.py +++ b/testsuite/files-from-depth_test.py @@ -44,6 +44,27 @@ def seed(): for rel in unlisted: assert_not_exists(TODIR / rel, label=f'--from0 excluded {rel}') +# --- comments: line mode ignores them, --from0 treats them as filenames ------ +rmtree(TODIR) +commented = SCRATCHDIR / 'files-commented.lst' +commented.write_text('\n'.join(['', ';ignored', '#ignored', *listed]) + '\n') +run_rsync('-a', f'--files-from={commented}', f'{src}/', f'{TODIR}/') +for rel in listed: + assert_same(TODIR / rel, src / rel, label=f'--files-from comment list {rel}') +for rel in unlisted: + assert_not_exists(TODIR / rel, label=f'--files-from comment list excluded {rel}') + +rmtree(TODIR) +(src / '#literal').write_text('hash literal\n') +(src / ';literal').write_text('semi literal\n') +comments0 = SCRATCHDIR / 'files-comments0.lst' +comments0.write_bytes(b'#literal\0;literal\0') +run_rsync('-a', '--from0', f'--files-from={comments0}', f'{src}/', f'{TODIR}/') +assert_same(TODIR / '#literal', src / '#literal', + label='--from0 literal hash filename') +assert_same(TODIR / ';literal', src / ';literal', + label='--from0 literal semicolon filename') + # --- --exclude-from drops matching files at depth --------------------------- seed() (src / 'a.skip').write_text('s\n') From 91c1e55ad3ffbc5084ac6061af4c0402f2c75e04 Mon Sep 17 00:00:00 2001 From: Zen Dodd Date: Sat, 6 Jun 2026 14:41:42 +1000 Subject: [PATCH 3/3] testsuite: correct files-from comment coverage --- rsync.1.md | 6 +++--- testsuite/files-from-depth_test.py | 21 +++++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/rsync.1.md b/rsync.1.md index 9e0f34469..7aa34f9e7 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -2390,9 +2390,9 @@ expand it. The filenames that are read from the FILE are all relative to the source dir -- any leading slashes are removed and no ".." references are allowed - to go higher than the source dir. Blank lines are ignored, as are - whole-line comments that start with '`;`' or '`#`' (unless [`--from0`](#opt) - is specified). For example, take this command: + to go higher than the source dir. Blank entries are ignored, as are + whole-entry comments that start with '`;`' or '`#`'. For example, take + this command: > rsync -a --files-from=/tmp/foo /usr remote:/backup diff --git a/testsuite/files-from-depth_test.py b/testsuite/files-from-depth_test.py index 5f6c0cce1..3a1b990ae 100644 --- a/testsuite/files-from-depth_test.py +++ b/testsuite/files-from-depth_test.py @@ -44,8 +44,10 @@ def seed(): for rel in unlisted: assert_not_exists(TODIR / rel, label=f'--from0 excluded {rel}') -# --- comments: line mode ignores them, --from0 treats them as filenames ------ +# --- comments: line mode and --from0 both ignore them ----------------------- rmtree(TODIR) +(src / '#ignored').write_text('hash ignored\n') +(src / ';ignored').write_text('semi ignored\n') commented = SCRATCHDIR / 'files-commented.lst' commented.write_text('\n'.join(['', ';ignored', '#ignored', *listed]) + '\n') run_rsync('-a', f'--files-from={commented}', f'{src}/', f'{TODIR}/') @@ -53,17 +55,20 @@ def seed(): assert_same(TODIR / rel, src / rel, label=f'--files-from comment list {rel}') for rel in unlisted: assert_not_exists(TODIR / rel, label=f'--files-from comment list excluded {rel}') +for rel in ['#ignored', ';ignored']: + assert_not_exists(TODIR / rel, label=f'--files-from comment list skipped {rel}') rmtree(TODIR) -(src / '#literal').write_text('hash literal\n') -(src / ';literal').write_text('semi literal\n') comments0 = SCRATCHDIR / 'files-comments0.lst' -comments0.write_bytes(b'#literal\0;literal\0') +comments0.write_bytes( + b'\0;ignored\0#ignored\0' + b'\0'.join(p.encode() for p in listed) + b'\0') run_rsync('-a', '--from0', f'--files-from={comments0}', f'{src}/', f'{TODIR}/') -assert_same(TODIR / '#literal', src / '#literal', - label='--from0 literal hash filename') -assert_same(TODIR / ';literal', src / ';literal', - label='--from0 literal semicolon filename') +for rel in listed: + assert_same(TODIR / rel, src / rel, label=f'--from0 comment list {rel}') +for rel in unlisted: + assert_not_exists(TODIR / rel, label=f'--from0 comment list excluded {rel}') +for rel in ['#ignored', ';ignored']: + assert_not_exists(TODIR / rel, label=f'--from0 comment list skipped {rel}') # --- --exclude-from drops matching files at depth --------------------------- seed()