diff --git a/lib/core/rsync_filter.dart b/lib/core/rsync_filter.dart index 1a3a9f6..684632e 100644 --- a/lib/core/rsync_filter.dart +++ b/lib/core/rsync_filter.dart @@ -93,19 +93,29 @@ class RsyncFilter { fromFile(v!, true); } else if ((v = value('--filter')) != null) { 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=')) { // 28 확인: 고른 파일만 보내므로 미리 보기와 다르다 (미리 보기는 더 많이 보임) - 알린다 if (o == '--files-from' && i + 1 < t.length) i++; unsupported.add(o); } else if (o == '--delete-excluded') { 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); + } 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); diff --git a/test/rsync_filter_test.dart b/test/rsync_filter_test.dart index d7b8a9a..c70eb7a 100644 --- a/test/rsync_filter_test.dart +++ b/test/rsync_filter_test.dart @@ -58,6 +58,15 @@ void main() { expect(ex('-f"+ *.txt" --exclude=*', 'old.log'), isTrue); expect(ex('-f"- *.log"', 'c.log'), isTrue); 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 파일 (빈 줄 · # · + / - 머리) · 못 읽으면 알림 목록', () { final f = RsyncFilter.parse('--exclude-from=list.txt', @@ -280,6 +289,10 @@ void main() { // 28 확인 (63ef362): 붙여 쓴 -f '-a --delete -f"+ *.txt" --exclude=*', '-a --delete -f"- *.log"', + // 28 확인 (10/20): 짧은 옵션 묶음 안의 f + '-a --delete -rf"- *.log"', + '-a --delete -avf"+ *.txt" -f"- *"', + '--delete -raf "- build/"', ].indexed) { test('${i + 1}: $opts', () async { if (!Platform.isWindows || !File(rsync).existsSync()) return markTestSkipped('앱에 든 rsync 가 없음');