From 2720331213aa2ce301c46ef1c05ef6e4583fbba7 Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Mon, 26 Aug 2024 14:58:49 -0400 Subject: [PATCH] Do not assert on diff if hard link sources are not found due to exclusions. --- src/borg/archive.py | 14 ++++++++++---- src/borg/archiver.py | 2 +- src/borg/testsuite/archiver.py | 22 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/borg/archive.py b/src/borg/archive.py index 91030dcb1..562528203 100644 --- a/src/borg/archive.py +++ b/src/borg/archive.py @@ -1038,7 +1038,7 @@ Utilization of max. archive size: {csize_max:.0%} logger.warning('borg check --repair is required to free all space.') @staticmethod - def compare_archives_iter(archive1, archive2, matcher=None, can_compare_chunk_ids=False, content_only=False): + def compare_archives_iter(print_warning, archive1, archive2, matcher=None, can_compare_chunk_ids=False, content_only=False): """ Yields tuples with a path and an ItemDiff instance describing changes/indicating equality. @@ -1117,10 +1117,16 @@ Utilization of max. archive size: {csize_max:.0%} update_hardlink_masters(deleted, deleted_item) yield (path, compare_items(deleted, deleted_item)) for item1, item2 in deferred: - assert hardlink_master_seen(item1) - assert hardlink_master_seen(item2) assert item1.path == item2.path, "Deferred items have different paths" - yield (item1.path, compare_items(item1, item2)) + hl_found1 = hardlink_master_seen(item1) + hl_found2 = hardlink_master_seen(item2) + if hl_found1 and hl_found2: + yield (item1.path, compare_items(item1, item2)) + else: + if not hl_found1: + print_warning(f"cannot find hardlink source for {item1.path} ({item1.source}), skipping compare.") + if not hl_found2: + print_warning(f"cannot find hardlink source for {item2.path} ({item2.source}), skipping compare.") class MetadataCollector: diff --git a/src/borg/archiver.py b/src/borg/archiver.py index 4fe2ff540..bcb03aa33 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -1162,7 +1162,7 @@ class Archiver: matcher = self.build_matcher(args.patterns, args.paths) - diffs = Archive.compare_archives_iter(archive1, archive2, matcher, can_compare_chunk_ids=can_compare_chunk_ids, content_only=args.content_only) + diffs = Archive.compare_archives_iter(self.print_warning, archive1, archive2, matcher, can_compare_chunk_ids=can_compare_chunk_ids, content_only=args.content_only) # Conversion to string and filtering for diff.equal to save memory if sorting diffs = ((path, diff.changes()) for path, diff in diffs if not diff.equal) diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index 7aa8a17dc..bc1fe691b 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -4536,6 +4536,8 @@ class ArchiverCorruptionTestCase(ArchiverTestCaseBase): class DiffArchiverTestCase(ArchiverTestCaseBase): + requires_hardlinks = pytest.mark.skipif(not are_hardlinks_supported(), reason='hardlinks not supported') + def test_basic_functionality(self): # Setup files for the first snapshot self.create_regular_file('empty', size=0) @@ -4838,6 +4840,26 @@ class DiffArchiverTestCase(ArchiverTestCaseBase): self.assert_not_in("ctime", output) + @requires_hardlinks + def test_multiple_link_exclusion(self): + path_a = os.path.join(self.input_path, 'a') + path_b = os.path.join(self.input_path, 'b') + os.mkdir(path_a) + os.mkdir(path_b) + hl_a = os.path.join(path_a, 'hardlink') + hl_b = os.path.join(path_b, 'hardlink') + self.create_regular_file(hl_a, contents=b'123456') + os.link(hl_a, hl_b) + self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd('create', self.repository_location + '::test0', 'input') + os.unlink(hl_a) # Don't duplicate warning message- one is enough. + self.cmd('create', self.repository_location + '::test1', 'input') + + output = self.cmd('diff', '--pattern=+ fm:input/b', '--pattern=! **/', self.repository_location + '::test0', 'test1', exit_code=EXIT_WARNING) + lines = output.splitlines() + self.assert_line_exists(lines, 'cannot find hardlink source for.*skipping compare.') + + def test_get_args(): archiver = Archiver() # everything normal: