diff --git a/src/borg/archive.py b/src/borg/archive.py index f4acf8749..c27faf67c 100644 --- a/src/borg/archive.py +++ b/src/borg/archive.py @@ -422,7 +422,7 @@ Number of files: {0.stats.nfiles}'''.format( return stats def extract_item(self, item, restore_attrs=True, dry_run=False, stdout=False, sparse=False, - hardlink_masters=None, original_path=None, pi=None): + hardlink_masters=None, stripped_components=0, original_path=None, pi=None): """ Extract archive item. @@ -432,9 +432,11 @@ Number of files: {0.stats.nfiles}'''.format( :param stdout: write extracted data to stdout :param sparse: write sparse files (chunk-granularity, independent of the original being sparse) :param hardlink_masters: maps paths to (chunks, link_target) for extracting subtrees with hardlinks correctly + :param stripped_components: stripped leading path components to correct hard link extraction :param original_path: 'path' key as stored in archive :param pi: ProgressIndicatorPercent (or similar) for file extraction progress (in bytes) """ + hardlink_masters = hardlink_masters or {} has_damaged_chunks = 'chunks_healthy' in item if dry_run or stdout: if 'chunks' in item: @@ -473,11 +475,11 @@ Number of files: {0.stats.nfiles}'''.format( os.makedirs(os.path.dirname(path)) # Hard link? if 'source' in item: - source = os.path.join(dest, item.source) + source = os.path.join(dest, *item.source.split(os.sep)[stripped_components:]) with backup_io(): if os.path.exists(path): os.unlink(path) - if not hardlink_masters: + if item.source not in hardlink_masters: os.link(source, path) return item.chunks, link_target = hardlink_masters[item.source] diff --git a/src/borg/archiver.py b/src/borg/archiver.py index cf2f233f1..f5ebd7308 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -420,12 +420,14 @@ class Archiver: def build_filter(matcher, peek_and_store_hardlink_masters, strip_components): if strip_components: def item_filter(item): - peek_and_store_hardlink_masters(item) - return matcher.match(item.path) and os.sep.join(item.path.split(os.sep)[strip_components:]) + matched = matcher.match(item.path) and os.sep.join(item.path.split(os.sep)[strip_components:]) + peek_and_store_hardlink_masters(item, matched) + return matched else: def item_filter(item): - peek_and_store_hardlink_masters(item) - return matcher.match(item.path) + matched = matcher.match(item.path) + peek_and_store_hardlink_masters(item, matched) + return matched return item_filter @with_repository() @@ -450,8 +452,8 @@ class Archiver: partial_extract = not matcher.empty() or strip_components hardlink_masters = {} if partial_extract else None - def peek_and_store_hardlink_masters(item): - if (partial_extract and stat.S_ISREG(item.mode) and + def peek_and_store_hardlink_masters(item, matched): + if (partial_extract and not matched and stat.S_ISREG(item.mode) and item.get('hardlink_master', True) and 'source' not in item): hardlink_masters[item.get('path')] = (item.get('chunks'), None) @@ -486,7 +488,7 @@ class Archiver: archive.extract_item(item, restore_attrs=False) else: archive.extract_item(item, stdout=stdout, sparse=sparse, hardlink_masters=hardlink_masters, - original_path=orig_path, pi=pi) + stripped_components=strip_components, original_path=orig_path, pi=pi) except BackupOSError as e: self.print_warning('%s: %s', remove_surrogates(orig_path), e) diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index aa6614316..fd7eb5fc8 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -2204,8 +2204,8 @@ def test_compare_chunk_contents(): class TestBuildFilter: @staticmethod - def peek_and_store_hardlink_masters(item): - return False + def peek_and_store_hardlink_masters(item, matched): + pass def test_basic(self): matcher = PatternMatcher()