From a6a8a4ebd9af652cd3eaf63362ee0d8d8d4bff13 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 9 Nov 2015 03:41:06 +0100 Subject: [PATCH] Implement --exclude-if-present Add a new --exclude-if-present command-line flag to ``borg create``. If specified, directories containing the specified tag file will be excluded from the backup. The flag can be repeated to ignore more than a single tag file, irregardless of the contents. This is taken from a attic PR (and adapted for borg): commit 3462a9ca90388dc5d8b4fa4218a32769676b3623 Author: Yuri D'Elia Date: Sun Dec 7 19:15:17 2014 +0100 --- borg/archiver.py | 19 +++++++++++-------- borg/helpers.py | 16 +++++++++++++++- borg/testsuite/archiver.py | 11 +++++++++++ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/borg/archiver.py b/borg/archiver.py index 88a423311..4e4682059 100644 --- a/borg/archiver.py +++ b/borg/archiver.py @@ -20,7 +20,7 @@ from .helpers import Error, location_validator, format_time, format_file_size, \ format_file_mode, ExcludePattern, IncludePattern, exclude_path, adjust_patterns, to_localtime, timestamp, \ get_cache_dir, get_keys_dir, prune_within, prune_split, unhexlify, \ Manifest, remove_surrogates, update_excludes, format_archive, check_extension_modules, Statistics, \ - is_cachedir, bigint_to_int, ChunkerParams, CompressionSpec, have_cython, is_slow_msgpack, yes, \ + dir_is_tagged, bigint_to_int, ChunkerParams, CompressionSpec, have_cython, is_slow_msgpack, yes, \ EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR from .logger import create_logger, setup_logging logger = create_logger() @@ -166,8 +166,8 @@ class Archiver: continue else: restrict_dev = None - self._process(archive, cache, args.excludes, args.exclude_caches, skip_inodes, path, restrict_dev, - read_special=args.read_special, dry_run=dry_run) + self._process(archive, cache, args.excludes, args.exclude_caches, args.exclude_if_present, + skip_inodes, path, restrict_dev, read_special=args.read_special, dry_run=dry_run) if not dry_run: archive.save(timestamp=args.timestamp) if args.progress: @@ -182,8 +182,8 @@ class Archiver: print('-' * 78) return self.exit_code - def _process(self, archive, cache, excludes, exclude_caches, skip_inodes, path, restrict_dev, - read_special=False, dry_run=False): + def _process(self, archive, cache, excludes, exclude_caches, exclude_if_present, + skip_inodes, path, restrict_dev, read_special=False, dry_run=False): if exclude_path(path, excludes): return try: @@ -209,7 +209,7 @@ class Archiver: status = 'E' self.print_warning('%s: %s', path, e) elif stat.S_ISDIR(st.st_mode): - if exclude_caches and is_cachedir(path): + if dir_is_tagged(path, exclude_caches, exclude_if_present): return if not dry_run: status = archive.process_dir(path, st) @@ -221,8 +221,8 @@ class Archiver: else: for filename in sorted(entries): entry_path = os.path.normpath(os.path.join(path, filename)) - self._process(archive, cache, excludes, exclude_caches, skip_inodes, - entry_path, restrict_dev, read_special=read_special, + self._process(archive, cache, excludes, exclude_caches, exclude_if_present, + skip_inodes, entry_path, restrict_dev, read_special=read_special, dry_run=dry_run) elif stat.S_ISLNK(st.st_mode): if not dry_run: @@ -785,6 +785,9 @@ class Archiver: subparser.add_argument('--exclude-caches', dest='exclude_caches', action='store_true', default=False, help='exclude directories that contain a CACHEDIR.TAG file (http://www.brynosaurus.com/cachedir/spec.html)') + subparser.add_argument('--exclude-if-present', dest='exclude_if_present', + metavar='FILENAME', action='append', type=str, + help='exclude directories that contain the specified file') subparser.add_argument('-c', '--checkpoint-interval', dest='checkpoint_interval', type=int, default=300, metavar='SECONDS', help='write checkpoint every SECONDS seconds (Default: 300)') diff --git a/borg/helpers.py b/borg/helpers.py index 1706358df..9fc5c7ba4 100644 --- a/borg/helpers.py +++ b/borg/helpers.py @@ -428,7 +428,7 @@ def CompressionSpec(s): raise ValueError -def is_cachedir(path): +def dir_is_cachedir(path): """Determines whether the specified path is a cache directory (and therefore should potentially be excluded from the backup) according to the CACHEDIR.TAG protocol @@ -448,6 +448,20 @@ def is_cachedir(path): return False +def dir_is_tagged(path, exclude_caches, exclude_if_present): + """Determines whether the specified path is excluded by being a cache + directory or containing the user-specified tag file. + """ + if exclude_caches and dir_is_cachedir(path): + return True + if exclude_if_present is not None: + for tag in exclude_if_present: + tag_path = os.path.join(path, tag) + if os.path.isfile(tag_path): + return True + return False + + def format_time(t): """Format datetime suitable for fixed length list output """ diff --git a/borg/testsuite/archiver.py b/borg/testsuite/archiver.py index ca24807d5..02dc5d0a5 100644 --- a/borg/testsuite/archiver.py +++ b/borg/testsuite/archiver.py @@ -499,6 +499,17 @@ class ArchiverTestCase(ArchiverTestCaseBase): self.assert_equal(sorted(os.listdir('output/input')), ['cache2', 'file1']) self.assert_equal(sorted(os.listdir('output/input/cache2')), ['CACHEDIR.TAG']) + def test_exclude_tagged(self): + self.cmd('init', self.repository_location) + self.create_regular_file('file1', size=1024 * 80) + self.create_regular_file('tagged1/.NOBACKUP') + self.create_regular_file('tagged2/00-NOBACKUP') + self.create_regular_file('tagged3/.NOBACKUP/file2') + self.cmd('create', '--exclude-if-present', '.NOBACKUP', '--exclude-if-present', '00-NOBACKUP', self.repository_location + '::test', 'input') + with changedir('output'): + self.cmd('extract', self.repository_location + '::test') + self.assert_equal(sorted(os.listdir('output/input')), ['file1', 'tagged3']) + def test_path_normalization(self): self.cmd('init', self.repository_location) self.create_regular_file('dir1/dir2/file', size=1024 * 80)