1
0
Fork 0
mirror of https://github.com/borgbackup/borg.git synced 2024-12-24 16:55:36 +00:00

Add '--sort' option for sorting diff command output

Previously, on 'borg diff', the output always had first the modifications, then
additions, and finally removals. Output may be easier to follow if the various
kinds of changes are interleaved. This commit is a simple solution that first
collects the output lines and sorts them by file path before printing. This new
behavior is optional and disabled by default. It can be enabled with '--sort'
command line option.

This option will be especially useful after the planned multi-threading changes
arrive. Multi-threading may shuffle the archive order of files making diff
output hard to follow without sorting.

Resolves #797.
This commit is contained in:
Lauri Niskanen 2016-03-31 10:33:03 +03:00
parent 2a026bd842
commit 9d1a30c08b
3 changed files with 56 additions and 8 deletions

View file

@ -514,7 +514,7 @@ def compare_mode(item1, item2):
if item1[b'mode'] != item2[b'mode']:
return '[{} -> {}]'.format(get_mode(item1), get_mode(item2))
def compare_items(path, item1, item2, hardlink_masters, deleted=False):
def compare_items(output, path, item1, item2, hardlink_masters, deleted=False):
"""
Compare two items with identical paths.
:param deleted: Whether one of the items has been deleted
@ -545,43 +545,56 @@ def compare_items(path, item1, item2, hardlink_masters, deleted=False):
changes = [x for x in changes if x]
if changes:
print("{:<19} {}".format(' '.join(changes), remove_surrogates(path)))
output_line = (remove_surrogates(path), ' '.join(changes))
if args.sort:
output.append(output_line)
else:
print_output(output_line)
def print_output(line):
print("{:<19} {}".format(line[1], line[0]))
def compare_archives(archive1, archive2, matcher):
orphans_archive1 = collections.OrderedDict()
orphans_archive2 = collections.OrderedDict()
hardlink_masters = {}
output = []
for item1, item2 in zip_longest(
archive1.iter_items(lambda item: matcher.match(item[b'path'])),
archive2.iter_items(lambda item: matcher.match(item[b'path'])),
):
if item1 and item2 and item1[b'path'] == item2[b'path']:
compare_items(item1[b'path'], item1, item2, hardlink_masters)
compare_items(output, item1[b'path'], item1, item2, hardlink_masters)
continue
if item1:
matching_orphan = orphans_archive2.pop(item1[b'path'], None)
if matching_orphan:
compare_items(item1[b'path'], item1, matching_orphan, hardlink_masters)
compare_items(output, item1[b'path'], item1, matching_orphan, hardlink_masters)
else:
orphans_archive1[item1[b'path']] = item1
if item2:
matching_orphan = orphans_archive1.pop(item2[b'path'], None)
if matching_orphan:
compare_items(item2[b'path'], matching_orphan, item2, hardlink_masters)
compare_items(output, item2[b'path'], matching_orphan, item2, hardlink_masters)
else:
orphans_archive2[item2[b'path']] = item2
# At this point orphans_* contain items that had no matching partner in the other archive
for added in orphans_archive2.values():
compare_items(added[b'path'], {
compare_items(output, added[b'path'], {
b'deleted': True,
b'chunks': [],
}, added, hardlink_masters, deleted=True)
for deleted in orphans_archive1.values():
compare_items(deleted[b'path'], deleted, {
compare_items(output, deleted[b'path'], deleted, {
b'deleted': True,
b'chunks': [],
}, hardlink_masters, deleted=True)
for line in sorted(output):
print_output(line)
archive1 = archive
archive2 = Archive(repository, key, manifest, args.archive2)
@ -1308,6 +1321,9 @@ def build_parser(self, args=None, prog=None):
subparser.add_argument('--same-chunker-params', dest='same_chunker_params',
action='store_true', default=False,
help='Override check of chunker parameters.')
subparser.add_argument('--sort', dest='sort',
action='store_true', default=False,
help='Sort the output lines by file path.')
subparser.add_argument('location', metavar='ARCHIVE1',
type=location_validator(archive=True),
help='archive')

View file

@ -1351,6 +1351,37 @@ def do_asserts(output, archive):
# We expect exit_code=1 due to the chunker params warning
do_asserts(self.cmd('diff', self.repository_location + '::test0', 'test1b', exit_code=1), '1b')
def test_sort_option(self):
self.cmd('init', self.repository_location)
self.create_regular_file('a_file_removed', size=8)
self.create_regular_file('f_file_removed', size=16)
self.create_regular_file('c_file_changed', size=32)
self.create_regular_file('e_file_changed', size=64)
self.cmd('create', self.repository_location + '::test0', 'input')
os.unlink('input/a_file_removed')
os.unlink('input/f_file_removed')
os.unlink('input/c_file_changed')
os.unlink('input/e_file_changed')
self.create_regular_file('c_file_changed', size=512)
self.create_regular_file('e_file_changed', size=1024)
self.create_regular_file('b_file_added', size=128)
self.create_regular_file('d_file_added', size=256)
self.cmd('create', self.repository_location + '::test1', 'input')
output = self.cmd('diff', '--sort', self.repository_location + '::test0', 'test1')
expected = [
'a_file_removed',
'b_file_added',
'c_file_changed',
'd_file_added',
'e_file_changed',
'f_file_removed',
]
assert all(x in line for x, line in zip(expected, output.splitlines()))
def test_get_args():
archiver = Archiver()

View file

@ -8,7 +8,7 @@ borg diff
[--show-rc] [--no-files-cache] [--umask M]
[--remote-path PATH] [-e PATTERN]
[--exclude-from EXCLUDEFILE] [--numeric-owner]
[--same-chunker-params]
[--same-chunker-params] [--sort]
ARCHIVE1 ARCHIVE2 [PATH [PATH ...]]
Diff contents of two archives
@ -39,6 +39,7 @@ borg diff
--numeric-owner only consider numeric user and group identifiers
--same-chunker-params
Override check of chunker parameters.
--sort Sort the output lines by file path.
Description
~~~~~~~~~~~