From fce97d11c66e28bf629806b5df512cab7af36120 Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Sun, 14 Dec 2014 18:34:42 +0100 Subject: [PATCH] Force-include the specified paths in 'create' Ensure that if a PATH is specified directly in ``attic create`` is going to be included in the archive irregardless of the --exclude rules. We achieve this by building an intermediate exclusion list, and checking each exclusion pattern directly against the specified path. If the pattern matches, it's removed. This allows to have general exclusion rules while still including wanted subtrees in the archive. With the following file list: root/file1 root/unwanted/subtree1/file2 root/unwanted/subtree2/file3 The command: attic create archive::name --exclude root/unwanted root root/unwanted/subtree2 Will correctly archive: root/file1 root/unwanted/subtree2/file3. This reduces (removes?) the need of an additional --include flag, since it reduces the include/exclude logic to the most specific pattern directly specified on the command line. --- attic/archiver.py | 9 +++++---- attic/helpers.py | 10 ++++++++-- attic/testsuite/archiver.py | 11 +++++++++++ attic/testsuite/helpers.py | 4 ++-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/attic/archiver.py b/attic/archiver.py index 47650c2d..b2be0035 100644 --- a/attic/archiver.py +++ b/attic/archiver.py @@ -15,8 +15,8 @@ from attic.cache import Cache from attic.key import key_creator from attic.helpers import Error, location_validator, format_time, \ - format_file_mode, ExcludePattern, exclude_path, adjust_patterns, to_localtime, \ - get_cache_dir, get_keys_dir, format_timedelta, prune_within, prune_split, \ + format_file_mode, ExcludePattern, exclude_path, adjust_include_patterns, adjust_exclude_patterns, \ + to_localtime, get_cache_dir, get_keys_dir, format_timedelta, prune_within, prune_split, \ Manifest, remove_surrogates, update_excludes, format_archive, check_extension_modules, Statistics, \ is_cachedir, bigint_to_int from attic.remote import RepositoryServer, RemoteRepository @@ -125,7 +125,8 @@ def do_create(self, args): continue else: restrict_dev = None - self._process(archive, cache, args.excludes, args.exclude_caches, skip_inodes, path, restrict_dev) + excludes = adjust_exclude_patterns(path, args.excludes) + self._process(archive, cache, excludes, args.exclude_caches, skip_inodes, path, restrict_dev) archive.save() if args.stats: t = datetime.now() @@ -192,7 +193,7 @@ def do_extract(self, args): manifest, key = Manifest.load(repository) archive = Archive(repository, key, manifest, args.archive.archive, numeric_owner=args.numeric_owner) - patterns = adjust_patterns(args.paths, args.excludes) + patterns = adjust_include_patterns(args.paths, args.excludes) dry_run = args.dry_run strip_components = args.strip_components dirs = [] diff --git a/attic/helpers.py b/attic/helpers.py index ac526698..2cf46542 100644 --- a/attic/helpers.py +++ b/attic/helpers.py @@ -198,13 +198,20 @@ def update_excludes(args): file.close() -def adjust_patterns(paths, excludes): +def adjust_include_patterns(paths, excludes): if paths: return (excludes or []) + [IncludePattern(path) for path in paths] + [ExcludePattern('*')] else: return excludes +def adjust_exclude_patterns(path, excludes): + if excludes: + return [pattern for pattern in excludes if not pattern.match(path)] + else: + return [] + + def exclude_path(path, patterns): """Used by create and extract sub-commands to determine whether or not an item should be processed. @@ -566,4 +573,3 @@ def int_to_bigint(value): if value.bit_length() > 63: return value.to_bytes((value.bit_length() + 9) // 8, 'little', signed=True) return value - diff --git a/attic/testsuite/archiver.py b/attic/testsuite/archiver.py index 382fcc85..db62c1af 100644 --- a/attic/testsuite/archiver.py +++ b/attic/testsuite/archiver.py @@ -197,6 +197,17 @@ def test_extract_include_exclude(self): self.attic('extract', '--exclude-from=' + self.exclude_file_path, self.repository_location + '::test') self.assert_equal(sorted(os.listdir('output/input')), ['file1', 'file3']) + def test_subtree_include_exclude(self): + self.attic('init', self.repository_location) + self.create_regular_file('file1', size=1024 * 80) + self.create_regular_file('unwanted/file2', size=1024 * 80) + self.create_regular_file('unwanted/wanted/file3', size=1024 * 80) + self.attic('create', '--exclude=input/unwanted', self.repository_location + '::test', 'input', 'input/unwanted/wanted') + with changedir('output'): + self.attic('extract', self.repository_location + '::test', 'input') + self.assert_equal(sorted(os.listdir('output/input')), ['file1', 'unwanted']) + self.assert_equal(sorted(os.listdir('output/input/unwanted')), ['wanted']) + def test_exclude_caches(self): self.attic('init', self.repository_location) self.create_regular_file('file1', size=1024 * 80) diff --git a/attic/testsuite/helpers.py b/attic/testsuite/helpers.py index e01b652c..b4fb304a 100644 --- a/attic/testsuite/helpers.py +++ b/attic/testsuite/helpers.py @@ -4,7 +4,7 @@ import os import tempfile import unittest -from attic.helpers import adjust_patterns, exclude_path, Location, format_timedelta, IncludePattern, ExcludePattern, make_path_safe, UpgradableLock, prune_within, prune_split, to_localtime, \ +from attic.helpers import adjust_include_patterns, exclude_path, Location, format_timedelta, IncludePattern, ExcludePattern, make_path_safe, UpgradableLock, prune_within, prune_split, to_localtime, \ StableDict, int_to_bigint, bigint_to_int from attic.testsuite import AtticTestCase import msgpack @@ -73,7 +73,7 @@ class PatternTestCase(AtticTestCase): ] def evaluate(self, paths, excludes): - patterns = adjust_patterns(paths, [ExcludePattern(p) for p in excludes]) + patterns = adjust_include_patterns(paths, [ExcludePattern(p) for p in excludes]) return [path for path in self.files if not exclude_path(path, patterns)] def test(self):