Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/borg/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -1220,12 +1220,14 @@ def chunk_decref(id, stats, part=False):
logger.warning('borg check --repair is required to free all space.')

@staticmethod
def compare_archives_iter(print_warning, 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,
numeric_ids=False):
"""
Yields tuples with a path and an ItemDiff instance describing changes/indicating equality.

:param matcher: PatternMatcher class to restrict results to only matching paths.
:param can_compare_chunk_ids: Whether --chunker-params are the same for both archives.
:param numeric_ids: Whether to compare/report numeric uid/gid instead of user/group names.
"""

def hardlink_master_seen(item):
Expand All @@ -1249,6 +1251,7 @@ def compare_items(item1, item2):
return ItemDiff(item1, item2,
archive1.pipeline.fetch_many([c.id for c in item1.get('chunks', [])]),
archive2.pipeline.fetch_many([c.id for c in item2.get('chunks', [])]),
numeric_ids=numeric_ids,
can_compare_chunk_ids=can_compare_chunk_ids,
content_only=content_only)

Expand Down
3 changes: 2 additions & 1 deletion src/borg/archiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -1249,7 +1249,8 @@ def print_text_output(diff, path):

matcher = self.build_matcher(args.patterns, args.paths)

diffs = Archive.compare_archives_iter(self.print_warning, 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,
numeric_ids=args.numeric_ids)
diffs = ((path, diff) for path, diff in diffs if not diff.equal)

# Sorting support
Expand Down
45 changes: 45 additions & 0 deletions src/borg/testsuite/archiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -5752,6 +5752,51 @@ def test_sort_validation(self):
out = self.cmd('diff', self.repository_location + '::a', 'a', '--sort-by=invalid_field', fork=True, exit_code=2)
assert 'unsupported sort field' in out or 'invalid choice' in out or 'unsupported' in out

def _create_archive_with_items(self, name, items):
# Inject raw Items directly into an archive, bypassing "borg create", so the owner
# metadata (uid/gid/user/group) can be controlled exactly (without needing root).
repository = Repository(self.repository_path, exclusive=True)
with repository:
manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK)
with Cache(repository, key, manifest) as cache:
archive = Archive(repository, key, manifest, name, cache=cache, create=True)
for item in items:
archive.items_buffer.add(item)
archive.save()

def test_numeric_ids(self):
self.cmd('init', '--encryption=repokey', self.repository_location)

def item(path, uid, gid, user, group):
return Item(path=path, mode=stat.S_IFREG | 0o644, mtime=0, ctime=0,
uid=uid, gid=gid, user=user, group=group, chunks=[])

self._create_archive_with_items('a1', [
item('names_changed', 1000, 1000, 'alice', 'alice'),
item('ids_changed', 1000, 1000, 'alice', 'alice'),
])
self._create_archive_with_items('a2', [
item('names_changed', 1000, 1000, 'bob', 'bob'), # same uid/gid, different names
item('ids_changed', 2000, 2000, 'alice', 'alice'), # different uid/gid, same names
])

def changes_by_path(output):
return {j['path']: j['changes'] for j in (json.loads(line) for line in output.splitlines() if line)}

# without --numeric-ids, the user/group names are compared and reported
output = self.cmd('diff', self.repository_location + '::a1', 'a2', '--json-lines')
changes = changes_by_path(output)
assert {'type': 'owner', 'old_user': 'alice', 'old_group': 'alice',
'new_user': 'bob', 'new_group': 'bob'} in changes['names_changed']
assert 'ids_changed' not in changes # names unchanged, so the item counts as equal

# with --numeric-ids, the numeric uid/gid are compared and reported instead
output = self.cmd('diff', self.repository_location + '::a1', 'a2', '--json-lines', '--numeric-ids')
changes = changes_by_path(output)
assert {'type': 'owner', 'old_user': 1000, 'old_group': 1000,
'new_user': 2000, 'new_group': 2000} in changes['ids_changed']
assert 'names_changed' not in changes # uid/gid unchanged, so the item counts as equal

def test_time_diffs(self):
self.cmd('init', '--encryption=repokey', self.repository_location)
self.create_regular_file("test_file", size=10)
Expand Down
Loading