From 193fb1fcd527d545ab5af963cbcd65ee480f6f27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Borgstr=C3=B6m?= Date: Sat, 2 Aug 2014 22:15:21 +0200 Subject: [PATCH] Added support for stripping leading path segments closes #95 --- CHANGES | 2 ++ attic/archiver.py | 16 +++++++++++++--- attic/testsuite/__init__.py | 7 +++++++ attic/testsuite/archiver.py | 14 ++++++++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 6fce89758..a5a5fd24c 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,8 @@ Version 0.14 ------------ (feature release, released on X) +- Added support for stripping leading path segments (#95) + "attic extract --strip-segments X" - Add workaround for old Linux systems without acl_extended_file_no_follow (#96) - Add MacPorts' path to the default openssl search path (#101) - HashIndex improvements, eliminates unnecessary IO on low memory systems. diff --git a/attic/archiver.py b/attic/archiver.py index c17b487eb..6d9a37463 100644 --- a/attic/archiver.py +++ b/attic/archiver.py @@ -191,14 +191,21 @@ Type "Yes I am sure" if you understand this and want to continue.\n""") archive = Archive(repository, key, manifest, args.archive.archive, numeric_owner=args.numeric_owner) patterns = adjust_patterns(args.paths, args.excludes) + dry_run = args.dry_run + strip_components = args.strip_components dirs = [] for item in archive.iter_items(lambda item: not exclude_path(item[b'path'], patterns), preload=True): + orig_path = item[b'path'] + if strip_components: + item[b'path'] = os.sep.join(orig_path.split(os.sep)[strip_components:]) + if not item[b'path']: + continue if not args.dry_run: while dirs and not item[b'path'].startswith(dirs[-1][b'path']): archive.extract_item(dirs.pop(-1)) - self.print_verbose(remove_surrogates(item[b'path'])) + self.print_verbose(remove_surrogates(orig_path)) try: - if args.dry_run: + if dry_run: archive.extract_item(item, dry_run=True) else: if stat.S_ISDIR(item[b'mode']): @@ -207,7 +214,7 @@ Type "Yes I am sure" if you understand this and want to continue.\n""") else: archive.extract_item(item) except IOError as e: - self.print_error('%s: %s', remove_surrogates(item[b'path']), e) + self.print_error('%s: %s', remove_surrogates(orig_path), e) if not args.dry_run: while dirs: @@ -572,6 +579,9 @@ Type "Yes I am sure" if you understand this and want to continue.\n""") subparser.add_argument('--numeric-owner', dest='numeric_owner', action='store_true', default=False, help='only obey numeric user and group identifiers') + subparser.add_argument('--strip-components', dest='strip_components', + type=int, default=0, metavar='NUMBER', + help='Remove the specified number of leading path elements. Pathnames with fewer elements will be silently skipped.') subparser.add_argument('archive', metavar='ARCHIVE', type=location_validator(archive=True), help='archive to extract') diff --git a/attic/testsuite/__init__.py b/attic/testsuite/__init__.py index bdecaa989..684eeb91b 100644 --- a/attic/testsuite/__init__.py +++ b/attic/testsuite/__init__.py @@ -1,3 +1,4 @@ +from contextlib import contextmanager import filecmp import os import posix @@ -41,6 +42,12 @@ class AtticTestCase(unittest.TestCase): assert_raises = unittest.TestCase.assertRaises assert_true = unittest.TestCase.assertTrue + @contextmanager + def assert_creates_file(self, path): + self.assert_true(not os.path.exists(path), '{} should not exist'.format(path)) + yield + self.assert_true(os.path.exists(path), '{} should exist'.format(path)) + def assert_dirs_equal(self, dir1, dir2): diff = filecmp.dircmp(dir1, dir2) self._assert_dirs_equal_cmp(diff) diff --git a/attic/testsuite/archiver.py b/attic/testsuite/archiver.py index 3eb77173a..5714b2bbe 100644 --- a/attic/testsuite/archiver.py +++ b/attic/testsuite/archiver.py @@ -165,6 +165,20 @@ class ArchiverTestCase(ArchiverTestCaseBase): # end the same way as info_output assert info_output2.endswith(info_output) + def test_strip_components(self): + self.attic('init', self.repository_location) + self.create_regular_file('dir/file') + self.attic('create', self.repository_location + '::test', 'input') + with changedir('output'): + self.attic('extract', self.repository_location + '::test', '--strip-components', '3') + self.assert_true(not os.path.exists('file')) + with self.assert_creates_file('file'): + self.attic('extract', self.repository_location + '::test', '--strip-components', '2') + with self.assert_creates_file('dir/file'): + self.attic('extract', self.repository_location + '::test', '--strip-components', '1') + with self.assert_creates_file('input/dir/file'): + self.attic('extract', self.repository_location + '::test', '--strip-components', '0') + def test_extract_include_exclude(self): self.attic('init', self.repository_location) self.create_regular_file('file1', size=1024 * 80)