diff --git a/attic/archive.py b/attic/archive.py index d3a387add..c4287b5aa 100644 --- a/attic/archive.py +++ b/attic/archive.py @@ -451,17 +451,15 @@ class ArchiveChecker: def __init__(self): self.error_found = False - self.progress = True self.possibly_superseded = set() self.tmpdir = tempfile.mkdtemp() def __del__(self): shutil.rmtree(self.tmpdir) - def check(self, repository, progress=True, repair=False): + def check(self, repository, repair=False): self.report_progress('Starting archive consistency check...') self.repair = repair - self.progress = progress self.repository = repository self.init_chunks() self.key = self.identify_key(repository) @@ -494,9 +492,8 @@ class ArchiveChecker: def report_progress(self, msg, error=False): if error: self.error_found = True - if error or self.progress: - print(msg, file=sys.stderr) - sys.stderr.flush() + print(msg, file=sys.stderr) + sys.stderr.flush() def identify_key(self, repository): cdata = repository.get(next(self.chunks.iteritems())[0]) diff --git a/attic/archiver.py b/attic/archiver.py index b3e6a1132..eec1045a0 100644 --- a/attic/archiver.py +++ b/attic/archiver.py @@ -6,6 +6,7 @@ import io import os import stat import sys +import textwrap from attic import __version__ from attic.archive import Archive, ArchiveChecker @@ -72,13 +73,10 @@ in data loss. Type "Yes I am sure" if you understand this and want to continue.\n""") if input('Do you want to continue? ') == 'Yes I am sure': break - if args.progress is None: - args.progress = sys.stdout.isatty() or args.verbose - if not repository.check(progress=args.progress, repair=args.repair): - return 1 - - if not ArchiveChecker().check(repository, progress=args.progress, repair=args.repair): - return 1 + if args.phase in ('all', 'repository') and not repository.check(repair=args.repair): + return 1 + if args.phase in ('all', 'archive') and not ArchiveChecker().check(repository, repair=args.repair): + return 1 return 0 def do_change_passphrase(self, args): @@ -429,26 +427,31 @@ Type "Yes I am sure" if you understand this and want to continue.\n""") choices=('none', 'passphrase', 'keyfile'), default='none', help='select encryption method') - check_epilog = """ - Progress status will be reported on the standard error stream by default when - it is attached to a terminal. Any problems found are printed to the standard error - stream and the command will have a non zero exit code. - """ + check_epilog = textwrap.dedent(""" + The check command verifies the consistency of a repository and corresponding + archives. The check is performed in two phases. In the first phase the + checksums of the underlying repository segment files are verified to detect + bit rot and other types of damage. In the second phase the consistency and + correctness of the archive metadata is verified. + + A specific check phase can be selected using the --phase=repository|archive + option. This can be useful since the "archive" phase can be time consuming + and requires access to the key file and/or passphrase if encryption is enabled. + """) subparser = subparsers.add_parser('check', parents=[common_parser], description=self.do_check.__doc__, - epilog=check_epilog) + epilog=check_epilog, + formatter_class=argparse.RawDescriptionHelpFormatter) subparser.set_defaults(func=self.do_check) subparser.add_argument('repository', metavar='REPOSITORY', type=location_validator(archive=False), help='repository to check consistency of') - subparser.add_argument('--progress', dest='progress', action='store_true', - default=None, - help='Report progress status to standard output stream') - subparser.add_argument('--no-progress', dest='progress', action='store_false', - help='Disable progress reporting') + subparser.add_argument('--phase', dest='phase', choices=['repository', 'archive', 'all'], + default='all', + help='which checks to perform (default: all)') subparser.add_argument('--repair', dest='repair', action='store_true', default=False, - help='Attempt to repair any inconsistencies found') + help='attempt to repair any inconsistencies found') subparser = subparsers.add_parser('change-passphrase', parents=[common_parser], description=self.do_change_passphrase.__doc__) diff --git a/attic/remote.py b/attic/remote.py index ae61ba310..5017ef4de 100644 --- a/attic/remote.py +++ b/attic/remote.py @@ -182,8 +182,8 @@ class RemoteRepository(object): w_fds = [] self.ignore_responses |= set(waiting_for) - def check(self, progress=False, repair=False): - return self.call('check', progress, repair) + def check(self, repair=False): + return self.call('check', repair) def commit(self, *args): return self.call('commit') diff --git a/attic/repository.py b/attic/repository.py index 06aa150cf..bc7b62a68 100644 --- a/attic/repository.py +++ b/attic/repository.py @@ -233,7 +233,7 @@ class Repository(object): self.write_index() self.rollback() - def check(self, progress=False, repair=False): + def check(self, repair=False): """Check repository consistency This method verifies all segment checksums and makes sure @@ -244,9 +244,8 @@ class Repository(object): nonlocal error_found if error: error_found = True - if error or progress: - print(msg, file=sys.stderr) - sys.stderr.flush() + print(msg, file=sys.stderr) + sys.stderr.flush() assert not self._active_txn report_progress('Starting repository check...') diff --git a/attic/testsuite/archiver.py b/attic/testsuite/archiver.py index a4b07a801..b81567425 100644 --- a/attic/testsuite/archiver.py +++ b/attic/testsuite/archiver.py @@ -340,6 +340,17 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase): archive = Archive(repository, key, manifest, name) return archive, repository + def test_check_usage(self): + output = self.attic('check', self.repository_location, exit_code=0) + self.assert_in('Starting repository check', output) + self.assert_in('Starting archive consistency check', output) + output = self.attic('check', '--phase', 'repository', self.repository_location, exit_code=0) + self.assert_in('Starting repository check', output) + self.assert_not_in('Starting archive consistency check', output) + output = self.attic('check', '--phase', 'archive', self.repository_location, exit_code=0) + self.assert_not_in('Starting repository check', output) + self.assert_in('Starting archive consistency check', output) + def test_missing_file_chunk(self): archive, repository = self.open_archive('archive1') for item in archive.iter_items(): @@ -372,8 +383,8 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase): repository.delete(Manifest.MANIFEST_ID) repository.commit() self.attic('check', self.repository_location, exit_code=1) - self.attic('check', '--repair', '--progress', self.repository_location, exit_code=0) - self.attic('check', '--progress', self.repository_location, exit_code=0) + self.attic('check', '--repair', self.repository_location, exit_code=0) + self.attic('check', self.repository_location, exit_code=0) def test_extra_chunks(self): self.attic('check', self.repository_location, exit_code=0) diff --git a/docs/usage.rst b/docs/usage.rst index f67813403..57405599b 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -86,10 +86,6 @@ Examples .. include:: usage/check.rst.inc -The check command verifies the consistency of a repository. Any inconsistencies -found are reported to the standard error stream and the command will have a -non zero exit code. - .. include:: usage/delete.rst.inc This command deletes an archive from the repository. Any disk space not