Add archive comments

- Archives now have a new metadata field 'comment'
- 'info' command shows a comment if it's present
- 'create' command now has option '--comment' for adding comments to archives.
- A new command 'comment' is added for modifying the comments on existing
  archives.

Resolves #842.
This commit is contained in:
Lauri Niskanen 2016-04-06 14:04:18 +03:00
parent 7e3849367b
commit 327c7219df
6 changed files with 104 additions and 6 deletions

View file

@ -180,7 +180,7 @@ class Archive:
def load(self, id):
self.id = id
self.metadata = self._load_meta(self.id)
decode_dict(self.metadata, (b'name', b'hostname', b'username', b'time', b'time_end'))
decode_dict(self.metadata, (b'name', b'comment', b'hostname', b'username', b'time', b'time_end'))
self.metadata[b'cmdline'] = [arg.decode('utf-8', 'surrogateescape') for arg in self.metadata[b'cmdline']]
self.name = self.metadata[b'name']
@ -240,7 +240,7 @@ Number of files: {0.stats.nfiles}'''.format(
del self.manifest.archives[self.checkpoint_name]
self.cache.chunk_decref(self.id, self.stats)
def save(self, name=None, timestamp=None):
def save(self, name=None, comment=None, timestamp=None):
name = name or self.name
if name in self.manifest.archives:
raise self.AlreadyExists(name)
@ -256,6 +256,7 @@ Number of files: {0.stats.nfiles}'''.format(
metadata = StableDict({
'version': 1,
'name': name,
'comment': comment,
'items': self.items_buffer.chunks,
'cmdline': sys.argv,
'hostname': socket.gethostname(),
@ -884,7 +885,7 @@ class ArchiveChecker:
archive = StableDict(msgpack.unpackb(data))
if archive[b'version'] != 1:
raise Exception('Unknown archive metadata version')
decode_dict(archive, (b'name', b'hostname', b'username', b'time', b'time_end'))
decode_dict(archive, (b'name', b'comment', b'hostname', b'username', b'time', b'time_end'))
archive[b'cmdline'] = [arg.decode('utf-8', 'surrogateescape') for arg in archive[b'cmdline']]
items_buffer = ChunkBuffer(self.key)
items_buffer.write_chunk = add_callback

View file

@ -262,7 +262,7 @@ class Archiver:
args.keep_tag_files, skip_inodes, path, restrict_dev,
read_special=args.read_special, dry_run=dry_run)
if not dry_run:
archive.save(timestamp=args.timestamp)
archive.save(comment=args.comment, timestamp=args.timestamp)
if args.progress:
archive.stats.show_progress(final=True)
if args.stats:
@ -628,6 +628,16 @@ class Archiver:
cache.commit()
return self.exit_code
@with_repository(exclusive=True, cache=True)
@with_archive
def do_comment(self, args, repository, manifest, key, cache, archive):
"""Set the archive comment"""
archive.set_meta(b'comment', args.comment)
manifest.write()
repository.commit()
cache.commit()
return self.exit_code
@with_repository(exclusive=True, cache=True)
def do_delete(self, args, repository, manifest, key, cache):
"""Delete an existing repository or archive"""
@ -735,6 +745,7 @@ class Archiver:
stats = archive.calc_stats(cache)
print('Name:', archive.name)
print('Fingerprint: %s' % hexlify(archive.id).decode('ascii'))
print('Comment:', archive.metadata.get(b'comment', ''))
print('Hostname:', archive.metadata[b'hostname'])
print('Username:', archive.metadata[b'username'])
print('Time (start): %s' % format_time(to_localtime(archive.ts)))
@ -1179,6 +1190,8 @@ class Archiver:
formatter_class=argparse.RawDescriptionHelpFormatter,
help='create backup')
subparser.set_defaults(func=self.do_create)
subparser.add_argument('--comment', dest='comment', metavar='COMMENT', default='',
help='add a comment text to the archive')
subparser.add_argument('-s', '--stats', dest='stats',
action='store_true', default=False,
help='print statistics for the created archive')
@ -1356,6 +1369,21 @@ class Archiver:
type=archivename_validator(),
help='the new archive name to use')
comment_epilog = textwrap.dedent("""
This command sets the archive comment.
""")
subparser = subparsers.add_parser('comment', parents=[common_parser],
description=self.do_comment.__doc__,
epilog=comment_epilog,
formatter_class=argparse.RawDescriptionHelpFormatter,
help='set the archive comment')
subparser.set_defaults(func=self.do_comment)
subparser.add_argument('location', metavar='ARCHIVE',
type=location_validator(archive=True),
help='archive to modify')
subparser.add_argument('comment', metavar='COMMENT',
help='the new archive comment')
delete_epilog = textwrap.dedent("""
This command deletes an archive from the repository or the complete repository.
Disk space is reclaimed accordingly. If you delete the complete repository, the

View file

@ -754,6 +754,19 @@ class ArchiverTestCase(ArchiverTestCaseBase):
self.assert_in('test.3', manifest.archives)
self.assert_in('test.4', manifest.archives)
def test_comment(self):
self.create_regular_file('file1', size=1024 * 80)
self.cmd('init', self.repository_location)
self.cmd('create', self.repository_location + '::test1', 'input')
self.cmd('create', '--comment', 'this is the comment', self.repository_location + '::test2', 'input')
assert 'Comment: \n' in self.cmd('info', self.repository_location + '::test1')
assert 'Comment: this is the comment' in self.cmd('info', self.repository_location + '::test2')
self.cmd('comment', self.repository_location + '::test1', 'added comment')
self.cmd('comment', self.repository_location + '::test2', 'modified comment')
assert 'Comment: added comment' in self.cmd('info', self.repository_location + '::test1')
assert 'Comment: modified comment' in self.cmd('info', self.repository_location + '::test2')
def test_delete(self):
self.create_regular_file('file1', size=1024 * 80)
self.create_regular_file('dir2/file2', size=1024 * 80)

View file

@ -335,6 +335,26 @@ Examples
newname Mon, 2016-02-15 19:50:19
.. include:: usage/comment.rst.inc
Examples
~~~~~~~~
::
$ borg create --comment "This is a comment" /mnt/backup::archivename ~
$ borg info /mnt/backup::archivename
Name: archivename
Fingerprint: ...
Comment: This is a comment
...
$ borg comment /mnt/backup::archivename "This is a better comment"
$ borg info /mnt/backup::archivename
Name: archivename
Fingerprint: ...
Comment: This is a better comment
...
.. include:: usage/list.rst.inc
Examples
@ -825,4 +845,4 @@ for e.g. regular pruning.
Further protections can be implemented, but are outside of Borgs scope. For example,
file system snapshots or wrapping ``borg serve`` to set special permissions or ACLs on
new data files.
new data files.

View file

@ -0,0 +1,35 @@
.. _borg_comment:
borg comment
------------
::
usage: borg comment [-h] [-v] [--debug] [--lock-wait N] [--show-version]
[--show-rc] [--no-files-cache] [--umask M]
[--remote-path PATH]
ARCHIVE COMMENT
Set the archive comment
positional arguments:
ARCHIVE archive to rename
COMMENT the new archive comment
optional arguments:
-h, --help show this help message and exit
-v, --verbose, --info
enable informative (verbose) output, work on log level
INFO
--debug enable debug output, work on log level DEBUG
--lock-wait N wait for the lock, but max. N seconds (default: 1).
--show-version show/log the borg version
--show-rc show/log the return code (rc)
--no-files-cache do not load/update the file metadata cache used to
detect unchanged files
--umask M set umask to M (local and remote, default: 0077)
--remote-path PATH set remote path to executable (default: "borg")
Description
~~~~~~~~~~~
This command sets the archive comment.

View file

@ -6,7 +6,7 @@ borg create
usage: borg create [-h] [-v] [--debug] [--lock-wait N] [--show-version]
[--show-rc] [--no-files-cache] [--umask M]
[--remote-path PATH] [-s] [-p] [--list]
[--remote-path PATH] [--comment COMMENT] [-s] [-p] [--list]
[--filter STATUSCHARS] [-e PATTERN]
[--exclude-from EXCLUDEFILE] [--exclude-caches]
[--exclude-if-present FILENAME] [--keep-tag-files]
@ -36,6 +36,7 @@ borg create
detect unchanged files
--umask M set umask to M (local and remote, default: 0077)
--remote-path PATH set remote path to executable (default: "borg")
--comment COMMENT add a comment text to the archive
-s, --stats print statistics for the created archive
-p, --progress show progress display while creating the archive,
showing Original, Compressed and Deduplicated sizes,