WIP 177 보충 5 (28 확인): 짧은 옵션 묶음을 rsync (popt) 처럼 한 글자씩 풂 - -avf"- *.log" · -rf "- x" 의 f 규칙을 따르고, 패턴 안의 C · F 를 -C · -F 로 잘못 보지 않음 (검토 대기)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
next5
RyuJongJin 2026-10-20 11:18:48 +09:00
parent ca63bd1fb5
commit f13de6b1d8
2 changed files with 29 additions and 6 deletions

View File

@ -93,19 +93,29 @@ class RsyncFilter {
fromFile(v!, true); fromFile(v!, true);
} else if ((v = value('--filter')) != null) { } else if ((v = value('--filter')) != null) {
filterRule(v!); filterRule(v!);
} else if (o == '-f' && i + 1 < t.length) {
filterRule(t[++i]);
} else if (o.startsWith('-f') && !o.startsWith('--')) {
// 28 확인: -f"+ *.txt" 처럼 붙여 쓴 것 (따옴표가 벗겨져 "-f+ *.txt" 한 낱말) - 예전에는 말없이 버렸다
filterRule(o.substring(2));
} else if (o == '--files-from' || o.startsWith('--files-from=')) { } else if (o == '--files-from' || o.startsWith('--files-from=')) {
// 28 확인: 고른 파일만 보내므로 미리 보기와 다르다 (미리 보기는 더 많이 보임) - 알린다 // 28 확인: 고른 파일만 보내므로 미리 보기와 다르다 (미리 보기는 더 많이 보임) - 알린다
if (o == '--files-from' && i + 1 < t.length) i++; if (o == '--files-from' && i + 1 < t.length) i++;
unsupported.add(o); unsupported.add(o);
} else if (o == '--delete-excluded') { } else if (o == '--delete-excluded') {
deleteExcluded = true; deleteExcluded = true;
} else if (o == '--cvs-exclude' || o == '-F' || (o.startsWith('-') && !o.startsWith('--') && (o.contains('C') || o.contains('F')))) { } else if (o == '--cvs-exclude') {
unsupported.add(o); unsupported.add(o);
} else if (o.length > 1 && o.startsWith('-') && !o.startsWith('--')) {
// 짧은 옵션 묶음 (-avPog · -avf"- *.log" · -rf "- x"): rsync (popt) 처럼 한 글자씩 푼다 (28 확인).
// 값을 받는 글자 (B · e · f · M · T · @) 를 만나면 그 뒤 글이 값, 비어 있으면 다음 낱말이 값.
// 예전에는 -f 가 맨 앞일 때만 읽고, 묶음 안의 C · F 를 패턴 글자까지 뒤져 잘못 알렸다
for (var j = 1; j < o.length; j++) {
final ch = o[j];
if ('BefMT@'.contains(ch)) {
var arg = o.substring(j + 1);
if (arg.isEmpty && i + 1 < t.length) arg = t[++i];
if (ch == 'f') filterRule(arg);
break;
}
// -C (CVS 빼기 목록) · -F (.rsync-filter 읽기) 는 미리 볼 수 없다
if (ch == 'C' || ch == 'F') unsupported.add('-$ch');
}
} }
} }
return RsyncFilter(rules, deleteExcluded: deleteExcluded, unsupported: unsupported); return RsyncFilter(rules, deleteExcluded: deleteExcluded, unsupported: unsupported);

View File

@ -58,6 +58,15 @@ void main() {
expect(ex('-f"+ *.txt" --exclude=*', 'old.log'), isTrue); expect(ex('-f"+ *.txt" --exclude=*', 'old.log'), isTrue);
expect(ex('-f"- *.log"', 'c.log'), isTrue); expect(ex('-f"- *.log"', 'c.log'), isTrue);
expect(RsyncFilter.parse('--files-from=list.txt').unsupported, ['--files-from=list.txt']); expect(RsyncFilter.parse('--files-from=list.txt').unsupported, ['--files-from=list.txt']);
// 28 확인 (10/20): 짧은 옵션 묶음 안의 f (rsync 처럼 그 뒤가 값, 비면 다음 낱말)
expect(ex('-avf"- *.log"', 'a.log'), isTrue);
expect(ex('-rf "- x"', 'x'), isTrue);
expect(ex('-avPf"+ *.txt" -f"- *"', 'a.txt'), isFalse);
expect(ex('-avPf"+ *.txt" -f"- *"', 'a.log'), isTrue);
expect(RsyncFilter.parse('-f"- *.CFG"').unsupported, isEmpty, reason: '패턴의 C · F 는 -C · -F 가 아님');
expect(RsyncFilter.parse('-avC').unsupported, ['-C']);
expect(RsyncFilter.parse('-aFF').unsupported, ['-F', '-F']);
expect(RsyncFilter.parse('-e "ssh -p 22" -av').isEmpty, isTrue, reason: '-e 의 값은 거르기가 아님');
}); });
test('--exclude-from 파일 (빈 줄 · # · + / - 머리) · 못 읽으면 알림 목록', () { test('--exclude-from 파일 (빈 줄 · # · + / - 머리) · 못 읽으면 알림 목록', () {
final f = RsyncFilter.parse('--exclude-from=list.txt', final f = RsyncFilter.parse('--exclude-from=list.txt',
@ -280,6 +289,10 @@ void main() {
// 28 확인 (63ef362): 붙여 쓴 -f // 28 확인 (63ef362): 붙여 쓴 -f
'-a --delete -f"+ *.txt" --exclude=*', '-a --delete -f"+ *.txt" --exclude=*',
'-a --delete -f"- *.log"', '-a --delete -f"- *.log"',
// 28 확인 (10/20): 짧은 옵션 묶음 안의 f
'-a --delete -rf"- *.log"',
'-a --delete -avf"+ *.txt" -f"- *"',
'--delete -raf "- build/"',
].indexed) { ].indexed) {
test('${i + 1}: $opts', () async { test('${i + 1}: $opts', () async {
if (!Platform.isWindows || !File(rsync).existsSync()) return markTestSkipped('앱에 든 rsync 가 없음'); if (!Platform.isWindows || !File(rsync).existsSync()) return markTestSkipped('앱에 든 rsync 가 없음');