diff --git a/borg/archive.py b/borg/archive.py index 0d0effce5..7fc2ba330 100644 --- a/borg/archive.py +++ b/borg/archive.py @@ -978,20 +978,20 @@ class ArchiveRecreater: self.interrupt = False self.errors = False - def recreate(self, archive_name): + def recreate(self, archive_name, comment=None): assert not self.is_temporary_archive(archive_name) archive = self.open_archive(archive_name) target, resume_from = self.create_target_or_resume(archive) if self.exclude_if_present or self.exclude_caches: self.matcher_add_tagged_dirs(archive) - if self.matcher.empty() and not self.recompress and not target.recreate_rechunkify: + if self.matcher.empty() and not self.recompress and not target.recreate_rechunkify and comment is None: logger.info("Skipping archive %s, nothing to do", archive_name) return True try: self.process_items(archive, target, resume_from) except self.Interrupted as e: return self.save(archive, target, completed=False, metadata=e.metadata) - return self.save(archive, target) + return self.save(archive, target, comment) def process_items(self, archive, target, resume_from=None): matcher = self.matcher @@ -1108,13 +1108,15 @@ class ArchiveRecreater: logger.debug('Copied %d chunks from a partially processed item', len(partial_chunks)) return partial_chunks - def save(self, archive, target, completed=True, metadata=None): + def save(self, archive, target, comment=None, completed=True, metadata=None): """Save target archive. If completed, replace source. If not, save temporary with additional 'metadata' dict.""" if self.dry_run: return completed if completed: timestamp = archive.ts.replace(tzinfo=None) - target.save(timestamp=timestamp, additional_metadata={ + if comment is None: + comment = archive.metadata.get(b'comment', '') + target.save(timestamp=timestamp, comment=comment, additional_metadata={ 'cmdline': archive.metadata[b'cmdline'], 'recreate_cmdline': sys.argv, }) diff --git a/borg/archiver.py b/borg/archiver.py index 4a7dab7f3..9e141c706 100644 --- a/borg/archiver.py +++ b/borg/archiver.py @@ -626,16 +626,6 @@ 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""" @@ -851,14 +841,14 @@ class Archiver: if recreater.is_temporary_archive(name): self.print_error('Refusing to work on temporary archive of prior recreate: %s', name) return self.exit_code - recreater.recreate(name) + recreater.recreate(name, args.comment) else: for archive in manifest.list_archive_infos(sort_by='ts'): name = archive.name if recreater.is_temporary_archive(name): continue print('Processing', name) - if not recreater.recreate(name): + if not recreater.recreate(name, args.comment): break manifest.write() repository.commit() @@ -1434,23 +1424,6 @@ class Archiver: type=archivename_validator(), help='the new archive name to use') - comment_epilog = textwrap.dedent(""" - This command sets the archive comment. - - This results in a different archive ID. - """) - subparser = subparsers.add_parser('comment', parents=[common_parser], add_help=False, - 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 @@ -1746,22 +1719,36 @@ class Archiver: subparser.add_argument('-s', '--stats', dest='stats', action='store_true', default=False, help='print statistics at end') - subparser.add_argument('-e', '--exclude', dest='excludes', - type=parse_pattern, action='append', - metavar="PATTERN", help='exclude paths matching PATTERN') - subparser.add_argument('--exclude-from', dest='exclude_files', - type=argparse.FileType('r'), action='append', - metavar='EXCLUDEFILE', help='read exclude patterns from EXCLUDEFILE, one per line') - subparser.add_argument('--exclude-caches', dest='exclude_caches', - action='store_true', default=False, - help='exclude directories that contain a CACHEDIR.TAG file (' - 'http://www.brynosaurus.com/cachedir/spec.html)') - subparser.add_argument('--exclude-if-present', dest='exclude_if_present', - metavar='FILENAME', action='append', type=str, - help='exclude directories that contain the specified file') - subparser.add_argument('--keep-tag-files', dest='keep_tag_files', - action='store_true', default=False, - help='keep tag files of excluded caches/directories') + + exclude_group = subparser.add_argument_group('Exclusion options') + exclude_group.add_argument('-e', '--exclude', dest='excludes', + type=parse_pattern, action='append', + metavar="PATTERN", help='exclude paths matching PATTERN') + exclude_group.add_argument('--exclude-from', dest='exclude_files', + type=argparse.FileType('r'), action='append', + metavar='EXCLUDEFILE', help='read exclude patterns from EXCLUDEFILE, one per line') + exclude_group.add_argument('--exclude-caches', dest='exclude_caches', + action='store_true', default=False, + help='exclude directories that contain a CACHEDIR.TAG file (' + 'http://www.brynosaurus.com/cachedir/spec.html)') + exclude_group.add_argument('--exclude-if-present', dest='exclude_if_present', + metavar='FILENAME', action='append', type=str, + help='exclude directories that contain the specified file') + exclude_group.add_argument('--keep-tag-files', dest='keep_tag_files', + action='store_true', default=False, + help='keep tag files of excluded caches/directories') + + archive_group = subparser.add_argument_group('Archive options') + archive_group.add_argument('--comment', dest='comment', metavar='COMMENT', default=None, + help='add a comment text to the archive') + archive_group.add_argument('--timestamp', dest='timestamp', + type=timestamp, default=None, + metavar='yyyy-mm-ddThh:mm:ss', + help='manually specify the archive creation date/time (UTC). ' + 'alternatively, give a reference file/directory.') + archive_group.add_argument('-c', '--checkpoint-interval', dest='checkpoint_interval', + type=int, default=300, metavar='SECONDS', + help='write checkpoint every SECONDS seconds (Default: 300)') archive_group.add_argument('-C', '--compression', dest='compression', type=CompressionSpec, default=None, metavar='COMPRESSION', help='select compression algorithm (and level):\n' @@ -1771,10 +1758,11 @@ class Archiver: 'zlib,0 .. zlib,9 == zlib (with level 0..9),\n' 'lzma == lzma (default level 6),\n' 'lzma,0 .. lzma,9 == lzma (with level 0..9).') - subparser.add_argument('--chunker-params', dest='chunker_params', - type=ChunkerParams, default=None, - metavar='CHUNK_MIN_EXP,CHUNK_MAX_EXP,HASH_MASK_BITS,HASH_WINDOW_SIZE', - help='specify the chunker parameters (or "default").') + archive_group.add_argument('--chunker-params', dest='chunker_params', + type=ChunkerParams, default=None, + metavar='CHUNK_MIN_EXP,CHUNK_MAX_EXP,HASH_MASK_BITS,HASH_WINDOW_SIZE', + help='specify the chunker parameters (or "default").') + subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='', type=location_validator(), help='repository/archive to recreate') diff --git a/borg/testsuite/archiver.py b/borg/testsuite/archiver.py index 080848d52..94dacbb6f 100644 --- a/borg/testsuite/archiver.py +++ b/borg/testsuite/archiver.py @@ -800,13 +800,19 @@ class ArchiverTestCase(ArchiverTestCaseBase): 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') + self.cmd('create', '--comment', '"deleted" comment', self.repository_location + '::test3', 'input') + self.cmd('create', '--comment', 'preserved comment', self.repository_location + '::test4', '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') + self.cmd('recreate', self.repository_location + '::test1', '--comment', 'added comment') + self.cmd('recreate', self.repository_location + '::test2', '--comment', 'modified comment') + self.cmd('recreate', self.repository_location + '::test3', '--comment', '') + self.cmd('recreate', self.repository_location + '::test4', '12345') assert 'Comment: added comment' in self.cmd('info', self.repository_location + '::test1') assert 'Comment: modified comment' in self.cmd('info', self.repository_location + '::test2') + assert 'Comment: \n' in self.cmd('info', self.repository_location + '::test3') + assert 'Comment: preserved comment' in self.cmd('info', self.repository_location + '::test4') def test_delete(self): self.create_regular_file('file1', size=1024 * 80) diff --git a/docs/usage.rst b/docs/usage.rst index 32cb47959..61ce42277 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -356,26 +356,6 @@ 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 @@ -648,6 +628,21 @@ Examples $ borg recreate /mnt/backup -e /home/icke/Pictures/drunk_photos + # Change archive comment + $ borg create --comment "This is a comment" /mnt/backup::archivename ~ + $ borg info /mnt/backup::archivename + Name: archivename + Fingerprint: ... + Comment: This is a comment + ... + $ borg recreate --comment "This is a better comment" /mnt/backup::archivename + $ borg info /mnt/backup::archivename + Name: archivename + Fingerprint: ... + Comment: This is a better comment + ... + + Miscellaneous Help ------------------ diff --git a/docs/usage/comment.rst.inc b/docs/usage/comment.rst.inc deleted file mode 100644 index 56709a537..000000000 --- a/docs/usage/comment.rst.inc +++ /dev/null @@ -1,23 +0,0 @@ -.. _borg_comment: - -borg comment ------------- -:: - - borg comment ARCHIVE COMMENT - -positional arguments - ARCHIVE - archive to modify - COMMENT - the new archive comment - -`Common options`_ - | - -Description -~~~~~~~~~~~ - -This command sets the archive comment. - -This results in a different archive ID. diff --git a/docs/usage/recreate.rst.inc b/docs/usage/recreate.rst.inc index f064f6418..8df27c7b5 100644 --- a/docs/usage/recreate.rst.inc +++ b/docs/usage/recreate.rst.inc @@ -4,58 +4,59 @@ borg recreate ------------- :: - usage: borg recreate [-h] [-v] [--debug] [--lock-wait N] [--show-version] - [--show-rc] [--no-files-cache] [--umask M] - [--remote-path PATH] [--list] [--filter STATUSCHARS] [-p] - [-n] [-s] [-e PATTERN] [--exclude-from EXCLUDEFILE] - [--exclude-caches] [--exclude-if-present FILENAME] - [--keep-tag-files] [-C COMPRESSION] - [--chunker-params CHUNK_MIN_EXP,CHUNK_MAX_EXP,HASH_MASK_BITS,HASH_WINDOW_SIZE] - [REPOSITORY_OR_ARCHIVE] [PATH [PATH ...]] - - Re-create archives - - positional arguments: - REPOSITORY_OR_ARCHIVE - repository/archive to recreate - PATH paths to recreate; patterns are supported - - 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") - --list output verbose list of items (files, dirs, ...) - --filter STATUSCHARS only display items with the given status characters - -p, --progress show progress display while rewriting archives - -n, --dry-run do not change anything - -s, --stats print statistics at end - -e PATTERN, --exclude PATTERN - exclude paths matching PATTERN - --exclude-from EXCLUDEFILE - read exclude patterns from EXCLUDEFILE, one per line - --exclude-caches exclude directories that contain a CACHEDIR.TAG file - (http://www.brynosaurus.com/cachedir/spec.html) - --exclude-if-present FILENAME - exclude directories that contain the specified file - --keep-tag-files keep tag files of excluded caches/directories - -C COMPRESSION, --compression COMPRESSION - select compression algorithm (and level): none == no - compression (default), lz4 == lz4, zlib == zlib - (default level 6), zlib,0 .. zlib,9 == zlib (with - level 0..9), lzma == lzma (default level 6), lzma,0 .. - lzma,9 == lzma (with level 0..9). - --chunker-params CHUNK_MIN_EXP,CHUNK_MAX_EXP,HASH_MASK_BITS,HASH_WINDOW_SIZE - specify the chunker parameters (or "default"). - + borg recreate REPOSITORY_OR_ARCHIVE PATH + +positional arguments + REPOSITORY_OR_ARCHIVE + repository/archive to recreate + PATH + paths to recreate; patterns are supported + +optional arguments + ``--list`` + | output verbose list of items (files, dirs, ...) + ``--filter STATUSCHARS`` + | only display items with the given status characters + ``-p``, ``--progress`` + | show progress display while rewriting archives + ``-n``, ``--dry-run`` + | do not change anything + ``-s``, ``--stats`` + | print statistics at end + +`Common options`_ + | + +Exclusion options + ``-e PATTERN``, ``--exclude PATTERN`` + | exclude paths matching PATTERN + ``--exclude-from EXCLUDEFILE`` + | read exclude patterns from EXCLUDEFILE, one per line + ``--exclude-caches`` + | exclude directories that contain a CACHEDIR.TAG file (http://www.brynosaurus.com/cachedir/spec.html) + ``--exclude-if-present FILENAME`` + | exclude directories that contain the specified file + ``--keep-tag-files`` + | keep tag files of excluded caches/directories + +Archive options + ``--comment COMMENT`` + | add a comment text to the archive + ``--timestamp yyyy-mm-ddThh:mm:ss`` + | manually specify the archive creation date/time (UTC). alternatively, give a reference file/directory. + ``-c SECONDS``, ``--checkpoint-interval SECONDS`` + | write checkpoint every SECONDS seconds (Default: 300) + ``-C COMPRESSION``, ``--compression COMPRESSION`` + | select compression algorithm (and level): + | none == no compression (default), + | lz4 == lz4, + | zlib == zlib (default level 6), + | zlib,0 .. zlib,9 == zlib (with level 0..9), + | lzma == lzma (default level 6), + | lzma,0 .. lzma,9 == lzma (with level 0..9). + ``--chunker-params CHUNK_MIN_EXP,CHUNK_MAX_EXP,HASH_MASK_BITS,HASH_WINDOW_SIZE`` + | specify the chunker parameters (or "default"). + Description ~~~~~~~~~~~