mirror of
https://github.com/borgbackup/borg.git
synced 2026-05-14 18:33:24 -04:00
Merge pull request #2792 from ThomasWaldmann/comment-placeholder
support borg list repo --format {comment}, fixes #2081
This commit is contained in:
commit
e36be956f1
4 changed files with 111 additions and 25 deletions
|
|
@ -291,7 +291,9 @@ class Archive:
|
|||
self.hard_links = {}
|
||||
self.stats = Statistics(output_json=log_json)
|
||||
self.show_progress = progress
|
||||
self.name = name
|
||||
self.name = name # overwritten later with name from archive metadata
|
||||
self.name_in_manifest = name # can differ from .name later (if borg check fixed duplicate archive names)
|
||||
self.comment = None
|
||||
self.checkpoint_interval = checkpoint_interval
|
||||
self.numeric_owner = numeric_owner
|
||||
self.noatime = noatime
|
||||
|
|
@ -340,6 +342,7 @@ class Archive:
|
|||
self.metadata = self._load_meta(self.id)
|
||||
self.metadata.cmdline = [safe_decode(arg) for arg in self.metadata.cmdline]
|
||||
self.name = self.metadata.name
|
||||
self.comment = self.metadata.get('comment', '')
|
||||
|
||||
@property
|
||||
def ts(self):
|
||||
|
|
|
|||
|
|
@ -1322,7 +1322,7 @@ class Archiver:
|
|||
if args.json_lines:
|
||||
self.print_error('The --json-lines option is only valid for listing archive contents, not archives.')
|
||||
return self.exit_code
|
||||
return self._list_repository(args, manifest, write)
|
||||
return self._list_repository(args, repository, manifest, key, write)
|
||||
|
||||
def _list_archive(self, args, repository, manifest, key, write):
|
||||
matcher = self.build_matcher(args.patterns, args.paths)
|
||||
|
|
@ -1350,14 +1350,14 @@ class Archiver:
|
|||
|
||||
return self.exit_code
|
||||
|
||||
def _list_repository(self, args, manifest, write):
|
||||
def _list_repository(self, args, repository, manifest, key, write):
|
||||
if args.format is not None:
|
||||
format = args.format
|
||||
elif args.short:
|
||||
format = "{archive}{NL}"
|
||||
else:
|
||||
format = "{archive:<36} {time} [{id}]{NL}"
|
||||
formatter = ArchiveFormatter(format)
|
||||
formatter = ArchiveFormatter(format, repository, manifest, key, json=args.json)
|
||||
|
||||
output_data = []
|
||||
|
||||
|
|
|
|||
|
|
@ -1589,28 +1589,107 @@ class BaseFormatter:
|
|||
|
||||
|
||||
class ArchiveFormatter(BaseFormatter):
|
||||
KEY_DESCRIPTIONS = {
|
||||
'name': 'archive name interpreted as text (might be missing non-text characters, see barchive)',
|
||||
'archive': 'archive name interpreted as text (might be missing non-text characters, see barchive)',
|
||||
'barchive': 'verbatim archive name, can contain any character except NUL',
|
||||
'comment': 'archive comment interpreted as text (might be missing non-text characters, see bcomment)',
|
||||
'bcomment': 'verbatim archive comment, can contain any character except NUL',
|
||||
'time': 'time (start) of creation of the archive',
|
||||
# *start* is the key used by borg-info for this timestamp, this makes the formats more compatible
|
||||
'start': 'time (start) of creation of the archive',
|
||||
'end': 'time (end) of creation of the archive',
|
||||
'id': 'internal ID of the archive',
|
||||
}
|
||||
KEY_GROUPS = (
|
||||
('name', 'archive', 'barchive', 'comment', 'bcomment', 'id'),
|
||||
('time', 'start', 'end'),
|
||||
)
|
||||
|
||||
def __init__(self, format):
|
||||
self.format = partial_format(format, self.FIXED_KEYS)
|
||||
@classmethod
|
||||
def available_keys(cls):
|
||||
fake_archive_info = ArchiveInfo('archivename', b'\1'*32, datetime(1970, 1, 1, tzinfo=timezone.utc))
|
||||
formatter = cls('', None, None, None)
|
||||
keys = []
|
||||
keys.extend(formatter.call_keys.keys())
|
||||
keys.extend(formatter.get_item_data(fake_archive_info).keys())
|
||||
return keys
|
||||
|
||||
def get_item_data(self, archive):
|
||||
return {
|
||||
# *name* is the key used by borg-info for the archive name, this makes the formats more compatible
|
||||
'name': remove_surrogates(archive.name),
|
||||
'barchive': archive.name,
|
||||
'archive': remove_surrogates(archive.name),
|
||||
'id': bin_to_hex(archive.id),
|
||||
'time': format_time(to_localtime(archive.ts)),
|
||||
# *start* is the key used by borg-info for this timestamp, this makes the formats more compatible
|
||||
'start': format_time(to_localtime(archive.ts)),
|
||||
@classmethod
|
||||
def keys_help(cls):
|
||||
help = []
|
||||
keys = cls.available_keys()
|
||||
for key in cls.FIXED_KEYS:
|
||||
keys.remove(key)
|
||||
|
||||
for group in cls.KEY_GROUPS:
|
||||
for key in group:
|
||||
keys.remove(key)
|
||||
text = "- " + key
|
||||
if key in cls.KEY_DESCRIPTIONS:
|
||||
text += ": " + cls.KEY_DESCRIPTIONS[key]
|
||||
help.append(text)
|
||||
help.append("")
|
||||
assert not keys, str(keys)
|
||||
return "\n".join(help)
|
||||
|
||||
def __init__(self, format, repository, manifest, key, *, json=False):
|
||||
self.repository = repository
|
||||
self.manifest = manifest
|
||||
self.key = key
|
||||
self.name = None
|
||||
self.id = None
|
||||
self._archive = None
|
||||
self.json = json
|
||||
static_keys = {} # here could be stuff on repo level, above archive level
|
||||
static_keys.update(self.FIXED_KEYS)
|
||||
self.format = partial_format(format, static_keys)
|
||||
self.format_keys = {f[1] for f in Formatter().parse(format)}
|
||||
self.call_keys = {
|
||||
'comment': partial(self.get_comment, rs=True),
|
||||
'bcomment': partial(self.get_comment, rs=False),
|
||||
'end': self.get_ts_end,
|
||||
}
|
||||
self.used_call_keys = set(self.call_keys) & self.format_keys
|
||||
if self.json:
|
||||
self.item_data = {}
|
||||
self.format_item = self.format_item_json
|
||||
else:
|
||||
self.item_data = static_keys
|
||||
|
||||
@staticmethod
|
||||
def keys_help():
|
||||
return "- archive, name: archive name interpreted as text (might be missing non-text characters, see barchive)\n" \
|
||||
"- barchive: verbatim archive name, can contain any character except NUL\n" \
|
||||
"- time: time of creation of the archive\n" \
|
||||
"- id: internal ID of the archive"
|
||||
def format_item_json(self, item):
|
||||
return json.dumps(self.get_item_data(item)) + '\n'
|
||||
|
||||
def get_item_data(self, archive_info):
|
||||
self.name = archive_info.name
|
||||
self.id = archive_info.id
|
||||
item_data = {}
|
||||
item_data.update(self.item_data)
|
||||
item_data.update({
|
||||
'name': remove_surrogates(archive_info.name),
|
||||
'archive': remove_surrogates(archive_info.name),
|
||||
'barchive': archive_info.name,
|
||||
'id': bin_to_hex(archive_info.id),
|
||||
'time': format_time(to_localtime(archive_info.ts)),
|
||||
'start': format_time(to_localtime(archive_info.ts)),
|
||||
})
|
||||
for key in self.used_call_keys:
|
||||
item_data[key] = self.call_keys[key]()
|
||||
return item_data
|
||||
|
||||
@property
|
||||
def archive(self):
|
||||
"""lazy load / update loaded archive"""
|
||||
if self._archive is None or self._archive.id != self.id:
|
||||
from .archive import Archive
|
||||
self._archive = Archive(self.repository, self.key, self.manifest, self.name)
|
||||
return self._archive
|
||||
|
||||
def get_comment(self, rs):
|
||||
return remove_surrogates(self.archive.comment) if rs else self.archive.comment
|
||||
|
||||
def get_ts_end(self):
|
||||
return format_time(to_localtime(self.archive.ts_end))
|
||||
|
||||
|
||||
class ItemFormatter(BaseFormatter):
|
||||
|
|
@ -1716,9 +1795,10 @@ class ItemFormatter(BaseFormatter):
|
|||
self.used_call_keys = set(self.call_keys) & self.format_keys
|
||||
|
||||
def get_item_data(self, item):
|
||||
item_data = {}
|
||||
item_data.update(self.item_data)
|
||||
mode = stat.filemode(item.mode)
|
||||
item_type = mode[0]
|
||||
item_data = self.item_data
|
||||
|
||||
source = item.get('source', '')
|
||||
extra = ''
|
||||
|
|
|
|||
|
|
@ -1791,8 +1791,8 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|||
|
||||
def test_list_repository_format(self):
|
||||
self.cmd('init', '--encryption=repokey', self.repository_location)
|
||||
self.cmd('create', self.repository_location + '::test-1', src_dir)
|
||||
self.cmd('create', self.repository_location + '::test-2', src_dir)
|
||||
self.cmd('create', '--comment', 'comment 1', self.repository_location + '::test-1', src_dir)
|
||||
self.cmd('create', '--comment', 'comment 2', self.repository_location + '::test-2', src_dir)
|
||||
output_1 = self.cmd('list', self.repository_location)
|
||||
output_2 = self.cmd('list', '--format', '{archive:<36} {time} [{id}]{NL}', self.repository_location)
|
||||
self.assertEqual(output_1, output_2)
|
||||
|
|
@ -1800,6 +1800,9 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|||
self.assertEqual(output_1, 'test-1\ntest-2\n')
|
||||
output_1 = self.cmd('list', '--format', '{barchive}/', self.repository_location)
|
||||
self.assertEqual(output_1, 'test-1/test-2/')
|
||||
output_3 = self.cmd('list', '--format', '{name} {comment}{NL}', self.repository_location)
|
||||
self.assert_in('test-1 comment 1\n', output_3)
|
||||
self.assert_in('test-2 comment 2\n', output_3)
|
||||
|
||||
def test_list_hash(self):
|
||||
self.create_regular_file('empty_file', size=0)
|
||||
|
|
|
|||
Loading…
Reference in a new issue