diff --git a/src/borg/archiver.py b/src/borg/archiver.py index 9b73a72ea..643df3627 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -1115,6 +1115,7 @@ class Archiver: archives.insert(0, args.location.archive) archive_names = tuple(archives) else: + args.consider_checkpoints = True archive_names = tuple(x.name for x in manifest.archives.list_considering(args)) if not archive_names: return self.exit_code @@ -1321,6 +1322,7 @@ class Archiver: if args.location.archive: archive_names = (args.location.archive,) else: + args.consider_checkpoints = True archive_names = tuple(x.name for x in manifest.archives.list_considering(args)) if not archive_names: return self.exit_code @@ -1409,6 +1411,7 @@ class Archiver: args.glob_archives = args.prefix + '*' checkpoint_re = r'\.checkpoint(\.\d+)?' archives_checkpoints = manifest.archives.list(glob=args.glob_archives, + consider_checkpoints=True, match_end=r'(%s)?\Z' % checkpoint_re, sort_by=['ts'], reverse=True) is_checkpoint = re.compile(r'(%s)\Z' % checkpoint_re).search @@ -2627,6 +2630,8 @@ class Archiver: parser.set_defaults(func=self.do_mount) parser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', type=location_validator(), help='repository or archive to mount') + parser.add_argument('--consider-checkpoints', action='store_true', dest='consider_checkpoints', + help='Show checkpoint archives in the repository contents list (default: hidden).') parser.add_argument('mountpoint', metavar='MOUNTPOINT', type=str, help='where to mount filesystem') parser.add_argument('-f', '--foreground', dest='foreground', @@ -3834,6 +3839,8 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='list archive or repository contents') subparser.set_defaults(func=self.do_list) + subparser.add_argument('--consider-checkpoints', action='store_true', dest='consider_checkpoints', + help='Show checkpoint archives in the repository contents list (default: hidden).') subparser.add_argument('--short', dest='short', action='store_true', help='only print file/directory names, nothing else') subparser.add_argument('--format', '--list-format', metavar='FORMAT', dest='format', diff --git a/src/borg/helpers/manifest.py b/src/borg/helpers/manifest.py index 6bbb9c27d..a8061aaaa 100644 --- a/src/borg/helpers/manifest.py +++ b/src/borg/helpers/manifest.py @@ -74,7 +74,7 @@ class Archives(abc.MutableMapping): name = safe_encode(name) del self._archives[name] - def list(self, *, glob=None, match_end=r'\Z', sort_by=(), first=None, last=None, reverse=False): + def list(self, *, glob=None, match_end=r'\Z', sort_by=(), consider_checkpoints=False, first=None, last=None, reverse=False): """ Return list of ArchiveInfo instances according to the parameters. @@ -87,6 +87,8 @@ class Archives(abc.MutableMapping): raise TypeError('sort_by must be a sequence of str') regex = re.compile(shellpattern.translate(glob or '*', match_end=match_end)) archives = [x for x in self.values() if regex.match(x.name) is not None] + if not consider_checkpoints: + archives = [x for x in archives if '.checkpoint' not in x.name] for sortkey in reversed(sort_by): archives.sort(key=attrgetter(sortkey)) if first: @@ -99,13 +101,13 @@ class Archives(abc.MutableMapping): def list_considering(self, args): """ - get a list of archives, considering --first/last/prefix/glob-archives/sort cmdline args + get a list of archives, considering --first/last/prefix/glob-archives/sort/consider-checkpoints cmdline args """ if args.location.archive: - raise Error('The options --first, --last, --prefix and --glob-archives can only be used on repository targets.') + raise Error('The options --first, --last, --prefix, and --glob-archives, and --consider-checkpoints can only be used on repository targets.') if args.prefix is not None: args.glob_archives = args.prefix + '*' - return self.list(sort_by=args.sort_by.split(','), glob=args.glob_archives, first=args.first, last=args.last) + return self.list(sort_by=args.sort_by.split(','), consider_checkpoints=args.consider_checkpoints, glob=args.glob_archives, first=args.first, last=args.last) def set_raw_dict(self, d): """set the dict we get from the msgpack unpacker""" diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index 1e906bf09..53eddccc2 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -1854,14 +1854,14 @@ class ArchiverTestCase(ArchiverTestCaseBase): assert re.search(r'Keeping archive \(rule: daily #1\):\s+test2', output) # must keep the latest checkpoint archive: assert re.search(r'Keeping checkpoint archive:\s+test4.checkpoint', output) - output = self.cmd('list', self.repository_location) + output = self.cmd('list', '--consider-checkpoints', self.repository_location) self.assert_in('test1', output) self.assert_in('test2', output) self.assert_in('test3.checkpoint', output) self.assert_in('test3.checkpoint.1', output) self.assert_in('test4.checkpoint', output) self.cmd('prune', self.repository_location, '--keep-daily=2') - output = self.cmd('list', self.repository_location) + output = self.cmd('list', '--consider-checkpoints', self.repository_location) self.assert_not_in('test1', output) # the latest non-checkpoint archive must be still there: self.assert_in('test2', output) @@ -1872,7 +1872,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): # now we supercede the latest checkpoint by a successful backup: self.cmd('create', self.repository_location + '::test5', src_dir) self.cmd('prune', self.repository_location, '--keep-daily=2') - output = self.cmd('list', self.repository_location) + output = self.cmd('list', '--consider-checkpoints', self.repository_location) # all checkpoints should be gone now: self.assert_not_in('checkpoint', output) # the latest archive must be still there @@ -1980,6 +1980,21 @@ class ArchiverTestCase(ArchiverTestCaseBase): assert "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0 input/amb" in output assert "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 input/empty_file" in output + def test_list_consider_checkpoints(self): + self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd('create', self.repository_location + '::test1', src_dir) + # these are not really a checkpoints, but they look like some: + self.cmd('create', self.repository_location + '::test2.checkpoint', src_dir) + self.cmd('create', self.repository_location + '::test3.checkpoint.1', src_dir) + output = self.cmd('list', self.repository_location) + assert "test1" in output + assert "test2.checkpoint" not in output + assert "test3.checkpoint.1" not in output + output = self.cmd('list', '--consider-checkpoints', self.repository_location) + assert "test1" in output + assert "test2.checkpoint" in output + assert "test3.checkpoint.1" in output + def test_list_chunk_counts(self): self.create_regular_file('empty_file', size=0) self.create_regular_file('two_chunks')