diff --git a/setup_docs.py b/setup_docs.py index 056c23213..2f9dcb35d 100644 --- a/setup_docs.py +++ b/setup_docs.py @@ -280,7 +280,7 @@ class build_man(Command): 'recreate': ('patterns', 'placeholders', 'compression'), 'list': ('info', 'diff', 'prune', 'patterns'), 'info': ('list', 'diff'), - 'init': ('create', 'delete', 'check', 'list', 'key-import', 'key-export', 'key-change-passphrase'), + 'rcreate': ('rdelete', 'rlist', 'check', 'key-import', 'key-export', 'key-change-passphrase'), 'key-import': ('key-export', ), 'key-export': ('key-import', ), 'mount': ('umount', 'extract'), # Would be cooler if these two were on the same page diff --git a/src/borg/archiver.py b/src/borg/archiver.py index c92ba7e05..3273e8cb5 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -52,7 +52,7 @@ try: from .helpers import EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR, EXIT_SIGNAL_BASE from .helpers import Error, NoManifestError, set_ec from .helpers import positive_int_validator, location_validator, archivename_validator, ChunkerParams, Location - from .helpers import PrefixSpec, GlobSpec, CommentSpec, SortBySpec, FilesCacheMode + from .helpers import PrefixSpec, GlobSpec, NameSpec, CommentSpec, SortBySpec, FilesCacheMode from .helpers import BaseFormatter, ItemFormatter, ArchiveFormatter from .helpers import format_timedelta, format_file_size, parse_file_size, format_archive from .helpers import remove_surrogates, bin_to_hex, prepare_dump_dict, eval_escapes @@ -116,7 +116,7 @@ def argument(args, str_or_bool): def get_repository(location, *, create, exclusive, lock_wait, lock, append_only, make_parent_dirs, storage_quota, args): if location.proto == 'ssh': - repository = RemoteRepository(location.omit_archive(), create=create, exclusive=exclusive, + repository = RemoteRepository(location, create=create, exclusive=exclusive, lock_wait=lock_wait, lock=lock, append_only=append_only, make_parent_dirs=make_parent_dirs, args=args) @@ -174,8 +174,10 @@ def with_repository(fake=False, invert_fake=False, create=False, lock=True, def decorator(method): @functools.wraps(method) def wrapper(self, args, **kwargs): + location = getattr(args, 'location') + if not location.valid: # location always must be given + raise Error('missing repository, please use --repo or BORG_REPO env var!') lock = getattr(args, 'lock', _lock) - location = args.location # note: 'location' must be always present in args append_only = getattr(args, 'append_only', False) storage_quota = getattr(args, 'storage_quota', None) make_parent_dirs = getattr(args, 'make_parent_dirs', False) @@ -220,8 +222,8 @@ def with_other_repository(manifest=False, key=False, cache=False, compatibility= def decorator(method): @functools.wraps(method) def wrapper(self, args, **kwargs): - location = getattr(args, 'other_location', None) - if location is None: # nothing to do + location = getattr(args, 'other_location') + if not location.valid: # nothing to do return method(self, args, **kwargs) repository = get_repository(location, create=False, exclusive=True, @@ -255,7 +257,9 @@ def with_other_repository(manifest=False, key=False, cache=False, compatibility= def with_archive(method): @functools.wraps(method) def wrapper(self, args, repository, key, manifest, **kwargs): - archive = Archive(repository, key, manifest, args.location.archive, + archive_name = getattr(args, 'name', None) + assert archive_name is not None + archive = Archive(repository, key, manifest, archive_name, numeric_ids=getattr(args, 'numeric_ids', False), noflags=getattr(args, 'nobsdflags', False) or getattr(args, 'noflags', False), noacls=getattr(args, 'noacls', False), @@ -468,8 +472,8 @@ class Archiver: @with_repository(create=True, exclusive=True, manifest=False) @with_other_repository(key=True, compatibility=(Manifest.Operation.READ, )) - def do_init(self, args, repository, *, other_repository=None, other_key=None): - """Initialize an empty repository""" + def do_rcreate(self, args, repository, *, other_repository=None, other_key=None): + """Create a new, empty repository""" path = args.location.canonical_path() logger.info('Initializing repository at "%s"' % path) try: @@ -539,7 +543,7 @@ class Archiver: if args.prefix is not None: args.glob_archives = args.prefix + '*' if not args.repo_only and not ArchiveChecker().check( - repository, repair=args.repair, archive=args.location.archive, + repository, repair=args.repair, archive=args.name, first=args.first, last=args.last, sort_by=args.sort_by or 'ts', glob=args.glob_archives, verify_data=args.verify_data, save_space=args.save_space): return EXIT_WARNING @@ -664,34 +668,37 @@ class Archiver: def do_benchmark_crud(self, args): """Benchmark Create, Read, Update, Delete for archives.""" def measurement_run(repo, path): - archive = repo + '::borg-benchmark-crud' compression = '--compression=none' # measure create perf (without files cache to always have it chunking) t_start = time.monotonic() - rc = self.do_create(self.parse_args(['create', compression, '--files-cache=disabled', archive + '1', path])) + rc = self.do_create(self.parse_args([f'--repo={repo}', 'create', compression, '--files-cache=disabled', + 'borg-benchmark-crud1', path])) t_end = time.monotonic() dt_create = t_end - t_start assert rc == 0 # now build files cache - rc1 = self.do_create(self.parse_args(['create', compression, archive + '2', path])) - rc2 = self.do_delete(self.parse_args(['delete', archive + '2'])) + rc1 = self.do_create(self.parse_args([f'--repo={repo}', 'create', compression, + 'borg-benchmark-crud2', path])) + rc2 = self.do_delete(self.parse_args([f'--repo={repo}', 'delete', '-a', 'borg-benchmark-crud2'])) assert rc1 == rc2 == 0 # measure a no-change update (archive1 is still present) t_start = time.monotonic() - rc1 = self.do_create(self.parse_args(['create', compression, archive + '3', path])) + rc1 = self.do_create(self.parse_args([f'--repo={repo}', 'create', compression, + 'borg-benchmark-crud3', path])) t_end = time.monotonic() dt_update = t_end - t_start - rc2 = self.do_delete(self.parse_args(['delete', archive + '3'])) + rc2 = self.do_delete(self.parse_args([f'--repo={repo}', 'delete', '-a', 'borg-benchmark-crud3'])) assert rc1 == rc2 == 0 # measure extraction (dry-run: without writing result to disk) t_start = time.monotonic() - rc = self.do_extract(self.parse_args(['extract', '--dry-run', archive + '1'])) + rc = self.do_extract(self.parse_args([f'--repo={repo}', 'extract', 'borg-benchmark-crud1', + '--dry-run'])) t_end = time.monotonic() dt_extract = t_end - t_start assert rc == 0 # measure archive deletion (of LAST present archive with the data) t_start = time.monotonic() - rc = self.do_delete(self.parse_args(['delete', archive + '1'])) + rc = self.do_delete(self.parse_args([f'--repo={repo}', 'delete', '-a', 'borg-benchmark-crud1'])) t_end = time.monotonic() dt_delete = t_end - t_start assert rc == 0 @@ -1007,7 +1014,7 @@ class Archiver: with Cache(repository, key, manifest, progress=args.progress, lock_wait=self.lock_wait, permit_adhoc_cache=args.no_cache_sync, cache_mode=args.files_cache_mode, iec=args.iec) as cache: - archive = Archive(repository, key, manifest, args.location.archive, cache=cache, + archive = Archive(repository, key, manifest, args.name, cache=cache, create=True, checkpoint_interval=args.checkpoint_interval, numeric_ids=args.numeric_ids, noatime=not args.atime, noctime=args.noctime, progress=args.progress, @@ -1470,7 +1477,7 @@ class Archiver: print_output = print_json_output if args.json_lines else print_text_output archive1 = archive - archive2 = Archive(repository, key, manifest, args.archive2, + archive2 = Archive(repository, key, manifest, args.other_name, consider_part_files=args.consider_part_files) can_compare_chunk_ids = archive1.metadata.get('chunker_params', False) == archive2.metadata.get( @@ -1501,42 +1508,87 @@ class Archiver: @with_archive def do_rename(self, args, repository, manifest, key, cache, archive): """Rename an existing archive""" - archive.rename(args.name) + archive.rename(args.newname) manifest.write() repository.commit(compact=False) cache.commit() return self.exit_code @with_repository(exclusive=True, manifest=False) - def do_delete(self, args, repository): - """Delete an existing repository or archives""" - archive_filter_specified = any((args.first, args.last, args.prefix is not None, args.glob_archives)) - explicit_archives_specified = args.location.archive or args.archives + def do_rdelete(self, args, repository): + """Delete a repository""" self.output_list = args.output_list - if archive_filter_specified and explicit_archives_specified: - self.print_error('Mixing archive filters and explicitly named archives is not supported.') - return self.exit_code - if archive_filter_specified or explicit_archives_specified: - return self._delete_archives(args, repository) - else: - return self._delete_repository(args, repository) - - def _delete_archives(self, args, repository): - """Delete archives""" dry_run = args.dry_run + keep_security_info = args.keep_security_info - manifest, key = Manifest.load(repository, (Manifest.Operation.DELETE,)) + if not args.cache_only: + if args.forced == 0: # without --force, we let the user see the archives list and confirm. + id = bin_to_hex(repository.id) + location = repository._location.canonical_path() + msg = [] + try: + manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK) + n_archives = len(manifest.archives) + msg.append(f"You requested to completely DELETE the following repository " + f"*including* {n_archives} archives it contains:") + except NoManifestError: + n_archives = None + msg.append("You requested to completely DELETE the following repository " + "*including* all archives it may contain:") - if args.location.archive or args.archives: - archives = list(args.archives) - if args.location.archive: - archives.insert(0, args.location.archive) - archive_names = tuple(archives) + msg.append(DASHES) + msg.append(f"Repository ID: {id}") + msg.append(f"Location: {location}") + + if self.output_list: + msg.append("") + msg.append("Archives:") + + if n_archives is not None: + if n_archives > 0: + for archive_info in manifest.archives.list(sort_by=['ts']): + msg.append(format_archive(archive_info)) + else: + msg.append("This repository seems to not have any archives.") + else: + msg.append("This repository seems to have no manifest, so we can't " + "tell anything about its contents.") + + msg.append(DASHES) + msg.append("Type 'YES' if you understand this and want to continue: ") + msg = '\n'.join(msg) + if not yes(msg, false_msg="Aborting.", invalid_msg='Invalid answer, aborting.', truish=('YES',), + retry=False, env_var_override='BORG_DELETE_I_KNOW_WHAT_I_AM_DOING'): + self.exit_code = EXIT_ERROR + return self.exit_code + if not dry_run: + repository.destroy() + logger.info("Repository deleted.") + if not keep_security_info: + SecurityManager.destroy(repository) + else: + logger.info("Would delete repository.") + logger.info("Would %s security info." % ("keep" if keep_security_info else "delete")) + if not dry_run: + Cache.destroy(repository) + logger.info("Cache deleted.") 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 + logger.info("Would delete cache.") + return self.exit_code + + @with_repository(exclusive=True, manifest=False) + def do_delete(self, args, repository): + """Delete archives""" + self.output_list = args.output_list + dry_run = args.dry_run + manifest, key = Manifest.load(repository, (Manifest.Operation.DELETE,)) + archive_names = tuple(x.name for x in manifest.archives.list_considering(args)) + if not archive_names: + return self.exit_code + if args.glob_archives is None and args.first == 0 and args.last == 0: + self.print_error("Aborting: if you really want to delete all archives, please use -a '*' " + "or just delete the whole repository (might be much faster).") + return EXIT_ERROR if args.forced == 2: deleted = False @@ -1598,66 +1650,6 @@ class Archiver: return self.exit_code - def _delete_repository(self, args, repository): - """Delete a repository""" - dry_run = args.dry_run - keep_security_info = args.keep_security_info - - if not args.cache_only: - if args.forced == 0: # without --force, we let the user see the archives list and confirm. - id = bin_to_hex(repository.id) - location = repository._location.canonical_path() - msg = [] - try: - manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK) - n_archives = len(manifest.archives) - msg.append(f"You requested to completely DELETE the following repository " - f"*including* {n_archives} archives it contains:") - except NoManifestError: - n_archives = None - msg.append("You requested to completely DELETE the following repository " - "*including* all archives it may contain:") - - msg.append(DASHES) - msg.append(f"Repository ID: {id}") - msg.append(f"Location: {location}") - - if self.output_list: - msg.append("") - msg.append("Archives:") - - if n_archives is not None: - if n_archives > 0: - for archive_info in manifest.archives.list(sort_by=['ts']): - msg.append(format_archive(archive_info)) - else: - msg.append("This repository seems to not have any archives.") - else: - msg.append("This repository seems to have no manifest, so we can't " - "tell anything about its contents.") - - msg.append(DASHES) - msg.append("Type 'YES' if you understand this and want to continue: ") - msg = '\n'.join(msg) - if not yes(msg, false_msg="Aborting.", invalid_msg='Invalid answer, aborting.', truish=('YES',), - retry=False, env_var_override='BORG_DELETE_I_KNOW_WHAT_I_AM_DOING'): - self.exit_code = EXIT_ERROR - return self.exit_code - if not dry_run: - repository.destroy() - logger.info("Repository deleted.") - if not keep_security_info: - SecurityManager.destroy(repository) - else: - logger.info("Would delete repository.") - logger.info("Would %s security info." % ("keep" if keep_security_info else "delete")) - if not dry_run: - Cache.destroy(repository) - logger.info("Cache deleted.") - else: - logger.info("Would delete cache.") - return self.exit_code - def do_mount(self, args): """Mount archive or an entire repository as a FUSE filesystem""" # Perform these checks before opening the repository and asking for a passphrase. @@ -1693,19 +1685,7 @@ class Archiver: @with_repository(compatibility=(Manifest.Operation.READ,)) def do_list(self, args, repository, manifest, key): - """List archive or repository contents""" - if args.location.archive: - if args.json: - self.print_error('The --json option is only valid for listing archives, not archive contents.') - return self.exit_code - return self._list_archive(args, repository, manifest, key) - else: - 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, repository, manifest, key) - - def _list_archive(self, args, repository, manifest, key): + """List archive contents""" matcher = self.build_matcher(args.patterns, args.paths) if args.format is not None: format = args.format @@ -1715,7 +1695,7 @@ class Archiver: format = "{mode} {user:6} {group:6} {size:8} {mtime} {path}{extra}{NL}" def _list_inner(cache): - archive = Archive(repository, key, manifest, args.location.archive, cache=cache, + archive = Archive(repository, key, manifest, args.name, cache=cache, consider_part_files=args.consider_part_files) formatter = ItemFormatter(archive, format, json_lines=args.json_lines) @@ -1731,7 +1711,9 @@ class Archiver: return self.exit_code - def _list_repository(self, args, repository, manifest, key): + @with_repository(compatibility=(Manifest.Operation.READ,)) + def do_rlist(self, args, repository, manifest, key): + """List the archives contained in a repository""" if args.format is not None: format = args.format elif args.short: @@ -1755,23 +1737,48 @@ class Archiver: return self.exit_code + @with_repository(cache=True, compatibility=(Manifest.Operation.READ,)) + def do_rinfo(self, args, repository, manifest, key, cache): + """Show repository infos""" + info = basic_json_data(manifest, cache=cache, extra={ + 'security_dir': cache.security_manager.dir, + }) + + if args.json: + json_print(info) + else: + encryption = 'Encrypted: ' + if key.NAME in ('plaintext', 'authenticated'): + encryption += 'No' + else: + encryption += 'Yes (%s)' % key.NAME + if key.NAME.startswith('key file'): + encryption += '\nKey file: %s' % key.find_key() + info['encryption'] = encryption + + print(textwrap.dedent(""" + Repository ID: {id} + Location: {location} + {encryption} + Cache: {cache.path} + Security dir: {security_dir} + """).strip().format( + id=bin_to_hex(repository.id), + location=repository._location.canonical_path(), + **info)) + print(DASHES) + print(STATS_HEADER) + print(str(cache)) + return self.exit_code + @with_repository(cache=True, compatibility=(Manifest.Operation.READ,)) def do_info(self, args, repository, manifest, key, cache): """Show archive details such as disk space used""" - if any((args.location.archive, args.first, args.last, args.prefix is not None, args.glob_archives)): - return self._info_archives(args, repository, manifest, key, cache) - else: - return self._info_repository(args, repository, manifest, key, cache) - - def _info_archives(self, args, repository, manifest, key, cache): def format_cmdline(cmdline): return remove_surrogates(' '.join(shlex.quote(x) for x in cmdline)) - 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)) + args.consider_checkpoints = True + archive_names = tuple(x.name for x in manifest.archives.list_considering(args)) output_data = [] @@ -1812,38 +1819,6 @@ class Archiver: })) return self.exit_code - def _info_repository(self, args, repository, manifest, key, cache): - info = basic_json_data(manifest, cache=cache, extra={ - 'security_dir': cache.security_manager.dir, - }) - - if args.json: - json_print(info) - else: - encryption = 'Encrypted: ' - if key.NAME in ('plaintext', 'authenticated'): - encryption += 'No' - else: - encryption += 'Yes (%s)' % key.NAME - if key.NAME.startswith('key file'): - encryption += '\nKey file: %s' % key.find_key() - info['encryption'] = encryption - - print(textwrap.dedent(""" - Repository ID: {id} - Location: {location} - {encryption} - Cache: {cache.path} - Security dir: {security_dir} - """).strip().format( - id=bin_to_hex(repository.id), - location=repository._location.canonical_path(), - **info)) - print(DASHES) - print(STATS_HEADER) - print(str(cache)) - return self.exit_code - @with_repository(exclusive=True, compatibility=(Manifest.Operation.DELETE,)) def do_prune(self, args, repository, manifest, key): """Prune repository archives according to specified rules""" @@ -1998,25 +1973,16 @@ class Archiver: checkpoint_interval=args.checkpoint_interval, dry_run=args.dry_run, timestamp=args.timestamp) - if args.location.archive: - name = args.location.archive + archive_names = tuple(archive.name for archive in manifest.archives.list_considering(args)) + if args.target is not None and len(archive_names) != 1: + self.print_error('--target: Need to specify single archive') + return self.exit_code + for name in archive_names: if recreater.is_temporary_archive(name): - self.print_error('Refusing to work on temporary archive of prior recreate: %s', name) - return self.exit_code + continue + print('Processing', name) if not recreater.recreate(name, args.comment, args.target): - self.print_error('Nothing to do. Archive was not processed.\n' - 'Specify at least one pattern, PATH, --comment, re-compression or re-chunking option.') - else: - if args.target is not None: - self.print_error('--target: Need to specify single archive') - return self.exit_code - for archive in manifest.archives.list(sort_by=['ts']): - name = archive.name - if recreater.is_temporary_archive(name): - continue - print('Processing', name) - if not recreater.recreate(name, args.comment): - logger.info('Skipped archive %s: Nothing to do. Archive was not processed.', name) + logger.info('Skipped archive %s: Nothing to do. Archive was not processed.', name) if not args.dry_run: manifest.write() repository.commit(compact=False) @@ -2043,7 +2009,7 @@ class Archiver: t0 = datetime.utcnow() t0_monotonic = time.monotonic() - archive = Archive(repository, key, manifest, args.location.archive, cache=cache, + archive = Archive(repository, key, manifest, args.name, cache=cache, create=True, checkpoint_interval=args.checkpoint_interval, progress=args.progress, chunker_params=args.chunker_params, start=t0, start_monotonic=t0_monotonic, @@ -2284,7 +2250,7 @@ class Archiver: @with_repository(compatibility=Manifest.NO_OPERATION_CHECK) def do_debug_dump_archive_items(self, args, repository, manifest, key): """dump (decrypted, decompressed) archive items metadata (not: data)""" - archive = Archive(repository, key, manifest, args.location.archive, + archive = Archive(repository, key, manifest, args.name, consider_part_files=args.consider_part_files) for i, item_id in enumerate(archive.metadata.items): data = key.decrypt(item_id, repository.get(item_id)) @@ -2300,9 +2266,9 @@ class Archiver: """dump decoded archive metadata (not: data)""" try: - archive_meta_orig = manifest.archives.get_raw_dict()[args.location.archive] + archive_meta_orig = manifest.archives.get_raw_dict()[args.name] except KeyError: - raise Archive.DoesNotExist(args.location.archive) + raise Archive.DoesNotExist(args.name) indent = 4 @@ -2312,7 +2278,7 @@ class Archiver: def output(fd): # this outputs megabytes of data for a modest sized archive, so some manual streaming json output fd.write('{\n') - fd.write(' "_name": ' + json.dumps(args.location.archive) + ",\n") + fd.write(' "_name": ' + json.dumps(args.name) + ",\n") fd.write(' "_manifest_entry":\n') fd.write(do_indent(prepare_dump_dict(archive_meta_orig))) fd.write(',\n') @@ -2803,7 +2769,7 @@ class Archiver: This allows you to share the same patterns between multiple repositories without needing to specify them on the command line.\n\n''') helptext['placeholders'] = textwrap.dedent(''' - Repository (or Archive) URLs, ``--prefix``, ``--glob-archives``, ``--comment`` + Repository URLs, ``--name``, ``--prefix``, ``--glob-archives``, ``--comment`` and ``--remote-path`` values support these placeholders: {hostname} @@ -3200,6 +3166,9 @@ class Archiver: 'compatible file can be generated by suffixing FILE with ".pyprof".') add_common_option('--rsh', metavar='RSH', dest='rsh', help="Use this command to connect to the 'borg serve' process (default: 'ssh')") + add_common_option('-r', '--repo', metavar='REPO', dest='location', + type=location_validator(other=False), default=Location(other=False), + help="repository to use") def define_exclude_and_patterns(add_option, *, tag_files=False, strip_components=False): add_option('-e', '--exclude', metavar='PATTERN', dest='patterns', @@ -3263,8 +3232,6 @@ class Archiver: def define_borg_mount(parser): 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, @@ -3427,10 +3394,6 @@ class Archiver: help='benchmarks borg CRUD (create, extract, update, delete).') subparser.set_defaults(func=self.do_benchmark_crud) - subparser.add_argument('location', metavar='REPOSITORY', - type=location_validator(archive=False), - help='repository to use for benchmark (must exist)') - subparser.add_argument('path', metavar='PATH', help='path were to create benchmark input data') bench_cpu_epilog = process_epilog(""" @@ -3460,9 +3423,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='break repository and cache locks') subparser.set_defaults(func=self.do_break_lock) - subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', - type=location_validator(archive=False), - help='repository for which to break the locks') # borg check check_epilog = process_epilog(""" @@ -3545,9 +3505,8 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='verify repository') subparser.set_defaults(func=self.do_check) - subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='', - type=location_validator(), - help='repository or archive to check consistency of') + subparser.add_argument('--name', dest='name', metavar='NAME', type=NameSpec, + help='specify the archive name') subparser.add_argument('--repository-only', dest='repo_only', action='store_true', help='only perform repository checks') subparser.add_argument('--archives-only', dest='archives_only', action='store_true', @@ -3595,9 +3554,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='compact segment files / free space in repo') subparser.set_defaults(func=self.do_compact) - subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', - type=location_validator(archive=False), - help='repository to compact') subparser.add_argument('--cleanup-commits', dest='cleanup_commits', action='store_true', help='cleanup commit-only 17-byte segment files') subparser.add_argument('--threshold', metavar='PERCENT', dest='threshold', @@ -3635,9 +3591,6 @@ class Archiver: group.add_argument('-l', '--list', dest='list', action='store_true', help='list the configuration of the repo') - subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', - type=location_validator(archive=False, proto='file'), - help='repository to configure') subparser.add_argument('name', metavar='NAME', nargs='?', help='name of config key') subparser.add_argument('value', metavar='VALUE', nargs='?', @@ -3921,9 +3874,8 @@ class Archiver: help='select compression algorithm, see the output of the ' '"borg help compression" command for details.') - subparser.add_argument('location', metavar='ARCHIVE', - type=location_validator(archive=True), - help='name of archive to create (must be also a valid directory name)') + subparser.add_argument('name', metavar='NAME', type=NameSpec, + help='specify the archive name') subparser.add_argument('paths', metavar='PATH', nargs='*', type=str, help='paths to archive') @@ -3966,9 +3918,8 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='dump archive items (metadata) (debug)') subparser.set_defaults(func=self.do_debug_dump_archive_items) - subparser.add_argument('location', metavar='ARCHIVE', - type=location_validator(archive=True), - help='archive to dump') + subparser.add_argument('name', metavar='NAME', type=NameSpec, + help='specify the archive name') debug_dump_archive_epilog = process_epilog(""" This command dumps all metadata of an archive in a decoded form to a file. @@ -3979,9 +3930,8 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='dump decoded archive metadata (debug)') subparser.set_defaults(func=self.do_debug_dump_archive) - subparser.add_argument('location', metavar='ARCHIVE', - type=location_validator(archive=True), - help='archive to dump') + subparser.add_argument('name', metavar='NAME', type=NameSpec, + help='specify the archive name') subparser.add_argument('path', metavar='PATH', type=str, help='file to dump data into') @@ -3994,9 +3944,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='dump decoded repository metadata (debug)') subparser.set_defaults(func=self.do_debug_dump_manifest) - subparser.add_argument('location', metavar='REPOSITORY', - type=location_validator(archive=False), - help='repository to dump') subparser.add_argument('path', metavar='PATH', type=str, help='file to dump data into') @@ -4009,9 +3956,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='dump repo objects (debug)') subparser.set_defaults(func=self.do_debug_dump_repo_objs) - subparser.add_argument('location', metavar='REPOSITORY', - type=location_validator(archive=False), - help='repository to dump') subparser.add_argument('--ghost', dest='ghost', action='store_true', help='dump all segment file contents, including deleted/uncommitted objects and commits.') @@ -4024,9 +3968,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='search repo objects (debug)') subparser.set_defaults(func=self.do_debug_search_repo_objs) - subparser.add_argument('location', metavar='REPOSITORY', - type=location_validator(archive=False), - help='repository to search') subparser.add_argument('wanted', metavar='WANTED', type=str, help='term to search the repo for, either 0x1234abcd hex term or a string') @@ -4039,9 +3980,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='get object from repository (debug)') subparser.set_defaults(func=self.do_debug_get_obj) - subparser.add_argument('location', metavar='REPOSITORY', - type=location_validator(archive=False), - help='repository to use') subparser.add_argument('id', metavar='ID', type=str, help='hex object ID to get from the repo') subparser.add_argument('path', metavar='PATH', type=str, @@ -4056,9 +3994,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='put object to repository (debug)') subparser.set_defaults(func=self.do_debug_put_obj) - subparser.add_argument('location', metavar='REPOSITORY', - type=location_validator(archive=False), - help='repository to use') subparser.add_argument('paths', metavar='PATH', nargs='+', type=str, help='file(s) to read and create object(s) from') @@ -4071,9 +4006,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='delete object from repository (debug)') subparser.set_defaults(func=self.do_debug_delete_obj) - subparser.add_argument('location', metavar='REPOSITORY', - type=location_validator(archive=False), - help='repository to use') subparser.add_argument('ids', metavar='IDs', nargs='+', type=str, help='hex object ID(s) to delete from the repo') @@ -4086,9 +4018,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='show refcount for object from repository (debug)') subparser.set_defaults(func=self.do_debug_refcount_obj) - subparser.add_argument('location', metavar='REPOSITORY', - type=location_validator(archive=False), - help='repository to use') subparser.add_argument('ids', metavar='IDs', nargs='+', type=str, help='hex object ID(s) to show refcounts for') @@ -4101,9 +4030,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='dump repo hints (debug)') subparser.set_defaults(func=self.do_debug_dump_hints) - subparser.add_argument('location', metavar='REPOSITORY', - type=location_validator(archive=False), - help='repository to dump') subparser.add_argument('path', metavar='PATH', type=str, help='file to dump data into') @@ -4121,18 +4047,42 @@ class Archiver: subparser.add_argument('output', metavar='OUTPUT', type=argparse.FileType('wb'), help='Output file') - # borg delete - delete_epilog = process_epilog(""" - This command deletes an archive from the repository or the complete repository. - - Important: When deleting archives, repository disk space is **not** freed until - you run ``borg compact``. + # borg rdelete + rdelete_epilog = process_epilog(""" + This command deletes the complete repository. When you delete a complete repository, the security info and local cache for it (if any) are also deleted. Alternatively, you can delete just the local cache with the ``--cache-only`` option, or keep the security info with the ``--keep-security-info`` option. + Always first use ``--dry-run --list`` to see what would be deleted. + """) + subparser = subparsers.add_parser('rdelete', parents=[common_parser], add_help=False, + description=self.do_rdelete.__doc__, + epilog=rdelete_epilog, + formatter_class=argparse.RawDescriptionHelpFormatter, + help='delete repository') + subparser.set_defaults(func=self.do_rdelete) + subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true', + help='do not change repository') + subparser.add_argument('--list', dest='output_list', action='store_true', + help='output verbose list of archives') + subparser.add_argument('--force', dest='forced', action='count', default=0, + help='force deletion of corrupted archives, ' + 'use ``--force --force`` in case ``--force`` does not work.') + subparser.add_argument('--cache-only', dest='cache_only', action='store_true', + help='delete only the local cache for the given repository') + subparser.add_argument('--keep-security-info', dest='keep_security_info', action='store_true', + help='keep the local security info when deleting a repository') + + # borg delete + delete_epilog = process_epilog(""" + This command deletes archives from the repository. + + Important: When deleting archives, repository disk space is **not** freed until + you run ``borg compact``. + When in doubt, use ``--dry-run --list`` to see what would be deleted. When using ``--stats``, you will get some statistics about how much data was @@ -4146,9 +4096,7 @@ class Archiver: (for more info on these patterns, see :ref:`borg_patterns`). Note that these two options are mutually exclusive. - To avoid accidentally deleting archives, especially when using glob patterns, - it might be helpful to use the ``--dry-run`` to test out the command without - actually making any changes to the repository. + Always first use ``--dry-run --list`` to see what would be deleted. """) subparser = subparsers.add_parser('delete', parents=[common_parser], add_help=False, description=self.do_delete.__doc__, @@ -4160,6 +4108,8 @@ class Archiver: help='do not change repository') subparser.add_argument('--list', dest='output_list', action='store_true', help='output verbose list of archives') + subparser.add_argument('--consider-checkpoints', action='store_true', dest='consider_checkpoints', + help='consider checkpoint archives for deletion (default: not considered).') subparser.add_argument('-s', '--stats', dest='stats', action='store_true', help='print statistics for the deleted archive') subparser.add_argument('--cache-only', dest='cache_only', action='store_true', @@ -4171,11 +4121,6 @@ class Archiver: help='keep the local security info when deleting a repository') subparser.add_argument('--save-space', dest='save_space', action='store_true', help='work slower, but using less space') - subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='', - type=location_validator(), - help='repository or archive to delete') - subparser.add_argument('archives', metavar='ARCHIVE', nargs='*', - help='archives to delete') define_archive_filters_group(subparser) # borg transfer @@ -4186,12 +4131,12 @@ class Archiver: # initialize DST_REPO reusing key material from SRC_REPO, so that # chunking and chunk id generation will work in the same way as before. - borg init --other-location=SRC_REPO --encryption=DST_ENC DST_REPO + borg --repo=DST_REPO init --other-repo=SRC_REPO --encryption=DST_ENC # transfer archives from SRC_REPO to DST_REPO - borg transfer --dry-run SRC_REPO DST_REPO # check what it would do - borg transfer SRC_REPO DST_REPO # do it! - borg transfer --dry-run SRC_REPO DST_REPO # check! anything left? + borg --repo=DST_REPO transfer --other-repo=SRC_REPO --dry-run # check what it would do + borg --repo=DST_REPO transfer --other-repo=SRC_REPO # do it! + borg --repo=DST_REPO transfer --other-repo=SRC_REPO --dry-run # check! anything left? The default is to transfer all archives, including checkpoint archives. @@ -4207,12 +4152,9 @@ class Archiver: subparser.set_defaults(func=self.do_transfer) subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true', help='do not change repository, just check') - subparser.add_argument('other_location', metavar='SRC_REPOSITORY', - type=location_validator(archive=False, other=True), - help='source repository') - subparser.add_argument('location', metavar='DST_REPOSITORY', - type=location_validator(archive=False, other=False), - help='destination repository') + subparser.add_argument('--other-repo', metavar='SRC_REPOSITORY', dest='other_location', + type=location_validator(other=True), default=Location(other=True), + help='transfer archives from the other repository') define_archive_filters_group(subparser) # borg diff @@ -4250,12 +4192,12 @@ class Archiver: help='Sort the output lines by file path.') subparser.add_argument('--json-lines', action='store_true', help='Format output as JSON Lines. ') - subparser.add_argument('location', metavar='REPO::ARCHIVE1', - type=location_validator(archive=True), - help='repository location and ARCHIVE1 name') - subparser.add_argument('archive2', metavar='ARCHIVE2', + subparser.add_argument('name', metavar='ARCHIVE1', type=archivename_validator(), - help='ARCHIVE2 name (no repository location allowed)') + help='ARCHIVE1 name') + subparser.add_argument('other_name', metavar='ARCHIVE2', + type=archivename_validator(), + help='ARCHIVE2 name') subparser.add_argument('paths', metavar='PATH', nargs='*', type=str, help='paths of items inside the archives to compare; patterns are supported') define_exclusion_group(subparser) @@ -4317,9 +4259,8 @@ class Archiver: subparser.add_argument('--tar-format', metavar='FMT', dest='tar_format', default='GNU', choices=('BORG', 'PAX', 'GNU'), help='select tar format: BORG, PAX or GNU') - subparser.add_argument('location', metavar='ARCHIVE', - type=location_validator(archive=True), - help='archive to export') + subparser.add_argument('name', metavar='NAME', type=NameSpec, + help='specify the archive name') subparser.add_argument('tarfile', metavar='FILE', help='output tar file. "-" to write to stdout instead.') subparser.add_argument('paths', metavar='PATH', nargs='*', type=str, @@ -4377,9 +4318,8 @@ class Archiver: help='write all extracted data to stdout') subparser.add_argument('--sparse', dest='sparse', action='store_true', help='create holes in output sparse file from all-zero chunks') - subparser.add_argument('location', metavar='ARCHIVE', - type=location_validator(archive=True), - help='archive to extract') + subparser.add_argument('name', metavar='NAME', type=NameSpec, + help='specify the archive name') subparser.add_argument('paths', metavar='PATH', nargs='*', type=str, help='paths to extract; patterns are supported') define_exclusion_group(subparser, strip_components=True) @@ -4393,9 +4333,31 @@ class Archiver: subparser.add_argument('topic', metavar='TOPIC', type=str, nargs='?', help='additional help on TOPIC') + # borg rinfo + rinfo_epilog = process_epilog(""" + This command displays detailed information about the repository. + + Please note that the deduplicated sizes of the individual archives do not add + up to the deduplicated size of the repository ("all archives"), because the two + are meaning different things: + + This archive / deduplicated size = amount of data stored ONLY for this archive + = unique chunks of this archive. + All archives / deduplicated size = amount of data stored in the repo + = all chunks in the repository. + """) + subparser = subparsers.add_parser('rinfo', parents=[common_parser], add_help=False, + description=self.do_rinfo.__doc__, + epilog=rinfo_epilog, + formatter_class=argparse.RawDescriptionHelpFormatter, + help='show repository information') + subparser.set_defaults(func=self.do_rinfo) + subparser.add_argument('--json', action='store_true', + help='format output as JSON') + # borg info info_epilog = process_epilog(""" - This command displays detailed information about the specified archive or repository. + This command displays detailed information about the specified archive. Please note that the deduplicated sizes of the individual archives do not add up to the deduplicated size of the repository ("all archives"), because the two @@ -4417,16 +4379,13 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='show repository or archive information') subparser.set_defaults(func=self.do_info) - subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='', - type=location_validator(), - help='repository or archive to display information about') subparser.add_argument('--json', action='store_true', help='format output as JSON') define_archive_filters_group(subparser) - # borg init - init_epilog = process_epilog(""" - This command initializes an empty repository. A repository is a filesystem + # borg rcreate + rcreate_epilog = process_epilog(""" + This command creates a new, empty repository. A repository is a filesystem directory containing the deduplicated data from zero or more archives. Encryption mode TLDR @@ -4439,7 +4398,7 @@ class Archiver: :: - borg init --encryption repokey /path/to/repo + borg rcreate --encryption repokey /path/to/repo Borg will: @@ -4548,16 +4507,13 @@ class Archiver: Neither is inherently linked to the key derivation function, but since we were going to break backwards compatibility anyway we took the opportunity to fix all 3 issues at once. """) - subparser = subparsers.add_parser('init', parents=[common_parser], add_help=False, - description=self.do_init.__doc__, epilog=init_epilog, + subparser = subparsers.add_parser('rcreate', parents=[common_parser], add_help=False, + description=self.do_rcreate.__doc__, epilog=rcreate_epilog, formatter_class=argparse.RawDescriptionHelpFormatter, - help='initialize empty repository') - subparser.set_defaults(func=self.do_init) - subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', - type=location_validator(archive=False), - help='repository to create') - subparser.add_argument('--other-location', metavar='OTHER_REPOSITORY', dest='other_location', - type=location_validator(archive=False, other=True), + help='create a new, empty repository') + subparser.set_defaults(func=self.do_rcreate) + subparser.add_argument('--other-repo', metavar='SRC_REPOSITORY', dest='other_location', + type=location_validator(other=True), default=Location(other=True), help='reuse the key material from the other repository') subparser.add_argument('-e', '--encryption', metavar='MODE', dest='encryption', required=True, choices=key_argument_names(), @@ -4626,8 +4582,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='export repository key for backup') subparser.set_defaults(func=self.do_key_export) - subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', - type=location_validator(archive=False)) subparser.add_argument('path', metavar='PATH', nargs='?', type=str, help='where to store the backup') subparser.add_argument('--paper', dest='paper', action='store_true', @@ -4657,8 +4611,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='import repository key from backup') subparser.set_defaults(func=self.do_key_import) - subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', - type=location_validator(archive=False)) subparser.add_argument('path', metavar='PATH', nargs='?', type=str, help='path to the backup (\'-\' to read from stdin)') subparser.add_argument('--paper', dest='paper', action='store_true', @@ -4679,8 +4631,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='change repository passphrase') subparser.set_defaults(func=self.do_change_passphrase) - subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', - type=location_validator(archive=False)) change_location_epilog = process_epilog(""" Change the location of a borg key. The key can be stored at different locations: @@ -4697,8 +4647,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='change key location') subparser.set_defaults(func=self.do_change_location) - subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', - type=location_validator(archive=False)) subparser.add_argument('key_mode', metavar='KEY_LOCATION', choices=('repokey', 'keyfile'), help='select key location') subparser.add_argument('--keep', dest='keep', action='store_true', @@ -4741,14 +4689,12 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='change key algorithm') subparser.set_defaults(func=self.do_change_algorithm) - subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', - type=location_validator(archive=False)) subparser.add_argument('algorithm', metavar='ALGORITHM', choices=list(KEY_ALGORITHMS), help='select key algorithm') # borg list list_epilog = process_epilog(""" - This command lists the contents of a repository or an archive. + This command lists the contents of an archive. For more help on include/exclude patterns, see the :ref:`borg_patterns` command output. @@ -4763,35 +4709,20 @@ class Archiver: Examples: :: - $ borg list --format '{archive}{NL}' /path/to/repo - ArchiveFoo - ArchiveBar - ... - - # {VAR:NUMBER} - pad to NUMBER columns. - # Strings are left-aligned, numbers are right-aligned. - # Note: time columns except ``isomtime``, ``isoctime`` and ``isoatime`` cannot be padded. - $ borg list --format '{archive:36} {time} [{id}]{NL}' /path/to/repo - ArchiveFoo Thu, 2021-12-09 10:22:28 [0b8e9a312bef3f2f6e2d0fc110c196827786c15eba0188738e81697a7fa3b274] - $ borg list --format '{mode} {user:6} {group:6} {size:8} {mtime} {path}{extra}{NL}' /path/to/repo::ArchiveFoo + $ borg list --format '{mode} {user:6} {group:6} {size:8} {mtime} {path}{extra}{NL}' ArchiveFoo -rw-rw-r-- user user 1024 Thu, 2021-12-09 10:22:17 file-foo ... # {VAR:NUMBER} - pad to NUMBER columns right-aligned. - $ borg list --format '{mode} {user:>6} {group:>6} {size:<8} {mtime} {path}{extra}{NL}' /path/to/repo::ArchiveFoo + $ borg list --format '{mode} {user:>6} {group:>6} {size:<8} {mtime} {path}{extra}{NL}' ArchiveFoo -rw-rw-r-- user user 1024 Thu, 2021-12-09 10:22:17 file-foo ... The following keys are always available: - """) + BaseFormatter.keys_help() + textwrap.dedent(""" - Keys available only when listing archives in a repository: - - """) + ArchiveFormatter.keys_help() + textwrap.dedent(""" - Keys available only when listing files in an archive: """) + ItemFormatter.keys_help() @@ -4799,36 +4730,74 @@ class Archiver: description=self.do_list.__doc__, epilog=list_epilog, formatter_class=argparse.RawDescriptionHelpFormatter, - help='list archive or repository contents') + help='list archive 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', metavar='FORMAT', dest='format', - help='specify format for file or archive listing ' - '(default for files: "{mode} {user:6} {group:6} {size:8} {mtime} {path}{extra}{NL}"; ' - 'for archives: "{archive:<36} {time} [{id}]{NL}")') - subparser.add_argument('--json', action='store_true', - help='Only valid for listing repository contents. Format output as JSON. ' - 'The form of ``--format`` is ignored, ' - 'but keys used in it are added to the JSON output. ' - 'Some keys are always present. Note: JSON can only represent text. ' - 'A "barchive" key is therefore not available.') + help='specify format for file listing ' + '(default: "{mode} {user:6} {group:6} {size:8} {mtime} {path}{extra}{NL}")') subparser.add_argument('--json-lines', action='store_true', - help='Only valid for listing archive contents. Format output as JSON Lines. ' + help='Format output as JSON Lines. ' 'The form of ``--format`` is ignored, ' 'but keys used in it are added to the JSON output. ' 'Some keys are always present. Note: JSON can only represent text. ' 'A "bpath" key is therefore not available.') - subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='', - type=location_validator(), - help='repository or archive to list contents of') + subparser.add_argument('name', metavar='NAME', type=NameSpec, + help='specify the archive name') subparser.add_argument('paths', metavar='PATH', nargs='*', type=str, help='paths to list; patterns are supported') - define_archive_filters_group(subparser) define_exclusion_group(subparser) + # borg rlist + rlist_epilog = process_epilog(""" + This command lists the archives contained in a repository. + .. man NOTES + The FORMAT specifier syntax + +++++++++++++++++++++++++++ + The ``--format`` option uses python's `format string syntax + `_. + Examples: + :: + $ borg rlist --format '{archive}{NL}' + ArchiveFoo + ArchiveBar + ... + + # {VAR:NUMBER} - pad to NUMBER columns. + # Strings are left-aligned, numbers are right-aligned. + # Note: time columns except ``isomtime``, ``isoctime`` and ``isoatime`` cannot be padded. + $ borg rlist --format '{archive:36} {time} [{id}]{NL}' /path/to/repo + ArchiveFoo Thu, 2021-12-09 10:22:28 [0b8e9a312bef3f2f6e2d0fc110c196827786c15eba0188738e81697a7fa3b274] + ... + + The following keys are always available: + + """) + BaseFormatter.keys_help() + textwrap.dedent(""" + Keys available only when listing archives in a repository: + + """) + ArchiveFormatter.keys_help() + subparser = subparsers.add_parser('rlist', parents=[common_parser], add_help=False, + description=self.do_rlist.__doc__, + epilog=rlist_epilog, + formatter_class=argparse.RawDescriptionHelpFormatter, + help='list repository contents') + subparser.set_defaults(func=self.do_rlist) + 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 the archive names, nothing else') + subparser.add_argument('--format', metavar='FORMAT', dest='format', + help='specify format for archive listing ' + '(default: "{archive:<36} {time} [{id}]{NL}")') + subparser.add_argument('--json', action='store_true', + help='Format output as JSON. ' + 'The form of ``--format`` is ignored, ' + 'but keys used in it are added to the JSON output. ' + 'Some keys are always present. Note: JSON can only represent text. ' + 'A "barchive" key is therefore not available.') + define_archive_filters_group(subparser) + subparser = subparsers.add_parser('mount', parents=[common_parser], add_help=False, description=self.do_mount.__doc__, epilog=mount_epilog, @@ -4926,9 +4895,6 @@ class Archiver: define_archive_filters_group(subparser, sort_by=False, first_last=False) subparser.add_argument('--save-space', dest='save_space', action='store_true', help='work slower, but using less space') - subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', - type=location_validator(archive=False), - help='repository to prune') # borg recreate recreate_epilog = process_epilog(""" @@ -5001,6 +4967,7 @@ class Archiver: define_exclusion_group(subparser, tag_files=True) archive_group = subparser.add_argument_group('Archive options') + define_archive_filters_group(archive_group) archive_group.add_argument('--target', dest='target', metavar='TARGET', default=None, type=archivename_validator(), help='create a new archive with the name ARCHIVE, do not replace existing archive ' @@ -5036,9 +5003,6 @@ class Archiver: 'HASH_MASK_BITS, HASH_WINDOW_SIZE) or `default` to use the current defaults. ' 'default: %s,%d,%d,%d,%d' % CHUNKER_PARAMS) - subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='', - type=location_validator(), - help='repository or archive to recreate') subparser.add_argument('paths', metavar='PATH', nargs='*', type=str, help='paths to recreate; patterns are supported') @@ -5054,12 +5018,12 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='rename archive') subparser.set_defaults(func=self.do_rename) - subparser.add_argument('location', metavar='ARCHIVE', - type=location_validator(archive=True), - help='archive to rename') - subparser.add_argument('name', metavar='NEWNAME', + subparser.add_argument('name', metavar='OLDNAME', type=archivename_validator(), - help='the new archive name to use') + help='specify the archive name') + subparser.add_argument('newname', metavar='NEWNAME', + type=archivename_validator(), + help='specify the new archive name') # borg serve serve_epilog = process_epilog(""" @@ -5176,9 +5140,6 @@ class Archiver: help='Enable manifest authentication (in key and cache) (Borg 1.0.9 and later).') subparser.add_argument('--disable-tam', dest='disable_tam', action='store_true', help='Disable manifest authentication (in key and cache).') - subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='', - type=location_validator(archive=False), - help='path to the repository to be upgraded') # borg with-lock with_lock_epilog = process_epilog(""" @@ -5202,9 +5163,6 @@ class Archiver: formatter_class=argparse.RawDescriptionHelpFormatter, help='run user command with lock held') subparser.set_defaults(func=self.do_with_lock) - subparser.add_argument('location', metavar='REPOSITORY', - type=location_validator(archive=False), - help='repository to lock') subparser.add_argument('command', metavar='COMMAND', help='command to run') subparser.add_argument('args', metavar='ARGS', nargs=argparse.REMAINDER, @@ -5286,9 +5244,8 @@ class Archiver: help='select compression algorithm, see the output of the ' '"borg help compression" command for details.') - subparser.add_argument('location', metavar='ARCHIVE', - type=location_validator(archive=True), - help='name of archive to create (must be also a valid directory name)') + subparser.add_argument('name', metavar='NAME', type=NameSpec, + help='specify the archive name') subparser.add_argument('tarfile', metavar='TARFILE', help='input tar file. "-" to read from stdin instead.') return parser @@ -5354,8 +5311,8 @@ class Archiver: parser.error('Need at least one PATH argument.') if not getattr(args, 'lock', True): # Option --bypass-lock sets args.lock = False bypass_allowed = {self.do_check, self.do_config, self.do_diff, - self.do_export_tar, self.do_extract, self.do_info, - self.do_list, self.do_mount, self.do_umount} + self.do_export_tar, self.do_extract, self.do_info, self.do_rinfo, + self.do_list, self.do_rlist, self.do_mount, self.do_umount} if func not in bypass_allowed: raise Error('Not allowed to bypass locking mechanism for chosen command') if getattr(args, 'timestamp', None): diff --git a/src/borg/fuse.py b/src/borg/fuse.py index 5c9f8b935..b6349da7d 100644 --- a/src/borg/fuse.py +++ b/src/borg/fuse.py @@ -35,7 +35,7 @@ from .crypto.low_level import blake2b_128 from .archiver import Archiver from .archive import Archive, get_item_uid_gid from .hashindex import FuseVersionsIndex -from .helpers import daemonize, daemonizing, signal_handler, format_file_size, Error +from .helpers import daemonize, daemonizing, signal_handler, format_file_size from .helpers import HardLinkManager from .helpers import msgpack from .item import Item @@ -272,22 +272,16 @@ class FuseBackend: def _create_filesystem(self): self._create_dir(parent=1) # first call, create root dir (inode == 1) - if self._args.location.archive: + self.versions_index = FuseVersionsIndex() + for archive in self._manifest.archives.list_considering(self._args): if self.versions: - raise Error("for versions view, do not specify a single archive, " - "but always give the repository as location.") - self._process_archive(self._args.location.archive) - else: - self.versions_index = FuseVersionsIndex() - for archive in self._manifest.archives.list_considering(self._args): - if self.versions: - # process archives immediately - self._process_archive(archive.name) - else: - # lazily load archives, create archive placeholder inode - archive_inode = self._create_dir(parent=1, mtime=int(archive.ts.timestamp() * 1e9)) - self.contents[1][os.fsencode(archive.name)] = archive_inode - self.pending_archives[archive_inode] = archive.name + # process archives immediately + self._process_archive(archive.name) + else: + # lazily load archives, create archive placeholder inode + archive_inode = self._create_dir(parent=1, mtime=int(archive.ts.timestamp() * 1e9)) + self.contents[1][os.fsencode(archive.name)] = archive_inode + self.pending_archives[archive_inode] = archive.name def get_item(self, inode): item = self._inode_cache.get(inode) diff --git a/src/borg/helpers/manifest.py b/src/borg/helpers/manifest.py index 1b9d91fb4..786eb27bb 100644 --- a/src/borg/helpers/manifest.py +++ b/src/borg/helpers/manifest.py @@ -103,11 +103,13 @@ class Archives(abc.MutableMapping): """ 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, and --consider-checkpoints can only be used on repository targets.') + name = getattr(args, 'name', None) + consider_checkpoints = getattr(args, 'consider_checkpoints', None) + if name is not None: + raise Error('Giving a specific name is incompatible with options --first, --last, --prefix, and --glob-archives, and --consider-checkpoints.') if args.prefix is not None: args.glob_archives = args.prefix + '*' - return self.list(sort_by=args.sort_by.split(','), consider_checkpoints=args.consider_checkpoints, glob=args.glob_archives, first=args.first, last=args.last) + return self.list(sort_by=args.sort_by.split(','), consider_checkpoints=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/helpers/parseformat.py b/src/borg/helpers/parseformat.py index 43e8d05eb..808466776 100644 --- a/src/borg/helpers/parseformat.py +++ b/src/borg/helpers/parseformat.py @@ -213,6 +213,8 @@ PrefixSpec = replace_placeholders GlobSpec = replace_placeholders +NameSpec = replace_placeholders + CommentSpec = replace_placeholders @@ -299,9 +301,7 @@ def parse_stringified_list(s): class Location: - """Object representing a repository / archive location - """ - proto = user = _host = port = path = archive = None + """Object representing a repository location""" # user must not contain "@", ":" or "/". # Quoting adduser error message: @@ -333,15 +333,6 @@ class Location: (?P(/([^:]|(:(?!:)))+)) # start with /, then any chars, but no "::" """ - # optional ::archive_name at the end, archive name must not contain "/". - # borg mount's FUSE filesystem creates one level of directories from - # the archive names and of course "/" is not valid in a directory name. - optional_archive_re = r""" - (?: - :: # "::" as separator - (?P[^/]+) # archive name must not contain "/" - )?$""" # must match until the end - # host NAME, or host IP ADDRESS (v4 or v6, v6 must be in square brackets) host_re = r""" (?P( @@ -356,23 +347,13 @@ class Location: (?Pssh):// # ssh:// """ + optional_user_re + host_re + r""" # user@ (optional), host name or address (?::(?P\d+))? # :port (optional) - """ + abs_path_re + optional_archive_re, re.VERBOSE) # path or path::archive + """ + abs_path_re, re.VERBOSE) # path file_re = re.compile(r""" (?Pfile):// # file:// - """ + file_path_re + optional_archive_re, re.VERBOSE) # servername/path, path or path::archive + """ + file_path_re, re.VERBOSE) # servername/path or path - local_re = re.compile( - local_path_re + optional_archive_re, re.VERBOSE) # local path with optional archive - - # get the repo from BORG_REPO env and the optional archive from param. - # if the syntax requires giving REPOSITORY (see "borg mount"), - # use "::" to let it use the env var. - # if REPOSITORY argument is optional, it'll automatically use the env. - env_re = re.compile(r""" # the repo part is fetched from BORG_REPO - (?:::$) # just "::" is ok (when a pos. arg is required, no archive) - | # or - """ + optional_archive_re, re.VERBOSE) # archive name (optional, may be empty) + local_re = re.compile(local_path_re, re.VERBOSE) # local path win_file_re = re.compile(r""" (?:file://)? # optional file protocol @@ -380,31 +361,34 @@ class Location: (?:[a-zA-Z]:)? # Drive letter followed by a colon (optional) (?:[^:]+) # Anything which does not contain a :, at least one character ) - """ + optional_archive_re, re.VERBOSE) # archive name (optional, may be empty) + """, re.VERBOSE) def __init__(self, text='', overrides={}, other=False): self.repo_env_var = 'BORG_OTHER_REPO' if other else 'BORG_REPO' - if not self.parse(text, overrides): - raise ValueError('Invalid location format: "%s"' % self.processed) + self.valid = False + self.proto = None + self.user = None + self._host = None + self.port = None + self.path = None + self.raw = None + self.processed = None + self.parse(text, overrides) def parse(self, text, overrides={}): + if not text: + # we did not get a text to parse, so we try to fetch from the environment + text = os.environ.get(self.repo_env_var) + if text is None: + return + self.raw = text # as given by user, might contain placeholders - self.processed = text = replace_placeholders(text, overrides) # after placeholder replacement - valid = self._parse(text) + self.processed = replace_placeholders(self.raw, overrides) # after placeholder replacement + valid = self._parse(self.processed) if valid: - return True - m = self.env_re.match(text) - if not m: - return False - repo_raw = os.environ.get(self.repo_env_var) - if repo_raw is None: - return False - repo = replace_placeholders(repo_raw, overrides) - valid = self._parse(repo) - self.archive = m.group('archive') - self.raw = repo_raw if not self.archive else repo_raw + self.raw - self.processed = repo if not self.archive else f'{repo}::{self.archive}' - return valid + self.valid = True + else: + raise ValueError('Invalid location format: "%s"' % self.processed) def _parse(self, text): def normpath_special(p): @@ -418,10 +402,9 @@ class Location: if m: self.proto = 'file' self.path = m.group('path') - self.archive = m.group('archive') return True - # On windows we currently only support windows paths + # On windows we currently only support windows paths. return False m = self.ssh_re.match(text) @@ -431,19 +414,16 @@ class Location: self._host = m.group('host') self.port = m.group('port') and int(m.group('port')) or None self.path = normpath_special(m.group('path')) - self.archive = m.group('archive') return True m = self.file_re.match(text) if m: self.proto = m.group('proto') self.path = normpath_special(m.group('path')) - self.archive = m.group('archive') return True m = self.local_re.match(text) if m: - self.path = normpath_special(m.group('path')) - self.archive = m.group('archive') self.proto = 'file' + self.path = normpath_special(m.group('path')) return True return False @@ -454,7 +434,6 @@ class Location: 'host=%r' % self.host, 'port=%r' % self.port, 'path=%r' % self.path, - 'archive=%r' % self.archive, ] return ', '.join(items) @@ -499,24 +478,13 @@ class Location: 'utcnow': DatetimeWrapper(timestamp), }) - def omit_archive(self): - loc = Location(self.raw) - loc.archive = None - loc.raw = loc.raw.split("::")[0] - loc.processed = loc.processed.split("::")[0] - return loc - -def location_validator(archive=None, proto=None, other=False): +def location_validator(proto=None, other=False): def validator(text): try: loc = Location(text, other=other) except ValueError as err: raise argparse.ArgumentTypeError(str(err)) from None - if archive is True and not loc.archive: - raise argparse.ArgumentTypeError('"%s": No archive specified' % text) - elif archive is False and loc.archive: - raise argparse.ArgumentTypeError('"%s": No archive can be specified' % text) if proto is not None and loc.proto != proto: if proto == 'file': raise argparse.ArgumentTypeError('"%s": Repository must be local' % text) diff --git a/src/borg/testsuite/__init__.py b/src/borg/testsuite/__init__.py index b1db8aa95..307798e64 100644 --- a/src/borg/testsuite/__init__.py +++ b/src/borg/testsuite/__init__.py @@ -248,7 +248,7 @@ class BaseTestCase(unittest.TestCase): mountpoint = tempfile.mkdtemp() else: os.mkdir(mountpoint) - args = ['mount', location, mountpoint] + list(options) + args = [f'--repo={location}', 'mount', mountpoint] + list(options) if os_fork: # Do not spawn, but actually (OS) fork. if os.fork() == 0: diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index e033d6cf7..ef22bf58b 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -147,16 +147,16 @@ def test_return_codes(cmd, tmpdir): input = tmpdir.mkdir('input') output = tmpdir.mkdir('output') input.join('test_file').write('content') - rc, out = cmd('init', '--encryption=none', '%s' % str(repo)) + rc, out = cmd('--repo=%s' % str(repo), 'rcreate', '--encryption=none') assert rc == EXIT_SUCCESS - rc, out = cmd('create', '%s::archive' % repo, str(input)) + rc, out = cmd('--repo=%s' % repo, 'create', 'archive', str(input)) assert rc == EXIT_SUCCESS with changedir(str(output)): - rc, out = cmd('extract', '%s::archive' % repo) + rc, out = cmd('--repo=%s' % repo, 'extract', 'archive') assert rc == EXIT_SUCCESS - rc, out = cmd('extract', '%s::archive' % repo, 'does/not/match') + rc, out = cmd('--repo=%s' % repo, 'extract', 'archive', 'does/not/match') assert rc == EXIT_WARNING # pattern did not match - rc, out = cmd('create', '%s::archive' % repo, str(input)) + rc, out = cmd('--repo=%s' % repo, 'create', 'archive', str(input)) assert rc == EXIT_ERROR # duplicate archive name @@ -203,9 +203,9 @@ def test_disk_full(cmd): shutil.rmtree(input, ignore_errors=True) # keep some space and some inodes in reserve that we can free up later: make_files(reserve, 80, 100000, rnd=False) - rc, out = cmd('init', repo) + rc, out = cmd(f'--repo={repo}', 'rcreate') if rc != EXIT_SUCCESS: - print('init', rc, out) + print('rcreate', rc, out) assert rc == EXIT_SUCCESS try: success, i = True, 0 @@ -219,7 +219,7 @@ def test_disk_full(cmd): break raise try: - rc, out = cmd('create', '%s::test%03d' % (repo, i), input) + rc, out = cmd('--repo=%s' % repo, 'create', 'test%03d' % i, input) success = rc == EXIT_SUCCESS if not success: print('create', rc, out) @@ -231,10 +231,10 @@ def test_disk_full(cmd): # now some error happened, likely we are out of disk space. # free some space so we can expect borg to be able to work normally: shutil.rmtree(reserve, ignore_errors=True) - rc, out = cmd('list', repo) + rc, out = cmd(f'--repo={repo}', 'rlist') if rc != EXIT_SUCCESS: - print('list', rc, out) - rc, out = cmd('check', '--repair', repo) + print('rlist', rc, out) + rc, out = cmd(f'--repo={repo}', 'check', '--repair') if rc != EXIT_SUCCESS: print('check', rc, out) assert rc == EXIT_SUCCESS @@ -299,7 +299,7 @@ class ArchiverTestCaseBase(BaseTestCase): return output def create_src_archive(self, name): - self.cmd('create', '--compression=lz4', self.repository_location + '::' + name, src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', '--compression=lz4', name, src_dir) def open_archive(self, name): repository = Repository(self.repository_path, exclusive=True) @@ -391,16 +391,16 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_basic_functionality(self): have_root = self.create_test_files() # fork required to test show-rc output - output = self.cmd('init', '--encryption=repokey', '--show-version', '--show-rc', self.repository_location, fork=True) + output = self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey', '--show-version', '--show-rc', fork=True) self.assert_in('borgbackup version', output) self.assert_in('terminating with success status, rc 0', output) - self.cmd('create', '--exclude-nodump', self.repository_location + '::test', 'input') - output = self.cmd('create', '--exclude-nodump', '--stats', self.repository_location + '::test.2', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', '--exclude-nodump', 'test', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'create', '--exclude-nodump', '--stats', 'test.2', 'input') self.assert_in('Archive name: test.2', output) self.assert_in('This archive: ', output) with changedir('output'): - self.cmd('extract', self.repository_location + '::test') - list_output = self.cmd('list', '--short', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') + list_output = self.cmd(f'--repo={self.repository_location}', 'rlist', '--short') self.assert_in('test', list_output) self.assert_in('test.2', list_output) expected = [ @@ -427,15 +427,15 @@ class ArchiverTestCase(ArchiverTestCaseBase): # remove the file we did not backup, so input and output become equal expected.remove('input/flagfile') # this file is UF_NODUMP os.remove(os.path.join('input', 'flagfile')) - list_output = self.cmd('list', '--short', self.repository_location + '::test') + list_output = self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--short') for name in expected: self.assert_in(name, list_output) self.assert_dirs_equal('input', 'output/input') - info_output = self.cmd('info', self.repository_location + '::test') + info_output = self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test') item_count = 5 if has_lchflags else 6 # one file is UF_NODUMP self.assert_in('Number of files: %d' % item_count, info_output) shutil.rmtree(self.cache_path) - info_output2 = self.cmd('info', self.repository_location + '::test') + info_output2 = self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test') def filter(output): # filter for interesting "info" output, ignore cache rebuilding related stuff @@ -462,10 +462,10 @@ class ArchiverTestCase(ArchiverTestCaseBase): hl_b = os.path.join(path_b, 'hardlink') self.create_regular_file(hl_a, contents=b'123456') os.link(hl_a, hl_b) - self.cmd('init', '--encryption=none', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input', 'input') # give input twice! + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=none') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input', 'input') # give input twice! # test if created archive has 'input' contents twice: - archive_list = self.cmd('list', '--json-lines', self.repository_location + '::test') + archive_list = self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--json-lines') paths = [json.loads(line)['path'] for line in archive_list.split('\n') if line] # we have all fs items exactly once! assert sorted(paths) == ['input', 'input/a', 'input/a/hardlink', 'input/b', 'input/b/hardlink'] @@ -476,13 +476,13 @@ class ArchiverTestCase(ArchiverTestCaseBase): repository_location = self.prefix + repository_path with pytest.raises(Repository.ParentPathDoesNotExist): # normal borg init does NOT create missing parent dirs - self.cmd('init', '--encryption=none', repository_location) + self.cmd(f'--repo={repository_location}', 'rcreate', '--encryption=none') # but if told so, it does: - self.cmd('init', '--encryption=none', '--make-parent-dirs', repository_location) + self.cmd(f'--repo={repository_location}', 'rcreate', '--encryption=none', '--make-parent-dirs') assert os.path.exists(parent_path) def test_unix_socket(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') try: sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.bind(os.path.join(self.input_path, 'unix-socket')) @@ -491,19 +491,19 @@ class ArchiverTestCase(ArchiverTestCaseBase): pytest.skip('unix sockets disabled or not supported') elif err.errno == errno.EACCES: pytest.skip('permission denied to create unix sockets') - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') sock.close() with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') assert not os.path.exists('input/unix-socket') @pytest.mark.skipif(not are_symlinks_supported(), reason='symlinks not supported') def test_symlink_extract(self): self.create_test_files() - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') assert os.readlink('input/link1') == 'somewhere' @pytest.mark.skipif(not are_symlinks_supported() or not are_hardlinks_supported(), @@ -513,10 +513,10 @@ class ArchiverTestCase(ArchiverTestCaseBase): with changedir('input'): os.symlink('target', 'symlink1') os.link('symlink1', 'symlink2', follow_symlinks=False) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir('output'): - output = self.cmd('extract', self.repository_location + '::test') + output = self.cmd(f'--repo={self.repository_location}', 'extract', 'test') print(output) with changedir('input'): assert os.path.exists('target') @@ -547,10 +547,10 @@ class ArchiverTestCase(ArchiverTestCaseBase): atime, mtime = 123456780, 234567890 have_noatime = has_noatime('input/file1') os.utime('input/file1', (atime, mtime)) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', '--atime', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', '--atime', 'test', 'input') with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') sti = os.stat('input/file1') sto = os.stat('output/input/file1') assert sti.st_mtime_ns == sto.st_mtime_ns == mtime * 1e9 @@ -567,10 +567,10 @@ class ArchiverTestCase(ArchiverTestCaseBase): birthtime, mtime, atime = 946598400, 946684800, 946771200 os.utime('input/file1', (atime, birthtime)) os.utime('input/file1', (atime, mtime)) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') sti = os.stat('input/file1') sto = os.stat('output/input/file1') assert int(sti.st_birthtime * 1e9) == int(sto.st_birthtime * 1e9) == birthtime * 1e9 @@ -583,10 +583,10 @@ class ArchiverTestCase(ArchiverTestCaseBase): birthtime, mtime, atime = 946598400, 946684800, 946771200 os.utime('input/file1', (atime, birthtime)) os.utime('input/file1', (atime, mtime)) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', '--nobirthtime', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input', '--nobirthtime') with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') sti = os.stat('input/file1') sto = os.stat('output/input/file1') assert int(sti.st_birthtime * 1e9) == birthtime * 1e9 @@ -644,10 +644,10 @@ class ArchiverTestCase(ArchiverTestCaseBase): if sparse_support: # we could create a sparse input file, so creating a backup of it and # extracting it again (as sparse) should also work: - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir(self.output_path): - self.cmd('extract', '--sparse', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--sparse') self.assert_dirs_equal('input', 'output/input') filename = os.path.join(self.output_path, 'input', 'sparse') with open(filename, 'rb') as fd: @@ -663,158 +663,158 @@ class ArchiverTestCase(ArchiverTestCaseBase): filename = os.path.join(self.input_path, filename) with open(filename, 'wb'): pass - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') for filename in filenames: with changedir('output'): - self.cmd('extract', self.repository_location + '::test', os.path.join('input', filename)) + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', os.path.join('input', filename)) assert os.path.exists(os.path.join('output', 'input', filename)) def test_repository_swap_detection(self): self.create_test_files() os.environ['BORG_PASSPHRASE'] = 'passphrase' - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') repository_id = self._extract_repository_id(self.repository_path) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') shutil.rmtree(self.repository_path) - self.cmd('init', '--encryption=none', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=none') self._set_repository_id(self.repository_path, repository_id) self.assert_equal(repository_id, self._extract_repository_id(self.repository_path)) if self.FORK_DEFAULT: - self.cmd('create', self.repository_location + '::test.2', 'input', exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}', 'create', 'test.2', 'input', exit_code=EXIT_ERROR) else: with pytest.raises(Cache.EncryptionMethodMismatch): - self.cmd('create', self.repository_location + '::test.2', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test.2', 'input') def test_repository_swap_detection2(self): self.create_test_files() - self.cmd('init', '--encryption=none', self.repository_location + '_unencrypted') + self.cmd(f'--repo={self.repository_location}_unencrypted', 'rcreate', '--encryption=none') os.environ['BORG_PASSPHRASE'] = 'passphrase' - self.cmd('init', '--encryption=repokey', self.repository_location + '_encrypted') - self.cmd('create', self.repository_location + '_encrypted::test', 'input') + self.cmd(f'--repo={self.repository_location}_encrypted', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}_encrypted', 'create', 'test', 'input') shutil.rmtree(self.repository_path + '_encrypted') os.rename(self.repository_path + '_unencrypted', self.repository_path + '_encrypted') if self.FORK_DEFAULT: - self.cmd('create', self.repository_location + '_encrypted::test.2', 'input', exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}_encrypted', 'create', 'test.2', 'input', exit_code=EXIT_ERROR) else: with pytest.raises(Cache.RepositoryAccessAborted): - self.cmd('create', self.repository_location + '_encrypted::test.2', 'input') + self.cmd(f'--repo={self.repository_location}_encrypted', 'create', 'test.2', 'input') def test_repository_swap_detection_no_cache(self): self.create_test_files() os.environ['BORG_PASSPHRASE'] = 'passphrase' - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') repository_id = self._extract_repository_id(self.repository_path) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') shutil.rmtree(self.repository_path) - self.cmd('init', '--encryption=none', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=none') self._set_repository_id(self.repository_path, repository_id) self.assert_equal(repository_id, self._extract_repository_id(self.repository_path)) - self.cmd('delete', '--cache-only', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rdelete', '--cache-only') if self.FORK_DEFAULT: - self.cmd('create', self.repository_location + '::test.2', 'input', exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}', 'create', 'test.2', 'input', exit_code=EXIT_ERROR) else: with pytest.raises(Cache.EncryptionMethodMismatch): - self.cmd('create', self.repository_location + '::test.2', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test.2', 'input') def test_repository_swap_detection2_no_cache(self): self.create_test_files() - self.cmd('init', '--encryption=none', self.repository_location + '_unencrypted') + self.cmd(f'--repo={self.repository_location}_unencrypted', 'rcreate', '--encryption=none') os.environ['BORG_PASSPHRASE'] = 'passphrase' - self.cmd('init', '--encryption=repokey', self.repository_location + '_encrypted') - self.cmd('create', self.repository_location + '_encrypted::test', 'input') - self.cmd('delete', '--cache-only', self.repository_location + '_unencrypted') - self.cmd('delete', '--cache-only', self.repository_location + '_encrypted') + self.cmd(f'--repo={self.repository_location}_encrypted', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}_encrypted', 'create', 'test', 'input') + self.cmd(f'--repo={self.repository_location}_unencrypted', 'rdelete', '--cache-only') + self.cmd(f'--repo={self.repository_location}_encrypted', 'rdelete', '--cache-only') shutil.rmtree(self.repository_path + '_encrypted') os.rename(self.repository_path + '_unencrypted', self.repository_path + '_encrypted') if self.FORK_DEFAULT: - self.cmd('create', self.repository_location + '_encrypted::test.2', 'input', exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}_encrypted', 'create', 'test.2', 'input', exit_code=EXIT_ERROR) else: with pytest.raises(Cache.RepositoryAccessAborted): - self.cmd('create', self.repository_location + '_encrypted::test.2', 'input') + self.cmd(f'--repo={self.repository_location}_encrypted', 'create', 'test.2', 'input') def test_repository_swap_detection_repokey_blank_passphrase(self): # Check that a repokey repo with a blank passphrase is considered like a plaintext repo. self.create_test_files() # User initializes her repository with her passphrase - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') # Attacker replaces it with her own repository, which is encrypted but has no passphrase set shutil.rmtree(self.repository_path) with environment_variable(BORG_PASSPHRASE=''): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') # Delete cache & security database, AKA switch to user perspective - self.cmd('delete', '--cache-only', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rdelete', '--cache-only') shutil.rmtree(self.get_security_dir()) with environment_variable(BORG_PASSPHRASE=None): # This is the part were the user would be tricked, e.g. she assumes that BORG_PASSPHRASE # is set, while it isn't. Previously this raised no warning, # since the repository is, technically, encrypted. if self.FORK_DEFAULT: - self.cmd('create', self.repository_location + '::test.2', 'input', exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}', 'create', 'test.2', 'input', exit_code=EXIT_ERROR) else: with pytest.raises(Cache.CacheInitAbortedError): - self.cmd('create', self.repository_location + '::test.2', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test.2', 'input') def test_repository_move(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') security_dir = self.get_security_dir() os.rename(self.repository_path, self.repository_path + '_new') with environment_variable(BORG_RELOCATED_REPO_ACCESS_IS_OK='yes'): - self.cmd('info', self.repository_location + '_new') + self.cmd(f'--repo={self.repository_location}_new', 'rinfo') with open(os.path.join(security_dir, 'location')) as fd: location = fd.read() assert location == Location(self.repository_location + '_new').canonical_path() # Needs no confirmation anymore - self.cmd('info', self.repository_location + '_new') + self.cmd(f'--repo={self.repository_location}_new', 'rinfo') shutil.rmtree(self.cache_path) - self.cmd('info', self.repository_location + '_new') + self.cmd(f'--repo={self.repository_location}_new', 'rinfo') shutil.rmtree(security_dir) - self.cmd('info', self.repository_location + '_new') + self.cmd(f'--repo={self.repository_location}_new', 'rinfo') for file in ('location', 'key-type', 'manifest-timestamp'): assert os.path.exists(os.path.join(security_dir, file)) def test_security_dir_compat(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') with open(os.path.join(self.get_security_dir(), 'location'), 'w') as fd: fd.write('something outdated') # This is fine, because the cache still has the correct information. security_dir and cache can disagree # if older versions are used to confirm a renamed repository. - self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rinfo') def test_unknown_unencrypted(self): - self.cmd('init', '--encryption=none', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=none') # Ok: repository is known - self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rinfo') # Ok: repository is still known (through security_dir) shutil.rmtree(self.cache_path) - self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rinfo') # Needs confirmation: cache and security dir both gone (eg. another host or rm -rf ~) shutil.rmtree(self.cache_path) shutil.rmtree(self.get_security_dir()) if self.FORK_DEFAULT: - self.cmd('info', self.repository_location, exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}', 'rinfo', exit_code=EXIT_ERROR) else: with pytest.raises(Cache.CacheInitAbortedError): - self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rinfo') with environment_variable(BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK='yes'): - self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rinfo') def test_strip_components(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('dir/file') - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir('output'): - self.cmd('extract', self.repository_location + '::test', '--strip-components', '3') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--strip-components', '3') assert not os.path.exists('file') with self.assert_creates_file('file'): - self.cmd('extract', self.repository_location + '::test', '--strip-components', '2') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--strip-components', '2') with self.assert_creates_file('dir/file'): - self.cmd('extract', self.repository_location + '::test', '--strip-components', '1') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--strip-components', '1') with self.assert_creates_file('input/dir/file'): - self.cmd('extract', self.repository_location + '::test', '--strip-components', '0') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--strip-components', '0') def _extract_hardlinks_setup(self): os.mkdir(os.path.join(self.input_path, 'dir1')) @@ -832,8 +832,8 @@ class ArchiverTestCase(ArchiverTestCaseBase): os.link(os.path.join(self.input_path, 'dir1/source2'), os.path.join(self.input_path, 'dir1/aaaa')) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') @requires_hardlinks @unittest.skipUnless(llfuse, 'llfuse not installed') @@ -847,22 +847,22 @@ class ArchiverTestCase(ArchiverTestCaseBase): ignore_perms = ['-o', 'ignore_permissions,defer_permissions'] else: ignore_perms = ['-o', 'ignore_permissions'] - with self.fuse_mount(self.repository_location + '::test', mountpoint, '--strip-components=2', *ignore_perms), \ - changedir(mountpoint): + with self.fuse_mount(self.repository_location, mountpoint, '-a', 'test', '--strip-components=2', *ignore_perms), \ + changedir(os.path.join(mountpoint, 'test')): assert os.stat('hardlink').st_nlink == 2 assert os.stat('subdir/hardlink').st_nlink == 2 assert open('subdir/hardlink', 'rb').read() == b'123456' assert os.stat('aaaa').st_nlink == 2 assert os.stat('source2').st_nlink == 2 - with self.fuse_mount(self.repository_location + '::test', mountpoint, 'input/dir1', *ignore_perms), \ - changedir(mountpoint): + with self.fuse_mount(self.repository_location, mountpoint, 'input/dir1', '-a', 'test', *ignore_perms), \ + changedir(os.path.join(mountpoint, 'test')): assert os.stat('input/dir1/hardlink').st_nlink == 2 assert os.stat('input/dir1/subdir/hardlink').st_nlink == 2 assert open('input/dir1/subdir/hardlink', 'rb').read() == b'123456' assert os.stat('input/dir1/aaaa').st_nlink == 2 assert os.stat('input/dir1/source2').st_nlink == 2 - with self.fuse_mount(self.repository_location + '::test', mountpoint, *ignore_perms), \ - changedir(mountpoint): + with self.fuse_mount(self.repository_location, mountpoint, '-a', 'test', *ignore_perms), \ + changedir(os.path.join(mountpoint, 'test')): assert os.stat('input/source').st_nlink == 4 assert os.stat('input/abba').st_nlink == 4 assert os.stat('input/dir1/hardlink').st_nlink == 4 @@ -873,7 +873,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_extract_hardlinks1(self): self._extract_hardlinks_setup() with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') assert os.stat('input/source').st_nlink == 4 assert os.stat('input/abba').st_nlink == 4 assert os.stat('input/dir1/hardlink').st_nlink == 4 @@ -884,14 +884,14 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_extract_hardlinks2(self): self._extract_hardlinks_setup() with changedir('output'): - self.cmd('extract', self.repository_location + '::test', '--strip-components', '2') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--strip-components', '2') assert os.stat('hardlink').st_nlink == 2 assert os.stat('subdir/hardlink').st_nlink == 2 assert open('subdir/hardlink', 'rb').read() == b'123456' assert os.stat('aaaa').st_nlink == 2 assert os.stat('source2').st_nlink == 2 with changedir('output'): - self.cmd('extract', self.repository_location + '::test', 'input/dir1') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', 'input/dir1') assert os.stat('input/dir1/hardlink').st_nlink == 2 assert os.stat('input/dir1/subdir/hardlink').st_nlink == 2 assert open('input/dir1/subdir/hardlink', 'rb').read() == b'123456' @@ -909,11 +909,11 @@ class ArchiverTestCase(ArchiverTestCaseBase): hl_b = os.path.join(path_b, 'hardlink') self.create_regular_file(hl_a, contents=b'123456') os.link(hl_a, hl_b) - self.cmd('init', '--encryption=none', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input', 'input') # give input twice! + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=none') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input', 'input') # give input twice! # now test extraction with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') # if issue #5603 happens, extraction gives rc == 1 (triggering AssertionError) and warnings like: # input/a/hardlink: link: [Errno 2] No such file or directory: 'input/a/hardlink' -> 'input/a/hardlink' # input/b/hardlink: link: [Errno 2] No such file or directory: 'input/a/hardlink' -> 'input/b/hardlink' @@ -922,24 +922,24 @@ class ArchiverTestCase(ArchiverTestCaseBase): assert os.stat('input/b/hardlink').st_nlink == 2 def test_extract_include_exclude(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('file2', size=1024 * 80) self.create_regular_file('file3', size=1024 * 80) self.create_regular_file('file4', size=1024 * 80) - self.cmd('create', '--exclude=input/file4', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', '--exclude=input/file4', 'test', 'input') with changedir('output'): - self.cmd('extract', self.repository_location + '::test', 'input/file1', ) + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', 'input/file1', ) self.assert_equal(sorted(os.listdir('output/input')), ['file1']) with changedir('output'): - self.cmd('extract', '--exclude=input/file2', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--exclude=input/file2') self.assert_equal(sorted(os.listdir('output/input')), ['file1', 'file3']) with changedir('output'): - self.cmd('extract', '--exclude-from=' + self.exclude_file_path, self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--exclude-from=' + self.exclude_file_path) self.assert_equal(sorted(os.listdir('output/input')), ['file1', 'file3']) def test_extract_include_exclude_regex(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('file2', size=1024 * 80) self.create_regular_file('file3', size=1024 * 80) @@ -947,32 +947,32 @@ class ArchiverTestCase(ArchiverTestCaseBase): self.create_regular_file('file333', size=1024 * 80) # Create with regular expression exclusion for file4 - self.cmd('create', '--exclude=re:input/file4$', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', '--exclude=re:input/file4$', 'test', 'input') with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') self.assert_equal(sorted(os.listdir('output/input')), ['file1', 'file2', 'file3', 'file333']) shutil.rmtree('output/input') # Extract with regular expression exclusion with changedir('output'): - self.cmd('extract', '--exclude=re:file3+', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--exclude=re:file3+') self.assert_equal(sorted(os.listdir('output/input')), ['file1', 'file2']) shutil.rmtree('output/input') # Combine --exclude with fnmatch and regular expression with changedir('output'): - self.cmd('extract', '--exclude=input/file2', '--exclude=re:file[01]', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--exclude=input/file2', '--exclude=re:file[01]') self.assert_equal(sorted(os.listdir('output/input')), ['file3', 'file333']) shutil.rmtree('output/input') # Combine --exclude-from and regular expression exclusion with changedir('output'): - self.cmd('extract', '--exclude-from=' + self.exclude_file_path, '--exclude=re:file1', - '--exclude=re:file(\\d)\\1\\1$', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--exclude-from=' + self.exclude_file_path, + '--exclude=re:file1', '--exclude=re:file(\\d)\\1\\1$') self.assert_equal(sorted(os.listdir('output/input')), ['file3']) def test_extract_include_exclude_regex_from_file(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('file2', size=1024 * 80) self.create_regular_file('file3', size=1024 * 80) @@ -985,9 +985,9 @@ class ArchiverTestCase(ArchiverTestCaseBase): fd.write(b're:input/file4$\n') fd.write(b'fm:*aa:*thing\n') - self.cmd('create', '--exclude-from=' + self.exclude_file_path, self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', '--exclude-from=' + self.exclude_file_path, 'test', 'input') with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') self.assert_equal(sorted(os.listdir('output/input')), ['file1', 'file2', 'file3', 'file333']) shutil.rmtree('output/input') @@ -996,7 +996,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): fd.write(b're:file3+\n') with changedir('output'): - self.cmd('extract', '--exclude-from=' + self.exclude_file_path, self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--exclude-from=' + self.exclude_file_path) self.assert_equal(sorted(os.listdir('output/input')), ['file1', 'file2']) shutil.rmtree('output/input') @@ -1008,78 +1008,78 @@ class ArchiverTestCase(ArchiverTestCaseBase): fd.write(b're:file2$\n') with changedir('output'): - self.cmd('extract', '--exclude-from=' + self.exclude_file_path, self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--exclude-from=' + self.exclude_file_path) self.assert_equal(sorted(os.listdir('output/input')), ['file3']) def test_extract_with_pattern(self): - self.cmd("init", '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file("file1", size=1024 * 80) self.create_regular_file("file2", size=1024 * 80) self.create_regular_file("file3", size=1024 * 80) self.create_regular_file("file4", size=1024 * 80) self.create_regular_file("file333", size=1024 * 80) - self.cmd("create", self.repository_location + "::test", "input") + self.cmd(f'--repo={self.repository_location}', 'create', "test", "input") # Extract everything with regular expression with changedir("output"): - self.cmd("extract", self.repository_location + "::test", "re:.*") + self.cmd(f'--repo={self.repository_location}', "extract", "test", "re:.*") self.assert_equal(sorted(os.listdir("output/input")), ["file1", "file2", "file3", "file333", "file4"]) shutil.rmtree("output/input") # Extract with pattern while also excluding files with changedir("output"): - self.cmd("extract", "--exclude=re:file[34]$", self.repository_location + "::test", r"re:file\d$") + self.cmd(f'--repo={self.repository_location}', "extract", "--exclude=re:file[34]$", "test", r"re:file\d$") self.assert_equal(sorted(os.listdir("output/input")), ["file1", "file2"]) shutil.rmtree("output/input") # Combine --exclude with pattern for extraction with changedir("output"): - self.cmd("extract", "--exclude=input/file1", self.repository_location + "::test", "re:file[12]$") + self.cmd(f'--repo={self.repository_location}', "extract", "--exclude=input/file1", "test", "re:file[12]$") self.assert_equal(sorted(os.listdir("output/input")), ["file2"]) shutil.rmtree("output/input") # Multiple pattern with changedir("output"): - self.cmd("extract", self.repository_location + "::test", "fm:input/file1", "fm:*file33*", "input/file2") + self.cmd(f'--repo={self.repository_location}', "extract", "test", "fm:input/file1", "fm:*file33*", "input/file2") self.assert_equal(sorted(os.listdir("output/input")), ["file1", "file2", "file333"]) def test_extract_list_output(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file', size=1024 * 80) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir('output'): - output = self.cmd('extract', self.repository_location + '::test') + output = self.cmd(f'--repo={self.repository_location}', 'extract', 'test') self.assert_not_in("input/file", output) shutil.rmtree('output/input') with changedir('output'): - output = self.cmd('extract', '--info', self.repository_location + '::test') + output = self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--info') self.assert_not_in("input/file", output) shutil.rmtree('output/input') with changedir('output'): - output = self.cmd('extract', '--list', self.repository_location + '::test') + output = self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--list') self.assert_in("input/file", output) shutil.rmtree('output/input') with changedir('output'): - output = self.cmd('extract', '--list', '--info', self.repository_location + '::test') + output = self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--list', '--info') self.assert_in("input/file", output) def test_extract_progress(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file', size=1024 * 80) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir('output'): - output = self.cmd('extract', self.repository_location + '::test', '--progress') + output = self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--progress') assert 'Extracting:' in output def _create_test_caches(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('cache1/%s' % CACHE_TAG_NAME, contents=CACHE_TAG_CONTENTS + b' extra stuff') @@ -1093,122 +1093,122 @@ class ArchiverTestCase(ArchiverTestCaseBase): contents=CACHE_TAG_CONTENTS + b' extra stuff') def test_create_stdin(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') input_data = b'\x00foo\n\nbar\n \n' - self.cmd('create', self.repository_location + '::test', '-', input=input_data) - item = json.loads(self.cmd('list', '--json-lines', self.repository_location + '::test')) + self.cmd(f'--repo={self.repository_location}', 'create', 'test', '-', input=input_data) + item = json.loads(self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--json-lines')) assert item['uid'] == 0 assert item['gid'] == 0 assert item['size'] == len(input_data) assert item['path'] == 'stdin' - extracted_data = self.cmd('extract', '--stdout', self.repository_location + '::test', binary_output=True) + extracted_data = self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--stdout', binary_output=True) assert extracted_data == input_data def test_create_content_from_command(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') input_data = 'some test content' name = 'a/b/c' - self.cmd('create', '--stdin-name', name, '--content-from-command', - self.repository_location + '::test', '--', 'echo', input_data) - item = json.loads(self.cmd('list', '--json-lines', self.repository_location + '::test')) + self.cmd(f'--repo={self.repository_location}', 'create', '--stdin-name', name, '--content-from-command', + 'test', '--', 'echo', input_data) + item = json.loads(self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--json-lines')) assert item['uid'] == 0 assert item['gid'] == 0 assert item['size'] == len(input_data) + 1 # `echo` adds newline assert item['path'] == name - extracted_data = self.cmd('extract', '--stdout', self.repository_location + '::test') + extracted_data = self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--stdout') assert extracted_data == input_data + '\n' def test_create_content_from_command_with_failed_command(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('create', '--content-from-command', self.repository_location + '::test', + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'create', '--content-from-command', 'test', '--', 'sh', '-c', 'exit 73;', exit_code=2) assert output.endswith("Command 'sh' exited with status 73\n") - archive_list = json.loads(self.cmd('list', '--json', self.repository_location)) + archive_list = json.loads(self.cmd(f'--repo={self.repository_location}', 'rlist', '--json')) assert archive_list['archives'] == [] def test_create_content_from_command_missing_command(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('create', '--content-from-command', self.repository_location + '::test', exit_code=2) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'create', 'test', '--content-from-command', exit_code=2) assert output.endswith('No command given.\n') def test_create_paths_from_stdin(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file("file1", size=1024 * 80) self.create_regular_file("dir1/file2", size=1024 * 80) self.create_regular_file("dir1/file3", size=1024 * 80) self.create_regular_file("file4", size=1024 * 80) input_data = b'input/file1\0input/dir1\0input/file4' - self.cmd('create', '--paths-from-stdin', '--paths-delimiter', '\\0', - self.repository_location + '::test', input=input_data) - archive_list = self.cmd('list', '--json-lines', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', + '--paths-from-stdin', '--paths-delimiter', '\\0', input=input_data) + archive_list = self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--json-lines') paths = [json.loads(line)['path'] for line in archive_list.split('\n') if line] assert paths == ['input/file1', 'input/dir1', 'input/file4'] def test_create_paths_from_command(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file("file1", size=1024 * 80) self.create_regular_file("file2", size=1024 * 80) self.create_regular_file("file3", size=1024 * 80) self.create_regular_file("file4", size=1024 * 80) input_data = 'input/file1\ninput/file2\ninput/file3' - self.cmd('create', '--paths-from-command', - self.repository_location + '::test', '--', 'echo', input_data) - archive_list = self.cmd('list', '--json-lines', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'create', '--paths-from-command', 'test', + '--', 'echo', input_data) + archive_list = self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--json-lines') paths = [json.loads(line)['path'] for line in archive_list.split('\n') if line] assert paths == ['input/file1', 'input/file2', 'input/file3'] def test_create_paths_from_command_with_failed_command(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('create', '--paths-from-command', self.repository_location + '::test', + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'create', '--paths-from-command', 'test', '--', 'sh', '-c', 'exit 73;', exit_code=2) assert output.endswith("Command 'sh' exited with status 73\n") - archive_list = json.loads(self.cmd('list', '--json', self.repository_location)) + archive_list = json.loads(self.cmd(f'--repo={self.repository_location}', 'rlist', '--json')) assert archive_list['archives'] == [] def test_create_paths_from_command_missing_command(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('create', '--paths-from-command', self.repository_location + '::test', exit_code=2) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'create', 'test', '--paths-from-command', exit_code=2) assert output.endswith('No command given.\n') def test_create_without_root(self): """test create without a root""" - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', exit_code=2) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', exit_code=2) def test_create_pattern_root(self): """test create with only a root pattern""" - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('file2', size=1024 * 80) - output = self.cmd('create', '-v', '--list', '--pattern=R input', self.repository_location + '::test') + output = self.cmd(f'--repo={self.repository_location}', 'create', 'test', '-v', '--list', '--pattern=R input') self.assert_in("A input/file1", output) self.assert_in("A input/file2", output) def test_create_pattern(self): """test file patterns during create""" - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('file2', size=1024 * 80) self.create_regular_file('file_important', size=1024 * 80) - output = self.cmd('create', '-v', '--list', + output = self.cmd(f'--repo={self.repository_location}', 'create', '-v', '--list', '--pattern=+input/file_important', '--pattern=-input/file*', - self.repository_location + '::test', 'input') + 'test', 'input') self.assert_in("A input/file_important", output) self.assert_in('x input/file1', output) self.assert_in('x input/file2', output) def test_create_pattern_file(self): """test file patterns during create""" - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('file2', size=1024 * 80) self.create_regular_file('otherfile', size=1024 * 80) self.create_regular_file('file_important', size=1024 * 80) - output = self.cmd('create', '-v', '--list', + output = self.cmd(f'--repo={self.repository_location}', 'create', '-v', '--list', '--pattern=-input/otherfile', '--patterns-from=' + self.patterns_file_path, - self.repository_location + '::test', 'input') + 'test', 'input') self.assert_in("A input/file_important", output) self.assert_in('x input/file1', output) self.assert_in('x input/file2', output) @@ -1220,13 +1220,13 @@ class ArchiverTestCase(ArchiverTestCaseBase): with open(self.patterns_file_path2, 'wb') as fd: fd.write(b'+ input/x/b\n- input/x*\n') - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('x/a/foo_a', size=1024 * 80) self.create_regular_file('x/b/foo_b', size=1024 * 80) self.create_regular_file('y/foo_y', size=1024 * 80) - output = self.cmd('create', '-v', '--list', + output = self.cmd(f'--repo={self.repository_location}', 'create', '-v', '--list', '--patterns-from=' + self.patterns_file_path2, - self.repository_location + '::test', 'input') + 'test', 'input') self.assert_in('x input/x/a/foo_a', output) self.assert_in("A input/x/b/foo_b", output) self.assert_in('A input/y/foo_y', output) @@ -1237,13 +1237,13 @@ class ArchiverTestCase(ArchiverTestCaseBase): with open(self.patterns_file_path2, 'wb') as fd: fd.write(b'+ input/x/b\n! input/x*\n') - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('x/a/foo_a', size=1024 * 80) self.create_regular_file('x/b/foo_b', size=1024 * 80) self.create_regular_file('y/foo_y', size=1024 * 80) - output = self.cmd('create', '-v', '--list', + output = self.cmd(f'--repo={self.repository_location}', 'create', '-v', '--list', '--patterns-from=' + self.patterns_file_path2, - self.repository_location + '::test', 'input') + 'test', 'input') self.assert_not_in('input/x/a/foo_a', output) self.assert_not_in('input/x/a', output) self.assert_in('A input/y/foo_y', output) @@ -1254,17 +1254,17 @@ class ArchiverTestCase(ArchiverTestCaseBase): with open(self.patterns_file_path2, 'wb') as fd: fd.write(b'+ input/x/a\n+ input/x/b\n- input/x*\n') - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('x/a/foo_a', size=1024 * 80) self.create_regular_file('x/b/foo_b', size=1024 * 80) with changedir('input'): - self.cmd('create', '--patterns-from=' + self.patterns_file_path2, - self.repository_location + '::test', '.') + self.cmd(f'--repo={self.repository_location}', 'create', '--patterns-from=' + self.patterns_file_path2, + 'test', '.') # list the archive and verify that the "intermediate" folders appear before # their contents - out = self.cmd('list', '--format', '{type} {path}{NL}', self.repository_location + '::test') + out = self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--format', '{type} {path}{NL}') out_list = out.splitlines() self.assert_in('d x/a', out_list) @@ -1275,50 +1275,50 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_create_no_cache_sync(self): self.create_test_files() - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('delete', '--cache-only', self.repository_location) - create_json = json.loads(self.cmd('create', '--no-cache-sync', self.repository_location + '::test', 'input', - '--json', '--error')) # ignore experimental warning - info_json = json.loads(self.cmd('info', self.repository_location + '::test', '--json')) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'rdelete', '--cache-only') + create_json = json.loads(self.cmd(f'--repo={self.repository_location}', 'create', + '--no-cache-sync', '--json', '--error', + 'test', 'input')) # ignore experimental warning + info_json = json.loads(self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test', '--json')) create_stats = create_json['cache']['stats'] info_stats = info_json['cache']['stats'] assert create_stats == info_stats - self.cmd('delete', '--cache-only', self.repository_location) - self.cmd('create', '--no-cache-sync', self.repository_location + '::test2', 'input') - self.cmd('info', self.repository_location) - self.cmd('check', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rdelete', '--cache-only') + self.cmd(f'--repo={self.repository_location}', 'create', '--no-cache-sync', 'test2', 'input') + self.cmd(f'--repo={self.repository_location}', 'rinfo') + self.cmd(f'--repo={self.repository_location}', 'check') def test_extract_pattern_opt(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('file2', size=1024 * 80) self.create_regular_file('file_important', size=1024 * 80) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir('output'): - self.cmd('extract', - '--pattern=+input/file_important', '--pattern=-input/file*', - self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', + '--pattern=+input/file_important', '--pattern=-input/file*') self.assert_equal(sorted(os.listdir('output/input')), ['file_important']) def _assert_test_caches(self): with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') self.assert_equal(sorted(os.listdir('output/input')), ['cache2', 'file1']) self.assert_equal(sorted(os.listdir('output/input/cache2')), [CACHE_TAG_NAME]) def test_exclude_caches(self): self._create_test_caches() - self.cmd('create', '--exclude-caches', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input', '--exclude-caches') self._assert_test_caches() def test_recreate_exclude_caches(self): self._create_test_caches() - self.cmd('create', self.repository_location + '::test', 'input') - self.cmd('recreate', '--exclude-caches', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + self.cmd(f'--repo={self.repository_location}', 'recreate', '-a', 'test', '--exclude-caches') self._assert_test_caches() def _create_test_tagged(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('tagged1/.NOBACKUP') self.create_regular_file('tagged2/00-NOBACKUP') @@ -1326,23 +1326,24 @@ class ArchiverTestCase(ArchiverTestCaseBase): def _assert_test_tagged(self): with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') self.assert_equal(sorted(os.listdir('output/input')), ['file1']) def test_exclude_tagged(self): self._create_test_tagged() - self.cmd('create', '--exclude-if-present', '.NOBACKUP', '--exclude-if-present', '00-NOBACKUP', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input', + '--exclude-if-present', '.NOBACKUP', '--exclude-if-present', '00-NOBACKUP') self._assert_test_tagged() def test_recreate_exclude_tagged(self): self._create_test_tagged() - self.cmd('create', self.repository_location + '::test', 'input') - self.cmd('recreate', '--exclude-if-present', '.NOBACKUP', '--exclude-if-present', '00-NOBACKUP', - self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + self.cmd(f'--repo={self.repository_location}', 'recreate', '-a', 'test', '--exclude-if-present', '.NOBACKUP', + '--exclude-if-present', '00-NOBACKUP') self._assert_test_tagged() def _create_test_keep_tagged(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file0', size=1024) self.create_regular_file('tagged1/.NOBACKUP1') self.create_regular_file('tagged1/file1', size=1024) @@ -1359,7 +1360,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): def _assert_test_keep_tagged(self): with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') self.assert_equal(sorted(os.listdir('output/input')), ['file0', 'tagged1', 'tagged2', 'tagged3', 'taggedall']) self.assert_equal(os.listdir('output/input/tagged1'), ['.NOBACKUP1']) self.assert_equal(os.listdir('output/input/tagged2'), ['.NOBACKUP2']) @@ -1369,29 +1370,29 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_exclude_keep_tagged(self): self._create_test_keep_tagged() - self.cmd('create', '--exclude-if-present', '.NOBACKUP1', '--exclude-if-present', '.NOBACKUP2', - '--exclude-caches', '--keep-exclude-tags', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input', '--exclude-if-present', '.NOBACKUP1', + '--exclude-if-present', '.NOBACKUP2', '--exclude-caches', '--keep-exclude-tags') self._assert_test_keep_tagged() def test_recreate_exclude_keep_tagged(self): self._create_test_keep_tagged() - self.cmd('create', self.repository_location + '::test', 'input') - self.cmd('recreate', '--exclude-if-present', '.NOBACKUP1', '--exclude-if-present', '.NOBACKUP2', - '--exclude-caches', '--keep-exclude-tags', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + self.cmd(f'--repo={self.repository_location}', 'recreate', '-a', 'test', '--exclude-if-present', '.NOBACKUP1', + '--exclude-if-present', '.NOBACKUP2', '--exclude-caches', '--keep-exclude-tags') self._assert_test_keep_tagged() @pytest.mark.skipif(not are_hardlinks_supported(), reason='hardlinks not supported') def test_recreate_hardlinked_tags(self): # test for issue #4911 - self.cmd('init', '--encryption=none', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=none') self.create_regular_file('file1', contents=CACHE_TAG_CONTENTS) # "wrong" filename, but correct tag contents os.mkdir(os.path.join(self.input_path, 'subdir')) # to make sure the tag is encountered *after* file1 os.link(os.path.join(self.input_path, 'file1'), os.path.join(self.input_path, 'subdir', CACHE_TAG_NAME)) # correct tag name, hardlink to file1 - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') # in the "test" archive, we now have, in this order: # - a regular file item for "file1" # - a hardlink item for "CACHEDIR.TAG" referring back to file1 for its contents - self.cmd('recreate', '--exclude-caches', '--keep-exclude-tags', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'recreate', 'test', '--exclude-caches', '--keep-exclude-tags') # if issue #4911 is present, the recreate will crash with a KeyError for "input/file1" @pytest.mark.skipif(not xattr.XATTR_FAKEROOT, reason='Linux capabilities test, requires fakeroot >= 1.20.2') @@ -1408,11 +1409,11 @@ class ArchiverTestCase(ArchiverTestCaseBase): capabilities = b'\x01\x00\x00\x02\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' self.create_regular_file('file') xattr.setxattr(b'input/file', b'security.capability', capabilities) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir('output'): with patch.object(os, 'fchown', patched_fchown): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') assert xattr.getxattr(b'input/file', b'security.capability') == capabilities @pytest.mark.skipif(not xattr.XATTR_FAKEROOT, reason='xattr not supported on this system or on this version of' @@ -1429,88 +1430,89 @@ class ArchiverTestCase(ArchiverTestCaseBase): self.create_regular_file('file') xattr.setxattr(b'input/file', b'user.attribute', b'value') - self.cmd('init', self.repository_location, '-e' 'none') - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '-e' 'none') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir('output'): input_abspath = os.path.abspath('input/file') with patch.object(xattr, 'setxattr', patched_setxattr_E2BIG): - out = self.cmd('extract', self.repository_location + '::test', exit_code=EXIT_WARNING) + out = self.cmd(f'--repo={self.repository_location}', 'extract', 'test', exit_code=EXIT_WARNING) assert ': when setting extended attribute user.attribute: too big for this filesystem\n' in out os.remove(input_abspath) with patch.object(xattr, 'setxattr', patched_setxattr_ENOTSUP): - out = self.cmd('extract', self.repository_location + '::test', exit_code=EXIT_WARNING) + out = self.cmd(f'--repo={self.repository_location}', 'extract', 'test', exit_code=EXIT_WARNING) assert ': when setting extended attribute user.attribute: xattrs not supported on this filesystem\n' in out os.remove(input_abspath) with patch.object(xattr, 'setxattr', patched_setxattr_EACCES): - out = self.cmd('extract', self.repository_location + '::test', exit_code=EXIT_WARNING) + out = self.cmd(f'--repo={self.repository_location}', 'extract', 'test', exit_code=EXIT_WARNING) assert ': when setting extended attribute user.attribute: Permission denied\n' in out assert os.path.isfile(input_abspath) def test_path_normalization(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('dir1/dir2/file', size=1024 * 80) with changedir('input/dir1/dir2'): - self.cmd('create', self.repository_location + '::test', '../../../input/dir1/../dir1/dir2/..') - output = self.cmd('list', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', + '../../../input/dir1/../dir1/dir2/..') + output = self.cmd(f'--repo={self.repository_location}', 'list', 'test') self.assert_not_in('..', output) self.assert_in(' input/dir1/dir2/file', output) def test_exclude_normalization(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('file2', size=1024 * 80) with changedir('input'): - self.cmd('create', '--exclude=file1', self.repository_location + '::test1', '.') + self.cmd(f'--repo={self.repository_location}', 'create', 'test1', '.', '--exclude=file1') with changedir('output'): - self.cmd('extract', self.repository_location + '::test1') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test1') self.assert_equal(sorted(os.listdir('output')), ['file2']) with changedir('input'): - self.cmd('create', '--exclude=./file1', self.repository_location + '::test2', '.') + self.cmd(f'--repo={self.repository_location}', 'create', 'test2', '.', '--exclude=./file1') with changedir('output'): - self.cmd('extract', self.repository_location + '::test2') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test2') self.assert_equal(sorted(os.listdir('output')), ['file2']) - self.cmd('create', '--exclude=input/./file1', self.repository_location + '::test3', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test3', 'input', '--exclude=input/./file1') with changedir('output'): - self.cmd('extract', self.repository_location + '::test3') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test3') self.assert_equal(sorted(os.listdir('output/input')), ['file2']) def test_repeated_files(self): self.create_regular_file('file1', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input', 'input') def test_overwrite(self): self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('dir2/file2', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') # Overwriting regular files and directories should be supported os.mkdir('output/input') os.mkdir('output/input/file1') os.mkdir('output/input/dir2') with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') self.assert_dirs_equal('input', 'output/input') # But non-empty dirs should fail os.unlink('output/input/file1') os.mkdir('output/input/file1') os.mkdir('output/input/file1/dir') with changedir('output'): - self.cmd('extract', self.repository_location + '::test', exit_code=1) + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', exit_code=1) def test_rename(self): self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('dir2/file2', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') - self.cmd('create', self.repository_location + '::test.2', 'input') - self.cmd('extract', '--dry-run', self.repository_location + '::test') - self.cmd('extract', '--dry-run', self.repository_location + '::test.2') - self.cmd('rename', self.repository_location + '::test', 'test.3') - self.cmd('extract', '--dry-run', self.repository_location + '::test.2') - self.cmd('rename', self.repository_location + '::test.2', 'test.4') - self.cmd('extract', '--dry-run', self.repository_location + '::test.3') - self.cmd('extract', '--dry-run', self.repository_location + '::test.4') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test.2', 'input') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--dry-run') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test.2', '--dry-run') + self.cmd(f'--repo={self.repository_location}', 'rename', 'test', 'test.3') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test.2', '--dry-run') + self.cmd(f'--repo={self.repository_location}', 'rename', 'test.2', 'test.4') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test.3', '--dry-run') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test.4', '--dry-run') # Make sure both archives have been renamed with Repository(self.repository_path) as repository: manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK) @@ -1520,20 +1522,20 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_info(self): self.create_regular_file('file1', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') - info_repo = self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + info_repo = self.cmd(f'--repo={self.repository_location}', 'rinfo') assert 'All archives:' in info_repo - info_archive = self.cmd('info', self.repository_location + '::test') + info_archive = self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test') assert 'Archive name: test\n' in info_archive - info_archive = self.cmd('info', '--first', '1', self.repository_location) + info_archive = self.cmd(f'--repo={self.repository_location}', 'info', '--first', '1') assert 'Archive name: test\n' in info_archive def test_info_json(self): self.create_regular_file('file1', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') - info_repo = json.loads(self.cmd('info', '--json', self.repository_location)) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + info_repo = json.loads(self.cmd(f'--repo={self.repository_location}', 'rinfo', '--json')) repository = info_repo['repository'] assert len(repository['id']) == 64 assert 'last_modified' in repository @@ -1545,7 +1547,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): assert all(isinstance(o, int) for o in stats.values()) assert all(key in stats for key in ('total_chunks', 'total_size', 'total_unique_chunks', 'unique_size')) - info_archive = json.loads(self.cmd('info', '--json', self.repository_location + '::test')) + info_archive = json.loads(self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test', '--json')) assert info_repo['repository'] == info_archive['repository'] assert info_repo['cache'] == info_archive['cache'] archives = info_archive['archives'] @@ -1561,47 +1563,47 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_info_json_of_empty_archive(self): """See https://github.com/borgbackup/borg/issues/6120""" - self.cmd('init', '--encryption=repokey', self.repository_location) - info_repo = json.loads(self.cmd('info', '--json', '--first=1', self.repository_location)) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + info_repo = json.loads(self.cmd(f'--repo={self.repository_location}', 'info', '--json', '--first=1')) assert info_repo["archives"] == [] - info_repo = json.loads(self.cmd('info', '--json', '--last=1', self.repository_location)) + info_repo = json.loads(self.cmd(f'--repo={self.repository_location}', 'info', '--json', '--last=1')) assert info_repo["archives"] == [] def test_comment(self): self.create_regular_file('file1', size=1024 * 80) - self.cmd('init', '--encryption=repokey', 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(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test1', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test2', 'input', '--comment', 'this is the comment') + self.cmd(f'--repo={self.repository_location}', 'create', 'test3', 'input', '--comment', '"deleted" comment') + self.cmd(f'--repo={self.repository_location}', 'create', 'test4', 'input', '--comment', 'preserved comment') + assert 'Comment: \n' in self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test1') + assert 'Comment: this is the comment' in self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test2') - 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') + self.cmd(f'--repo={self.repository_location}', 'recreate', '-a', 'test1', '--comment', 'added comment') + self.cmd(f'--repo={self.repository_location}', 'recreate', '-a', 'test2', '--comment', 'modified comment') + self.cmd(f'--repo={self.repository_location}', 'recreate', '-a', 'test3', '--comment', '') + self.cmd(f'--repo={self.repository_location}', 'recreate', '-a', 'test4', '12345') + assert 'Comment: added comment' in self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test1') + assert 'Comment: modified comment' in self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test2') + assert 'Comment: \n' in self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test3') + assert 'Comment: preserved comment' in self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test4') def test_delete(self): self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('dir2/file2', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') - self.cmd('create', self.repository_location + '::test.2', 'input') - self.cmd('create', self.repository_location + '::test.3', 'input') - self.cmd('create', self.repository_location + '::another_test.1', 'input') - self.cmd('create', self.repository_location + '::another_test.2', 'input') - self.cmd('extract', '--dry-run', self.repository_location + '::test') - self.cmd('extract', '--dry-run', self.repository_location + '::test.2') - self.cmd('delete', '--prefix', 'another_', self.repository_location) - self.cmd('delete', '--last', '1', self.repository_location) - self.cmd('delete', self.repository_location + '::test') - self.cmd('extract', '--dry-run', self.repository_location + '::test.2') - output = self.cmd('delete', '--stats', self.repository_location + '::test.2') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test.2', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test.3', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'another_test.1', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'another_test.2', 'input') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--dry-run') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test.2', '--dry-run') + self.cmd(f'--repo={self.repository_location}', 'delete', '--prefix', 'another_') + self.cmd(f'--repo={self.repository_location}', 'delete', '--last', '1') + self.cmd(f'--repo={self.repository_location}', 'delete', '-a', 'test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test.2', '--dry-run') + output = self.cmd(f'--repo={self.repository_location}', 'delete', '-a', 'test.2', '--stats') self.assert_in('Deleted data:', output) # Make sure all data except the manifest has been deleted with Repository(self.repository_path) as repository: @@ -1609,31 +1611,32 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_delete_multiple(self): self.create_regular_file('file1', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test1', 'input') - self.cmd('create', self.repository_location + '::test2', 'input') - self.cmd('create', self.repository_location + '::test3', 'input') - self.cmd('delete', self.repository_location + '::test1', 'test2') - self.cmd('extract', '--dry-run', self.repository_location + '::test3') - self.cmd('delete', self.repository_location, 'test3') - assert not self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test1', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test2', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test3', 'input') + self.cmd(f'--repo={self.repository_location}', 'delete', '-a', 'test1') + self.cmd(f'--repo={self.repository_location}', 'delete', '-a', 'test2') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test3', '--dry-run') + self.cmd(f'--repo={self.repository_location}', 'delete', '-a', 'test3') + assert not self.cmd(f'--repo={self.repository_location}', 'rlist') def test_delete_repo(self): self.create_regular_file('file1', size=1024 * 80) self.create_regular_file('dir2/file2', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') - self.cmd('create', self.repository_location + '::test.2', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test.2', 'input') os.environ['BORG_DELETE_I_KNOW_WHAT_I_AM_DOING'] = 'no' - self.cmd('delete', self.repository_location, exit_code=2) + self.cmd(f'--repo={self.repository_location}', 'rdelete', exit_code=2) assert os.path.exists(self.repository_path) os.environ['BORG_DELETE_I_KNOW_WHAT_I_AM_DOING'] = 'YES' - self.cmd('delete', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rdelete') # Make sure the repo is gone self.assertFalse(os.path.exists(self.repository_path)) def test_delete_force(self): - self.cmd('init', '--encryption=none', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=none') self.create_src_archive('test') with Repository(self.repository_path, exclusive=True) as repository: manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK) @@ -1645,14 +1648,14 @@ class ArchiverTestCase(ArchiverTestCaseBase): else: assert False # missed the file repository.commit(compact=False) - output = self.cmd('delete', '--force', self.repository_location + '::test') + output = self.cmd(f'--repo={self.repository_location}', 'delete', '-a', 'test', '--force') self.assert_in('deleted archive was corrupted', output) - self.cmd('check', '--repair', self.repository_location) - output = self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'check', '--repair') + output = self.cmd(f'--repo={self.repository_location}', 'rlist') self.assert_not_in('test', output) def test_delete_double_force(self): - self.cmd('init', '--encryption=none', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=none') self.create_src_archive('test') with Repository(self.repository_path, exclusive=True) as repository: manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK) @@ -1660,16 +1663,16 @@ class ArchiverTestCase(ArchiverTestCaseBase): id = archive.metadata.items[0] repository.put(id, b'corrupted items metadata stream chunk') repository.commit(compact=False) - self.cmd('delete', '--force', '--force', self.repository_location + '::test') - self.cmd('check', '--repair', self.repository_location) - output = self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'delete', '-a', 'test', '--force', '--force') + self.cmd(f'--repo={self.repository_location}', 'check', '--repair') + output = self.cmd(f'--repo={self.repository_location}', 'rlist') self.assert_not_in('test', output) def test_corrupted_repository(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('test') - self.cmd('extract', '--dry-run', self.repository_location + '::test') - output = self.cmd('check', '--show-version', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--dry-run') + output = self.cmd(f'--repo={self.repository_location}', 'check', '--show-version') self.assert_in('borgbackup version', output) # implied output even without --info given self.assert_not_in('Starting repository check', output) # --info not given for root logger @@ -1677,103 +1680,103 @@ class ArchiverTestCase(ArchiverTestCaseBase): with open(os.path.join(self.tmpdir, 'repository', 'data', '0', name), 'r+b') as fd: fd.seek(100) fd.write(b'XXXX') - output = self.cmd('check', '--info', self.repository_location, exit_code=1) + output = self.cmd(f'--repo={self.repository_location}', 'check', '--info', exit_code=1) self.assert_in('Starting repository check', output) # --info given for root logger def test_readonly_check(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('test') with self.read_only(self.repository_path): # verify that command normally doesn't work with read-only repo if self.FORK_DEFAULT: - self.cmd('check', '--verify-data', self.repository_location, exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}', 'check', '--verify-data', exit_code=EXIT_ERROR) else: with pytest.raises((LockFailed, RemoteRepository.RPCError)) as excinfo: - self.cmd('check', '--verify-data', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'check', '--verify-data') if isinstance(excinfo.value, RemoteRepository.RPCError): assert excinfo.value.exception_class == 'LockFailed' # verify that command works with read-only repo when using --bypass-lock - self.cmd('check', '--verify-data', self.repository_location, '--bypass-lock') + self.cmd(f'--repo={self.repository_location}', 'check', '--verify-data', '--bypass-lock') def test_readonly_diff(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('a') self.create_src_archive('b') with self.read_only(self.repository_path): # verify that command normally doesn't work with read-only repo if self.FORK_DEFAULT: - self.cmd('diff', '%s::a' % self.repository_location, 'b', exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}', 'diff', 'a', 'b', exit_code=EXIT_ERROR) else: with pytest.raises((LockFailed, RemoteRepository.RPCError)) as excinfo: - self.cmd('diff', '%s::a' % self.repository_location, 'b') + self.cmd(f'--repo={self.repository_location}', 'diff', 'a', 'b') if isinstance(excinfo.value, RemoteRepository.RPCError): assert excinfo.value.exception_class == 'LockFailed' # verify that command works with read-only repo when using --bypass-lock - self.cmd('diff', '%s::a' % self.repository_location, 'b', '--bypass-lock') + self.cmd(f'--repo={self.repository_location}', 'diff', 'a', 'b', '--bypass-lock') def test_readonly_export_tar(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('test') with self.read_only(self.repository_path): # verify that command normally doesn't work with read-only repo if self.FORK_DEFAULT: - self.cmd('export-tar', '%s::test' % self.repository_location, 'test.tar', exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}', 'export-tar', 'test', 'test.tar', exit_code=EXIT_ERROR) else: with pytest.raises((LockFailed, RemoteRepository.RPCError)) as excinfo: - self.cmd('export-tar', '%s::test' % self.repository_location, 'test.tar') + self.cmd(f'--repo={self.repository_location}', 'export-tar', 'test', 'test.tar') if isinstance(excinfo.value, RemoteRepository.RPCError): assert excinfo.value.exception_class == 'LockFailed' # verify that command works with read-only repo when using --bypass-lock - self.cmd('export-tar', '%s::test' % self.repository_location, 'test.tar', '--bypass-lock') + self.cmd(f'--repo={self.repository_location}', 'export-tar', 'test', 'test.tar', '--bypass-lock') def test_readonly_extract(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('test') with self.read_only(self.repository_path): # verify that command normally doesn't work with read-only repo if self.FORK_DEFAULT: - self.cmd('extract', '%s::test' % self.repository_location, exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', exit_code=EXIT_ERROR) else: with pytest.raises((LockFailed, RemoteRepository.RPCError)) as excinfo: - self.cmd('extract', '%s::test' % self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') if isinstance(excinfo.value, RemoteRepository.RPCError): assert excinfo.value.exception_class == 'LockFailed' # verify that command works with read-only repo when using --bypass-lock - self.cmd('extract', '%s::test' % self.repository_location, '--bypass-lock') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', '--bypass-lock') def test_readonly_info(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('test') with self.read_only(self.repository_path): # verify that command normally doesn't work with read-only repo if self.FORK_DEFAULT: - self.cmd('info', self.repository_location, exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}', 'rinfo', exit_code=EXIT_ERROR) else: with pytest.raises((LockFailed, RemoteRepository.RPCError)) as excinfo: - self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rinfo') if isinstance(excinfo.value, RemoteRepository.RPCError): assert excinfo.value.exception_class == 'LockFailed' # verify that command works with read-only repo when using --bypass-lock - self.cmd('info', self.repository_location, '--bypass-lock') + self.cmd(f'--repo={self.repository_location}', 'rinfo', '--bypass-lock') def test_readonly_list(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('test') with self.read_only(self.repository_path): # verify that command normally doesn't work with read-only repo if self.FORK_DEFAULT: - self.cmd('list', self.repository_location, exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}', 'rlist', exit_code=EXIT_ERROR) else: with pytest.raises((LockFailed, RemoteRepository.RPCError)) as excinfo: - self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rlist') if isinstance(excinfo.value, RemoteRepository.RPCError): assert excinfo.value.exception_class == 'LockFailed' # verify that command works with read-only repo when using --bypass-lock - self.cmd('list', self.repository_location, '--bypass-lock') + self.cmd(f'--repo={self.repository_location}', 'rlist', '--bypass-lock') @unittest.skipUnless(llfuse, 'llfuse not installed') def test_readonly_mount(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('test') with self.read_only(self.repository_path): # verify that command normally doesn't work with read-only repo @@ -1794,14 +1797,14 @@ class ArchiverTestCase(ArchiverTestCaseBase): @pytest.mark.skipif('BORG_TESTS_IGNORE_MODES' in os.environ, reason='modes unreliable') def test_umask(self): self.create_regular_file('file1', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') mode = os.stat(self.repository_path).st_mode self.assertEqual(stat.S_IMODE(mode), 0o700) def test_create_dry_run(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', '--dry-run', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', '--dry-run', 'test', 'input') # Make sure no archive has been created with Repository(self.repository_path) as repository: manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK) @@ -1823,56 +1826,56 @@ class ArchiverTestCase(ArchiverTestCaseBase): assert excinfo.value.args == (['unknown-feature'],) def test_unknown_feature_on_create(self): - print(self.cmd('init', '--encryption=repokey', self.repository_location)) + print(self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey')) self.add_unknown_feature(Manifest.Operation.WRITE) - self.cmd_raises_unknown_feature(['create', self.repository_location + '::test', 'input']) + self.cmd_raises_unknown_feature([f'--repo={self.repository_location}', 'create', 'test', 'input']) def test_unknown_feature_on_cache_sync(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('delete', '--cache-only', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'rdelete', '--cache-only') self.add_unknown_feature(Manifest.Operation.READ) - self.cmd_raises_unknown_feature(['create', self.repository_location + '::test', 'input']) + self.cmd_raises_unknown_feature([f'--repo={self.repository_location}', 'create', 'test', 'input']) def test_unknown_feature_on_change_passphrase(self): - print(self.cmd('init', '--encryption=repokey', self.repository_location)) + print(self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey')) self.add_unknown_feature(Manifest.Operation.CHECK) - self.cmd_raises_unknown_feature(['key', 'change-passphrase', self.repository_location]) + self.cmd_raises_unknown_feature([f'--repo={self.repository_location}', 'key', 'change-passphrase']) def test_unknown_feature_on_read(self): - print(self.cmd('init', '--encryption=repokey', self.repository_location)) - self.cmd('create', self.repository_location + '::test', 'input') + print(self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey')) + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') self.add_unknown_feature(Manifest.Operation.READ) with changedir('output'): - self.cmd_raises_unknown_feature(['extract', self.repository_location + '::test']) + self.cmd_raises_unknown_feature([f'--repo={self.repository_location}', 'extract', 'test']) - self.cmd_raises_unknown_feature(['list', self.repository_location]) - self.cmd_raises_unknown_feature(['info', self.repository_location + '::test']) + self.cmd_raises_unknown_feature([f'--repo={self.repository_location}', 'rlist']) + self.cmd_raises_unknown_feature([f'--repo={self.repository_location}', 'info', '-a', 'test']) def test_unknown_feature_on_rename(self): - print(self.cmd('init', '--encryption=repokey', self.repository_location)) - self.cmd('create', self.repository_location + '::test', 'input') + print(self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey')) + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') self.add_unknown_feature(Manifest.Operation.CHECK) - self.cmd_raises_unknown_feature(['rename', self.repository_location + '::test', 'other']) + self.cmd_raises_unknown_feature([f'--repo={self.repository_location}', 'rename', 'test', 'other']) def test_unknown_feature_on_delete(self): - print(self.cmd('init', '--encryption=repokey', self.repository_location)) - self.cmd('create', self.repository_location + '::test', 'input') + print(self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey')) + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') self.add_unknown_feature(Manifest.Operation.DELETE) # delete of an archive raises - self.cmd_raises_unknown_feature(['delete', self.repository_location + '::test']) - self.cmd_raises_unknown_feature(['prune', '--keep-daily=3', self.repository_location]) + self.cmd_raises_unknown_feature([f'--repo={self.repository_location}', 'delete', '-a', 'test']) + self.cmd_raises_unknown_feature([f'--repo={self.repository_location}', 'prune', '--keep-daily=3']) # delete of the whole repository ignores features - self.cmd('delete', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rdelete') @unittest.skipUnless(llfuse, 'llfuse not installed') def test_unknown_feature_on_mount(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') self.add_unknown_feature(Manifest.Operation.READ) mountpoint = os.path.join(self.tmpdir, 'mountpoint') os.mkdir(mountpoint) # XXX this might hang if it doesn't raise an error - self.cmd_raises_unknown_feature(['mount', self.repository_location + '::test', mountpoint]) + self.cmd_raises_unknown_feature([f'--repo={self.repository_location}::test', 'mount', mountpoint]) @pytest.mark.allow_cache_wipe def test_unknown_mandatory_feature_in_cache(self): @@ -1881,7 +1884,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): else: path_prefix = '' - print(self.cmd('init', '--encryption=repokey', self.repository_location)) + print(self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey')) with Repository(self.repository_path, exclusive=True) as repository: if path_prefix: @@ -1893,7 +1896,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): cache.commit() if self.FORK_DEFAULT: - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') else: called = False wipe_cache_safe = LocalCache.wipe_cache @@ -1904,7 +1907,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): wipe_cache_safe(*args) with patch.object(LocalCache, 'wipe_cache', wipe_wrapper): - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') assert called @@ -1917,14 +1920,14 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_progress_on(self): self.create_regular_file('file1', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('create', '--progress', self.repository_location + '::test4', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'create', 'test4', 'input', '--progress') self.assert_in("\r", output) def test_progress_off(self): self.create_regular_file('file1', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('create', self.repository_location + '::test5', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'create', 'test5', 'input') self.assert_not_in("\r", output) def test_file_status(self): @@ -1934,12 +1937,12 @@ class ArchiverTestCase(ArchiverTestCaseBase): self.create_regular_file('file1', size=1024 * 80) time.sleep(1) # file2 must have newer timestamps than file1 self.create_regular_file('file2', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('create', '--list', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'create', '--list', 'test', 'input') self.assert_in("A input/file1", output) self.assert_in("A input/file2", output) # should find first file as unmodified - output = self.cmd('create', '--list', self.repository_location + '::test1', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'create', '--list', 'test2', 'input') self.assert_in("U input/file1", output) # this is expected, although surprising, for why, see: # https://borgbackup.readthedocs.org/en/latest/faq.html#i-am-seeing-a-added-status-for-a-unchanged-file @@ -1950,14 +1953,16 @@ class ArchiverTestCase(ArchiverTestCaseBase): self.create_regular_file('file1', contents=b'123') time.sleep(1) # file2 must have newer timestamps than file1 self.create_regular_file('file2', size=10) - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('create', '--list', '--files-cache=ctime,size', self.repository_location + '::test1', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'create', 'test1', 'input', + '--list', '--files-cache=ctime,size') # modify file1, but cheat with the mtime (and atime) and also keep same size: st = os.stat('input/file1') self.create_regular_file('file1', contents=b'321') os.utime('input/file1', ns=(st.st_atime_ns, st.st_mtime_ns)) # this mode uses ctime for change detection, so it should find file1 as modified - output = self.cmd('create', '--list', '--files-cache=ctime,size', self.repository_location + '::test2', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'create', 'test2', 'input', + '--list', '--files-cache=ctime,size') self.assert_in("M input/file1", output) def test_file_status_ms_cache_mode(self): @@ -1965,13 +1970,15 @@ class ArchiverTestCase(ArchiverTestCaseBase): self.create_regular_file('file1', size=10) time.sleep(1) # file2 must have newer timestamps than file1 self.create_regular_file('file2', size=10) - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('create', '--list', '--files-cache=mtime,size', self.repository_location + '::test1', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'create', + '--list', '--files-cache=mtime,size', 'test1', 'input') # change mode of file1, no content change: st = os.stat('input/file1') os.chmod('input/file1', st.st_mode ^ stat.S_IRWXO) # this triggers a ctime change, but mtime is unchanged # this mode uses mtime for change detection, so it should find file1 as unmodified - output = self.cmd('create', '--list', '--files-cache=mtime,size', self.repository_location + '::test2', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'create', + '--list', '--files-cache=mtime,size', 'test2', 'input') self.assert_in("U input/file1", output) def test_file_status_rc_cache_mode(self): @@ -1979,10 +1986,12 @@ class ArchiverTestCase(ArchiverTestCaseBase): self.create_regular_file('file1', size=10) time.sleep(1) # file2 must have newer timestamps than file1 self.create_regular_file('file2', size=10) - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('create', '--list', '--files-cache=rechunk,ctime', self.repository_location + '::test1', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'create', + '--list', '--files-cache=rechunk,ctime', 'test1', 'input') # no changes here, but this mode rechunks unconditionally - output = self.cmd('create', '--list', '--files-cache=rechunk,ctime', self.repository_location + '::test2', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'create', + '--list', '--files-cache=rechunk,ctime', 'test2', 'input') self.assert_in("A input/file1", output) def test_file_status_excluded(self): @@ -1994,14 +2003,15 @@ class ArchiverTestCase(ArchiverTestCaseBase): if has_lchflags: self.create_regular_file('file3', size=1024 * 80) platform.set_flags(os.path.join(self.input_path, 'file3'), stat.UF_NODUMP) - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('create', '--list', '--exclude-nodump', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'create', '--list', '--exclude-nodump', 'test', 'input') self.assert_in("A input/file1", output) self.assert_in("A input/file2", output) if has_lchflags: self.assert_in("x input/file3", output) # should find second file as excluded - output = self.cmd('create', '--list', '--exclude-nodump', self.repository_location + '::test1', 'input', '--exclude', '*/file2') + output = self.cmd(f'--repo={self.repository_location}', 'create', 'test1', 'input', + '--list', '--exclude-nodump', '--exclude', '*/file2') self.assert_in("U input/file1", output) self.assert_in("x input/file2", output) if has_lchflags: @@ -2009,8 +2019,9 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_create_json(self): self.create_regular_file('file1', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - create_info = json.loads(self.cmd('create', '--json', self.repository_location + '::test', 'input')) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + create_info = json.loads(self.cmd(f'--repo={self.repository_location}', 'create', '--json', + 'test', 'input')) # The usual keys assert 'encryption' in create_info assert 'repository' in create_info @@ -2028,23 +2039,23 @@ class ArchiverTestCase(ArchiverTestCaseBase): self.create_regular_file('file1', size=1024 * 80) time.sleep(1) # file2 must have newer timestamps than file1 self.create_regular_file('file2', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') # no listing by default - output = self.cmd('create', self.repository_location + '::test', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') self.assert_not_in('file1', output) # shouldn't be listed even if unchanged - output = self.cmd('create', self.repository_location + '::test0', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'create', 'test0', 'input') self.assert_not_in('file1', output) # should list the file as unchanged - output = self.cmd('create', '--list', '--filter=U', self.repository_location + '::test1', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'create', 'test1', 'input', '--list', '--filter=U') self.assert_in('file1', output) # should *not* list the file as changed - output = self.cmd('create', '--list', '--filter=AM', self.repository_location + '::test2', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'create', 'test2', 'input', '--list', '--filter=AM') self.assert_not_in('file1', output) # change the file self.create_regular_file('file1', size=1024 * 100) # should list the file as changed - output = self.cmd('create', '--list', '--filter=AM', self.repository_location + '::test3', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'create', 'test3', 'input', '--list', '--filter=AM') self.assert_in('file1', output) @pytest.mark.skipif(not are_fifos_supported(), reason='FIFOs not supported') @@ -2058,8 +2069,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): finally: os.close(fd) - self.cmd('init', '--encryption=repokey', self.repository_location) - archive = self.repository_location + '::test' + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') data = b'foobar' * 1000 fifo_fn = os.path.join(self.input_path, 'fifo') @@ -2070,11 +2080,11 @@ class ArchiverTestCase(ArchiverTestCaseBase): t = Thread(target=fifo_feeder, args=(fifo_fn, data)) t.start() try: - self.cmd('create', '--read-special', archive, 'input/link_fifo') + self.cmd(f'--repo={self.repository_location}', 'create', '--read-special', 'test', 'input/link_fifo') finally: t.join() with changedir('output'): - self.cmd('extract', archive) + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') fifo_fn = 'input/link_fifo' with open(fifo_fn, 'rb') as f: extracted_data = f.read() @@ -2082,41 +2092,40 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_create_read_special_broken_symlink(self): os.symlink('somewhere does not exist', os.path.join(self.input_path, 'link')) - self.cmd('init', '--encryption=repokey', self.repository_location) - archive = self.repository_location + '::test' - self.cmd('create', '--read-special', archive, 'input') - output = self.cmd('list', archive) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', '--read-special', 'test', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'list', 'test') assert 'input/link -> somewhere does not exist' in output # def test_cmdline_compatibility(self): # self.create_regular_file('file1', size=1024 * 80) - # self.cmd('init', '--encryption=repokey', self.repository_location) - # self.cmd('create', self.repository_location + '::test', 'input') + # self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + # self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') # output = self.cmd('foo', self.repository_location, '--old') # self.assert_in('"--old" has been deprecated. Use "--new" instead', output) def test_prune_repository(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test1', src_dir) - self.cmd('create', self.repository_location + '::test2', src_dir) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test1', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', 'test2', src_dir) # these are not really a checkpoints, but they look like some: - self.cmd('create', self.repository_location + '::test3.checkpoint', src_dir) - self.cmd('create', self.repository_location + '::test3.checkpoint.1', src_dir) - self.cmd('create', self.repository_location + '::test4.checkpoint', src_dir) - output = self.cmd('prune', '--list', '--dry-run', self.repository_location, '--keep-daily=1') + self.cmd(f'--repo={self.repository_location}', 'create', 'test3.checkpoint', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', 'test3.checkpoint.1', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', 'test4.checkpoint', src_dir) + output = self.cmd(f'--repo={self.repository_location}', 'prune', '--list', '--dry-run', '--keep-daily=1') assert re.search(r'Would prune:\s+test1', output) # must keep the latest non-checkpoint archive: 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', '--consider-checkpoints', self.repository_location) + output = self.cmd(f'--repo={self.repository_location}', 'rlist', '--consider-checkpoints') 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=1') - output = self.cmd('list', '--consider-checkpoints', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'prune', '--keep-daily=1') + output = self.cmd(f'--repo={self.repository_location}', 'rlist', '--consider-checkpoints') self.assert_not_in('test1', output) # the latest non-checkpoint archive must be still there: self.assert_in('test2', output) @@ -2125,9 +2134,9 @@ class ArchiverTestCase(ArchiverTestCaseBase): self.assert_not_in('test3.checkpoint.1', output) self.assert_in('test4.checkpoint', output) # now we supersede 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', '--consider-checkpoints', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'create', 'test5', src_dir) + self.cmd(f'--repo={self.repository_location}', 'prune', '--keep-daily=2') + output = self.cmd(f'--repo={self.repository_location}', 'rlist', '--consider-checkpoints') # all checkpoints should be gone now: self.assert_not_in('checkpoint', output) # the latest archive must be still there @@ -2140,12 +2149,12 @@ class ArchiverTestCase(ArchiverTestCaseBase): return dtime.astimezone(dateutil.tz.UTC).strftime("%Y-%m-%dT%H:%M:%S") def _create_archive_ts(self, name, y, m, d, H=0, M=0, S=0): - loc = self.repository_location + '::' + name - self.cmd('create', '--timestamp', self._to_utc_timestamp(y, m, d, H, M, S), loc, src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', + '--timestamp', self._to_utc_timestamp(y, m, d, H, M, S), name, src_dir) # This test must match docs/misc/prune-example.txt def test_prune_repository_example(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') # Archives that will be kept, per the example # Oldest archive self._create_archive_ts('test01', 2015, 1, 1) @@ -2178,7 +2187,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): self._create_archive_ts('test23', 2015, 5, 31) # The next older daily backup self._create_archive_ts('test24', 2015, 12, 16) - output = self.cmd('prune', '--list', '--dry-run', self.repository_location, '--keep-daily=14', '--keep-monthly=6', '--keep-yearly=1') + output = self.cmd(f'--repo={self.repository_location}', 'prune', '--list', '--dry-run', '--keep-daily=14', '--keep-monthly=6', '--keep-yearly=1') # Prune second backup of the year assert re.search(r'Would prune:\s+test22', output) # Prune next older monthly and daily backups @@ -2191,12 +2200,12 @@ class ArchiverTestCase(ArchiverTestCaseBase): assert re.search(r'Keeping archive \(rule: monthly #' + str(i) + r'\):\s+test' + ("%02d" % (8-i)), output) for i in range(1, 15): assert re.search(r'Keeping archive \(rule: daily #' + str(i) + r'\):\s+test' + ("%02d" % (22-i)), output) - output = self.cmd('list', self.repository_location) + output = self.cmd(f'--repo={self.repository_location}', 'rlist') # Nothing pruned after dry run for i in range(1, 25): self.assert_in('test%02d' % i, output) - self.cmd('prune', self.repository_location, '--keep-daily=14', '--keep-monthly=6', '--keep-yearly=1') - output = self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'prune', '--keep-daily=14', '--keep-monthly=6', '--keep-yearly=1') + output = self.cmd(f'--repo={self.repository_location}', 'rlist') # All matching backups plus oldest kept for i in range(1, 22): self.assert_in('test%02d' % i, output) @@ -2206,141 +2215,139 @@ class ArchiverTestCase(ArchiverTestCaseBase): # With an initial and daily backup, prune daily until oldest is replaced by a monthly backup def test_prune_retain_and_expire_oldest(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') # Initial backup self._create_archive_ts('original_archive', 2020, 9, 1, 11, 15) # Archive and prune daily for 30 days for i in range(1, 31): self._create_archive_ts('september%02d' % i, 2020, 9, i, 12) - self.cmd('prune', self.repository_location, '--keep-daily=7', '--keep-monthly=1') + self.cmd(f'--repo={self.repository_location}', 'prune', '--keep-daily=7', '--keep-monthly=1') # Archive and prune 6 days into the next month for i in range(1, 7): self._create_archive_ts('october%02d' % i, 2020, 10, i, 12) - self.cmd('prune', self.repository_location, '--keep-daily=7', '--keep-monthly=1') + self.cmd(f'--repo={self.repository_location}', 'prune', '--keep-daily=7', '--keep-monthly=1') # Oldest backup is still retained - output = self.cmd('prune', '--list', '--dry-run', self.repository_location, '--keep-daily=7', '--keep-monthly=1') + output = self.cmd(f'--repo={self.repository_location}', 'prune', '--list', '--dry-run', '--keep-daily=7', '--keep-monthly=1') assert re.search(r'Keeping archive \(rule: monthly\[oldest\] #1' + r'\):\s+original_archive', output) # Archive one more day and prune. self._create_archive_ts('october07', 2020, 10, 7, 12) - self.cmd('prune', self.repository_location, '--keep-daily=7', '--keep-monthly=1') + self.cmd(f'--repo={self.repository_location}', 'prune', '--keep-daily=7', '--keep-monthly=1') # Last day of previous month is retained as monthly, and oldest is expired. - output = self.cmd('prune', '--list', '--dry-run', self.repository_location, '--keep-daily=7', '--keep-monthly=1') + output = self.cmd(f'--repo={self.repository_location}', 'prune', '--list', '--dry-run', '--keep-daily=7', '--keep-monthly=1') assert re.search(r'Keeping archive \(rule: monthly #1\):\s+september30', output) self.assert_not_in('original_archive', output) def test_prune_repository_save_space(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test1', src_dir) - self.cmd('create', self.repository_location + '::test2', src_dir) - output = self.cmd('prune', '--list', '--dry-run', self.repository_location, '--keep-daily=1') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test1', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', 'test2', src_dir) + output = self.cmd(f'--repo={self.repository_location}', 'prune', '--list', '--dry-run', '--keep-daily=1') assert re.search(r'Keeping archive \(rule: daily #1\):\s+test2', output) assert re.search(r'Would prune:\s+test1', output) - output = self.cmd('list', self.repository_location) + output = self.cmd(f'--repo={self.repository_location}', 'rlist') self.assert_in('test1', output) self.assert_in('test2', output) - self.cmd('prune', '--save-space', self.repository_location, '--keep-daily=1') - output = self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'prune', '--save-space', '--keep-daily=1') + output = self.cmd(f'--repo={self.repository_location}', 'rlist') self.assert_not_in('test1', output) self.assert_in('test2', output) def test_prune_repository_prefix(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::foo-2015-08-12-10:00', src_dir) - self.cmd('create', self.repository_location + '::foo-2015-08-12-20:00', src_dir) - self.cmd('create', self.repository_location + '::bar-2015-08-12-10:00', src_dir) - self.cmd('create', self.repository_location + '::bar-2015-08-12-20:00', src_dir) - output = self.cmd('prune', '--list', '--dry-run', self.repository_location, '--keep-daily=1', '--prefix=foo-') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'foo-2015-08-12-10:00', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', 'foo-2015-08-12-20:00', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', 'bar-2015-08-12-10:00', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', 'bar-2015-08-12-20:00', src_dir) + output = self.cmd(f'--repo={self.repository_location}', 'prune', '--list', '--dry-run', '--keep-daily=1', '--prefix=foo-') assert re.search(r'Keeping archive \(rule: daily #1\):\s+foo-2015-08-12-20:00', output) assert re.search(r'Would prune:\s+foo-2015-08-12-10:00', output) - output = self.cmd('list', self.repository_location) + output = self.cmd(f'--repo={self.repository_location}', 'rlist') self.assert_in('foo-2015-08-12-10:00', output) self.assert_in('foo-2015-08-12-20:00', output) self.assert_in('bar-2015-08-12-10:00', output) self.assert_in('bar-2015-08-12-20:00', output) - self.cmd('prune', self.repository_location, '--keep-daily=1', '--prefix=foo-') - output = self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'prune', '--keep-daily=1', '--prefix=foo-') + output = self.cmd(f'--repo={self.repository_location}', 'rlist') self.assert_not_in('foo-2015-08-12-10:00', output) self.assert_in('foo-2015-08-12-20:00', output) self.assert_in('bar-2015-08-12-10:00', output) self.assert_in('bar-2015-08-12-20:00', output) def test_prune_repository_glob(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::2015-08-12-10:00-foo', src_dir) - self.cmd('create', self.repository_location + '::2015-08-12-20:00-foo', src_dir) - self.cmd('create', self.repository_location + '::2015-08-12-10:00-bar', src_dir) - self.cmd('create', self.repository_location + '::2015-08-12-20:00-bar', src_dir) - output = self.cmd('prune', '--list', '--dry-run', self.repository_location, '--keep-daily=1', '--glob-archives=2015-*-foo') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', '2015-08-12-10:00-foo', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', '2015-08-12-20:00-foo', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', '2015-08-12-10:00-bar', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', '2015-08-12-20:00-bar', src_dir) + output = self.cmd(f'--repo={self.repository_location}', 'prune', '--list', '--dry-run', '--keep-daily=1', '--glob-archives=2015-*-foo') assert re.search(r'Keeping archive \(rule: daily #1\):\s+2015-08-12-20:00-foo', output) assert re.search(r'Would prune:\s+2015-08-12-10:00-foo', output) - output = self.cmd('list', self.repository_location) + output = self.cmd(f'--repo={self.repository_location}', 'rlist') self.assert_in('2015-08-12-10:00-foo', output) self.assert_in('2015-08-12-20:00-foo', output) self.assert_in('2015-08-12-10:00-bar', output) self.assert_in('2015-08-12-20:00-bar', output) - self.cmd('prune', self.repository_location, '--keep-daily=1', '--glob-archives=2015-*-foo') - output = self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'prune', '--keep-daily=1', '--glob-archives=2015-*-foo') + output = self.cmd(f'--repo={self.repository_location}', 'rlist') self.assert_not_in('2015-08-12-10:00-foo', output) self.assert_in('2015-08-12-20:00-foo', output) self.assert_in('2015-08-12-10:00-bar', output) self.assert_in('2015-08-12-20:00-bar', output) def test_list_prefix(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 + '::something-else-than-test-1', src_dir) - self.cmd('create', self.repository_location + '::test-2', src_dir) - output = self.cmd('list', '--prefix=test-', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test-1', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', 'something-else-than-test-1', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', 'test-2', src_dir) + output = self.cmd(f'--repo={self.repository_location}', 'rlist', '--prefix=test-') self.assert_in('test-1', output) self.assert_in('test-2', output) self.assert_not_in('something-else', output) def test_list_format(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - test_archive = self.repository_location + '::test' - self.cmd('create', test_archive, src_dir) - output_1 = self.cmd('list', test_archive) - output_2 = self.cmd('list', '--format', '{mode} {user:6} {group:6} {size:8d} {mtime} {path}{extra}{NEWLINE}', test_archive) - output_3 = self.cmd('list', '--format', '{mtime:%s} {path}{NL}', test_archive) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', src_dir) + output_1 = self.cmd(f'--repo={self.repository_location}', 'list', 'test') + output_2 = self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--format', '{mode} {user:6} {group:6} {size:8d} {mtime} {path}{extra}{NEWLINE}') + output_3 = self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--format', '{mtime:%s} {path}{NL}') self.assertEqual(output_1, output_2) self.assertNotEqual(output_1, output_3) - def test_list_repository_format(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - 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) + def test_archives_format(self): + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', '--comment', 'comment 1', 'test-1', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', '--comment', 'comment 2', 'test-2', src_dir) + output_1 = self.cmd(f'--repo={self.repository_location}', 'rlist') + output_2 = self.cmd(f'--repo={self.repository_location}', 'rlist', '--format', '{archive:<36} {time} [{id}]{NL}') self.assertEqual(output_1, output_2) - output_1 = self.cmd('list', '--short', self.repository_location) + output_1 = self.cmd(f'--repo={self.repository_location}', 'rlist', '--short') self.assertEqual(output_1, 'test-1\ntest-2\n') - output_1 = self.cmd('list', '--format', '{barchive}/', self.repository_location) + output_1 = self.cmd(f'--repo={self.repository_location}', 'rlist', '--format', '{barchive}/') self.assertEqual(output_1, 'test-1/test-2/') - output_3 = self.cmd('list', '--format', '{name} {comment}{NL}', self.repository_location) + output_3 = self.cmd(f'--repo={self.repository_location}', 'rlist', '--format', '{name} {comment}{NL}') 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) self.create_regular_file('amb', contents=b'a' * 1000000) - self.cmd('init', '--encryption=repokey', self.repository_location) - test_archive = self.repository_location + '::test' - self.cmd('create', test_archive, 'input') - output = self.cmd('list', '--format', '{sha256} {path}{NL}', test_archive) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--format', '{sha256} {path}{NL}') 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) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', '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) + self.cmd(f'--repo={self.repository_location}', 'create', 'test2.checkpoint', src_dir) + self.cmd(f'--repo={self.repository_location}', 'create', 'test3.checkpoint.1', src_dir) + output = self.cmd(f'--repo={self.repository_location}', 'rlist') 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) + output = self.cmd(f'--repo={self.repository_location}', 'rlist', '--consider-checkpoints') assert "test1" in output assert "test2.checkpoint" in output assert "test3.checkpoint.1" in output @@ -2351,27 +2358,25 @@ class ArchiverTestCase(ArchiverTestCaseBase): with open(os.path.join(self.input_path, 'two_chunks'), 'wb') as fd: fd.write(b'abba' * 2000000) fd.write(b'baab' * 2000000) - self.cmd('init', '--encryption=repokey', self.repository_location) - test_archive = self.repository_location + '::test' - self.cmd('create', test_archive, 'input') - output = self.cmd('list', '--format', '{num_chunks} {unique_chunks} {path}{NL}', test_archive) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--format', '{num_chunks} {unique_chunks} {path}{NL}') assert "0 0 input/empty_file" in output assert "2 2 input/two_chunks" in output def test_list_size(self): self.create_regular_file('compressible_file', size=10000) - self.cmd('init', '--encryption=repokey', self.repository_location) - test_archive = self.repository_location + '::test' - self.cmd('create', '-C', 'lz4', test_archive, 'input') - output = self.cmd('list', '--format', '{size} {path}{NL}', test_archive) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', '-C', 'lz4', 'test', 'input') + output = self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--format', '{size} {path}{NL}') size, path = output.split("\n")[1].split(" ") assert int(size) == 10000 def test_list_json(self): self.create_regular_file('file1', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') - list_repo = json.loads(self.cmd('list', '--json', self.repository_location)) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + list_repo = json.loads(self.cmd(f'--repo={self.repository_location}', 'rlist', '--json')) repository = list_repo['repository'] assert len(repository['id']) == 64 assert datetime.strptime(repository['last_modified'], ISO_FORMAT) # must not raise @@ -2380,7 +2385,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): archive0 = list_repo['archives'][0] assert datetime.strptime(archive0['time'], ISO_FORMAT) # must not raise - list_archive = self.cmd('list', '--json-lines', self.repository_location + '::test') + list_archive = self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--json-lines') items = [json.loads(s) for s in list_archive.splitlines()] assert len(items) == 2 file1 = items[1] @@ -2388,22 +2393,17 @@ class ArchiverTestCase(ArchiverTestCaseBase): assert file1['size'] == 81920 assert datetime.strptime(file1['mtime'], ISO_FORMAT) # must not raise - list_archive = self.cmd('list', '--json-lines', '--format={sha256}', self.repository_location + '::test') + list_archive = self.cmd(f'--repo={self.repository_location}', 'list', 'test', '--json-lines', '--format={sha256}') items = [json.loads(s) for s in list_archive.splitlines()] assert len(items) == 2 file1 = items[1] assert file1['path'] == 'input/file1' assert file1['sha256'] == 'b2915eb69f260d8d3c25249195f2c8f4f716ea82ec760ae929732c0262442b2b' - def test_list_json_args(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('list', '--json-lines', self.repository_location, exit_code=2) - self.cmd('list', '--json', self.repository_location + '::archive', exit_code=2) - def test_log_json(self): self.create_test_files() - self.cmd('init', '--encryption=repokey', self.repository_location) - log = self.cmd('create', '--log-json', self.repository_location + '::test', 'input', '--list', '--debug') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + log = self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input', '--log-json', '--list', '--debug') messages = {} # type -> message, one of each kind for line in log.splitlines(): msg = json.loads(line) @@ -2420,67 +2420,67 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_debug_profile(self): self.create_test_files() - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input', '--debug-profile=create.prof') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input', '--debug-profile=create.prof') self.cmd('debug', 'convert-profile', 'create.prof', 'create.pyprof') stats = pstats.Stats('create.pyprof') stats.strip_dirs() stats.sort_stats('cumtime') - self.cmd('create', self.repository_location + '::test2', 'input', '--debug-profile=create.pyprof') + self.cmd(f'--repo={self.repository_location}', 'create', 'test2', 'input', '--debug-profile=create.pyprof') stats = pstats.Stats('create.pyprof') # Only do this on trusted data! stats.strip_dirs() stats.sort_stats('cumtime') def test_common_options(self): self.create_test_files() - self.cmd('init', '--encryption=repokey', self.repository_location) - log = self.cmd('--debug', 'create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + log = self.cmd(f'--repo={self.repository_location}', '--debug', 'create', 'test', 'input') assert 'security: read previous location' in log def test_change_passphrase(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') os.environ['BORG_NEW_PASSPHRASE'] = 'newpassphrase' # here we have both BORG_PASSPHRASE and BORG_NEW_PASSPHRASE set: - self.cmd('key', 'change-passphrase', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'key', 'change-passphrase') os.environ['BORG_PASSPHRASE'] = 'newpassphrase' - self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rlist') def test_change_location_to_keyfile(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - log = self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + log = self.cmd(f'--repo={self.repository_location}', 'rinfo') assert '(repokey)' in log - self.cmd('key', 'change-location', self.repository_location, 'keyfile') - log = self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'key', 'change-location', 'keyfile') + log = self.cmd(f'--repo={self.repository_location}', 'rinfo') assert '(key file)' in log def test_change_location_to_b2keyfile(self): - self.cmd('init', '--encryption=repokey-blake2', self.repository_location) - log = self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey-blake2') + log = self.cmd(f'--repo={self.repository_location}', 'rinfo') assert '(repokey BLAKE2b)' in log - self.cmd('key', 'change-location', self.repository_location, 'keyfile') - log = self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'key', 'change-location', 'keyfile') + log = self.cmd(f'--repo={self.repository_location}', 'rinfo') assert '(key file BLAKE2b)' in log def test_change_location_to_repokey(self): - self.cmd('init', '--encryption=keyfile', self.repository_location) - log = self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=keyfile') + log = self.cmd(f'--repo={self.repository_location}', 'rinfo') assert '(key file)' in log - self.cmd('key', 'change-location', self.repository_location, 'repokey') - log = self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'key', 'change-location', 'repokey') + log = self.cmd(f'--repo={self.repository_location}', 'rinfo') assert '(repokey)' in log def test_change_location_to_b2repokey(self): - self.cmd('init', '--encryption=keyfile-blake2', self.repository_location) - log = self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=keyfile-blake2') + log = self.cmd(f'--repo={self.repository_location}', 'rinfo') assert '(key file BLAKE2b)' in log - self.cmd('key', 'change-location', self.repository_location, 'repokey') - log = self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'key', 'change-location', 'repokey') + log = self.cmd(f'--repo={self.repository_location}', 'rinfo') assert '(repokey BLAKE2b)' in log def test_break_lock(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('break-lock', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'break-lock') def test_usage(self): self.cmd() @@ -2489,9 +2489,9 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_help(self): assert 'Borg' in self.cmd('help') assert 'patterns' in self.cmd('help', 'patterns') - assert 'Initialize' in self.cmd('help', 'init') - assert 'positional arguments' not in self.cmd('help', 'init', '--epilog-only') - assert 'This command initializes' not in self.cmd('help', 'init', '--usage-only') + assert 'creates a new, empty repository' in self.cmd('help', 'rcreate') + assert 'positional arguments' not in self.cmd('help', 'rcreate', '--epilog-only') + assert 'creates a new, empty repository' not in self.cmd('help', 'rcreate', '--usage-only') @unittest.skipUnless(llfuse, 'llfuse not installed') def test_fuse(self): @@ -2506,11 +2506,11 @@ class ArchiverTestCase(ArchiverTestCaseBase): noatime_used = flags_noatime != flags_normal return noatime_used and atime_before == atime_after - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_test_files() have_noatime = has_noatime('input/file1') - self.cmd('create', '--exclude-nodump', '--atime', self.repository_location + '::archive', 'input') - self.cmd('create', '--exclude-nodump', '--atime', self.repository_location + '::archive2', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', '--exclude-nodump', '--atime', 'archive', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', '--exclude-nodump', '--atime', 'archive2', 'input') if has_lchflags: # remove the file we did not backup, so input and output become equal os.remove(os.path.join('input', 'flagfile')) @@ -2523,13 +2523,12 @@ class ArchiverTestCase(ArchiverTestCaseBase): ignore_flags=True, ignore_xattrs=True) self.assert_dirs_equal(self.input_path, os.path.join(mountpoint, 'archive2', 'input'), ignore_flags=True, ignore_xattrs=True) - # mount only 1 archive, its contents shall show up directly in mountpoint: - with self.fuse_mount(self.repository_location + '::archive', mountpoint): - self.assert_dirs_equal(self.input_path, os.path.join(mountpoint, 'input'), + with self.fuse_mount(self.repository_location, mountpoint, '-a', 'archive'): + self.assert_dirs_equal(self.input_path, os.path.join(mountpoint, 'archive', 'input'), ignore_flags=True, ignore_xattrs=True) # regular file in_fn = 'input/file1' - out_fn = os.path.join(mountpoint, 'input', 'file1') + out_fn = os.path.join(mountpoint, 'archive', 'input', 'file1') # stat sti1 = os.stat(in_fn) sto1 = os.stat(out_fn) @@ -2550,7 +2549,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): # hardlink (to 'input/file1') if are_hardlinks_supported(): in_fn = 'input/hardlink' - out_fn = os.path.join(mountpoint, 'input', 'hardlink') + out_fn = os.path.join(mountpoint, 'archive', 'input', 'hardlink') sti2 = os.stat(in_fn) sto2 = os.stat(out_fn) assert sti2.st_nlink == sto2.st_nlink == 2 @@ -2558,7 +2557,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): # symlink if are_symlinks_supported(): in_fn = 'input/link1' - out_fn = os.path.join(mountpoint, 'input', 'link1') + out_fn = os.path.join(mountpoint, 'archive', 'input', 'link1') sti = os.stat(in_fn, follow_symlinks=False) sto = os.stat(out_fn, follow_symlinks=False) assert sti.st_size == len('somewhere') @@ -2568,13 +2567,13 @@ class ArchiverTestCase(ArchiverTestCaseBase): assert os.readlink(in_fn) == os.readlink(out_fn) # FIFO if are_fifos_supported(): - out_fn = os.path.join(mountpoint, 'input', 'fifo1') + out_fn = os.path.join(mountpoint, 'archive', 'input', 'fifo1') sto = os.stat(out_fn) assert stat.S_ISFIFO(sto.st_mode) # list/read xattrs try: in_fn = 'input/fusexattr' - out_fn = os.fsencode(os.path.join(mountpoint, 'input', 'fusexattr')) + out_fn = os.fsencode(os.path.join(mountpoint, 'archive', 'input', 'fusexattr')) if not xattr.XATTR_FAKEROOT and xattr.is_enabled(self.input_path): assert sorted(no_selinux(xattr.listxattr(out_fn))) == [b'user.empty', b'user.foo', ] assert xattr.getxattr(out_fn, b'user.foo') == b'bar' @@ -2596,15 +2595,15 @@ class ArchiverTestCase(ArchiverTestCaseBase): @unittest.skipUnless(llfuse, 'llfuse not installed') def test_fuse_versions_view(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('test', contents=b'first') if are_hardlinks_supported(): self.create_regular_file('hardlink1', contents=b'123456') os.link('input/hardlink1', 'input/hardlink2') os.link('input/hardlink1', 'input/hardlink3') - self.cmd('create', self.repository_location + '::archive1', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'archive1', 'input') self.create_regular_file('test', contents=b'second') - self.cmd('create', self.repository_location + '::archive2', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'archive2', 'input') mountpoint = os.path.join(self.tmpdir, 'mountpoint') # mount the whole repository, archive contents shall show up in versioned view: with self.fuse_mount(self.repository_location, mountpoint, '-o', 'versions'): @@ -2628,7 +2627,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): @unittest.skipUnless(llfuse, 'llfuse not installed') def test_fuse_allow_damaged_files(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('archive') # Get rid of a chunk and repair it archive, repository = self.open_archive('archive') @@ -2641,19 +2640,19 @@ class ArchiverTestCase(ArchiverTestCaseBase): else: assert False # missed the file repository.commit(compact=False) - self.cmd('check', '--repair', self.repository_location, exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', '--repair', exit_code=0) mountpoint = os.path.join(self.tmpdir, 'mountpoint') - with self.fuse_mount(self.repository_location + '::archive', mountpoint): + with self.fuse_mount(self.repository_location, mountpoint, '-a', 'archive'): with pytest.raises(OSError) as excinfo: - open(os.path.join(mountpoint, path)) + open(os.path.join(mountpoint, 'archive', path)) assert excinfo.value.errno == errno.EIO - with self.fuse_mount(self.repository_location + '::archive', mountpoint, '-o', 'allow_damaged_files'): - open(os.path.join(mountpoint, path)).close() + with self.fuse_mount(self.repository_location, mountpoint, '-a', 'archive', '-o', 'allow_damaged_files'): + open(os.path.join(mountpoint, 'archive', path)).close() @unittest.skipUnless(llfuse, 'llfuse not installed') def test_fuse_mount_options(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('arch11') self.create_src_archive('arch12') self.create_src_archive('arch21') @@ -2727,7 +2726,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): # Decorate borg.locking.Lock.migrate_lock = write_assert_data(borg.locking.Lock.migrate_lock) try: - self.cmd('init', '--encryption=none', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=none') self.create_src_archive('arch') mountpoint = os.path.join(self.tmpdir, 'mountpoint') # In order that the decoration is kept for the borg mount process, we must not spawn, but actually fork; @@ -2780,13 +2779,13 @@ class ArchiverTestCase(ArchiverTestCaseBase): self.create_test_files() os.environ['BORG_PASSPHRASE'] = 'passphrase' - self.cmd('init', '--encryption=' + method, self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=' + method) verify_uniqueness() - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') verify_uniqueness() - self.cmd('create', self.repository_location + '::test.2', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test.2', 'input') verify_uniqueness() - self.cmd('delete', self.repository_location + '::test.2') + self.cmd(f'--repo={self.repository_location}', 'delete', '-a', 'test.2') verify_uniqueness() def test_aes_counter_uniqueness_keyfile(self): @@ -2797,41 +2796,41 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_debug_dump_archive_items(self): self.create_test_files() - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir('output'): - output = self.cmd('debug', 'dump-archive-items', self.repository_location + '::test') + output = self.cmd(f'--repo={self.repository_location}', 'debug', 'dump-archive-items', 'test') output_dir = sorted(os.listdir('output')) assert len(output_dir) > 0 and output_dir[0].startswith('000000_') assert 'Done.' in output def test_debug_dump_repo_objs(self): self.create_test_files() - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir('output'): - output = self.cmd('debug', 'dump-repo-objs', self.repository_location) + output = self.cmd(f'--repo={self.repository_location}', 'debug', 'dump-repo-objs') output_dir = sorted(os.listdir('output')) assert len(output_dir) > 0 and output_dir[0].startswith('00000000_') assert 'Done.' in output def test_debug_put_get_delete_obj(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') data = b'some data' hexkey = sha256(data).hexdigest() self.create_regular_file('file', contents=data) - output = self.cmd('debug', 'put-obj', self.repository_location, 'input/file') + output = self.cmd(f'--repo={self.repository_location}', 'debug', 'put-obj', 'input/file') assert hexkey in output - output = self.cmd('debug', 'get-obj', self.repository_location, hexkey, 'output/file') + output = self.cmd(f'--repo={self.repository_location}', 'debug', 'get-obj', hexkey, 'output/file') assert hexkey in output with open('output/file', 'rb') as f: data_read = f.read() assert data == data_read - output = self.cmd('debug', 'delete-obj', self.repository_location, hexkey) + output = self.cmd(f'--repo={self.repository_location}', 'debug', 'delete-obj', hexkey) assert "deleted" in output - output = self.cmd('debug', 'delete-obj', self.repository_location, hexkey) + output = self.cmd(f'--repo={self.repository_location}', 'debug', 'delete-obj', hexkey) assert "not found" in output - output = self.cmd('debug', 'delete-obj', self.repository_location, 'invalid') + output = self.cmd(f'--repo={self.repository_location}', 'debug', 'delete-obj', 'invalid') assert "is invalid" in output def test_init_interrupt(self): @@ -2839,19 +2838,19 @@ class ArchiverTestCase(ArchiverTestCaseBase): raise EOFError with patch.object(FlexiKey, 'create', raise_eof): - self.cmd('init', '--encryption=repokey', self.repository_location, exit_code=1) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey', exit_code=1) assert not os.path.exists(self.repository_location) def test_init_requires_encryption_option(self): - self.cmd('init', self.repository_location, exit_code=2) + self.cmd(f'--repo={self.repository_location}', 'rcreate', exit_code=2) def test_init_nested_repositories(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') if self.FORK_DEFAULT: - self.cmd('init', '--encryption=repokey', self.repository_location + '/nested', exit_code=2) + self.cmd(f'--repo={self.repository_location}/nested', 'rcreate', '--encryption=repokey', exit_code=2) else: with pytest.raises(Repository.AlreadyExists): - self.cmd('init', '--encryption=repokey', self.repository_location + '/nested') + self.cmd(f'--repo={self.repository_location}/nested', 'rcreate', '--encryption=repokey') def test_init_refuse_to_overwrite_keyfile(self): """BORG_KEY_FILE=something borg init should quit if "something" already exists. @@ -2859,10 +2858,10 @@ class ArchiverTestCase(ArchiverTestCaseBase): See https://github.com/borgbackup/borg/pull/6046""" keyfile = os.path.join(self.tmpdir, 'keyfile') with environment_variable(BORG_KEY_FILE=keyfile): - self.cmd('init', '--encryption=keyfile', self.repository_location + '0') + self.cmd(f'--repo={self.repository_location}0', 'rcreate', '--encryption=keyfile') with open(keyfile) as file: before = file.read() - arg = ('init', '--encryption=keyfile', self.repository_location + '1') + arg = (f'--repo={self.repository_location}1', 'rcreate', '--encryption=keyfile') if self.FORK_DEFAULT: self.cmd(*arg, exit_code=2) else: @@ -2874,7 +2873,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): def check_cache(self): # First run a regular borg check - self.cmd('check', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'check') # Then check that the cache on disk matches exactly what's in the repo. with self.open_repository() as repository: manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK) @@ -2894,8 +2893,8 @@ class ArchiverTestCase(ArchiverTestCaseBase): assert id in seen def test_check_cache(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with self.open_repository() as repository: manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK) with Cache(repository, key, manifest, sync=False) as cache: @@ -2906,26 +2905,25 @@ class ArchiverTestCase(ArchiverTestCaseBase): self.check_cache() def test_recreate_target_rc(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('recreate', self.repository_location, '--target=asdf', exit_code=2) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'recreate', '--target=asdf', exit_code=2) assert 'Need to specify single archive' in output def test_recreate_target(self): self.create_test_files() - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.check_cache() - archive = self.repository_location + '::test0' - self.cmd('create', archive, 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test0', 'input') self.check_cache() - original_archive = self.cmd('list', self.repository_location) - self.cmd('recreate', archive, 'input/dir2', '-e', 'input/dir2/file3', '--target=new-archive') + original_archive = self.cmd(f'--repo={self.repository_location}', 'rlist') + self.cmd(f'--repo={self.repository_location}', 'recreate', 'test0', 'input/dir2', + '-e', 'input/dir2/file3', '--target=new-archive') self.check_cache() - archives = self.cmd('list', self.repository_location) + archives = self.cmd(f'--repo={self.repository_location}', 'rlist') assert original_archive in archives assert 'new-archive' in archives - archive = self.repository_location + '::new-archive' - listing = self.cmd('list', '--short', archive) + listing = self.cmd(f'--repo={self.repository_location}', 'list', 'new-archive', '--short') assert 'file1' not in listing assert 'dir2/file2' in listing assert 'dir2/file3' not in listing @@ -2933,12 +2931,11 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_recreate_basic(self): self.create_test_files() self.create_regular_file('dir2/file3', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - archive = self.repository_location + '::test0' - self.cmd('create', archive, 'input') - self.cmd('recreate', archive, 'input/dir2', '-e', 'input/dir2/file3') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test0', 'input') + self.cmd(f'--repo={self.repository_location}', 'recreate', 'test0', 'input/dir2', '-e', 'input/dir2/file3') self.check_cache() - listing = self.cmd('list', '--short', archive) + listing = self.cmd(f'--repo={self.repository_location}', 'list', 'test0', '--short') assert 'file1' not in listing assert 'dir2/file2' in listing assert 'dir2/file3' not in listing @@ -2947,48 +2944,48 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_recreate_subtree_hardlinks(self): # This is essentially the same problem set as in test_extract_hardlinks self._extract_hardlinks_setup() - self.cmd('create', self.repository_location + '::test2', 'input') - self.cmd('recreate', self.repository_location + '::test', 'input/dir1') + self.cmd(f'--repo={self.repository_location}', 'create', 'test2', 'input') + self.cmd(f'--repo={self.repository_location}', 'recreate', '-a', 'test', 'input/dir1') self.check_cache() with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') assert os.stat('input/dir1/hardlink').st_nlink == 2 assert os.stat('input/dir1/subdir/hardlink').st_nlink == 2 assert os.stat('input/dir1/aaaa').st_nlink == 2 assert os.stat('input/dir1/source2').st_nlink == 2 with changedir('output'): - self.cmd('extract', self.repository_location + '::test2') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test2') assert os.stat('input/dir1/hardlink').st_nlink == 4 def test_recreate_rechunkify(self): with open(os.path.join(self.input_path, 'large_file'), 'wb') as fd: fd.write(b'a' * 280) fd.write(b'b' * 280) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', '--chunker-params', '7,9,8,128', self.repository_location + '::test1', 'input') - self.cmd('create', self.repository_location + '::test2', 'input', '--files-cache=disabled') - list = self.cmd('list', self.repository_location + '::test1', 'input/large_file', + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test1', 'input', '--chunker-params', '7,9,8,128') + self.cmd(f'--repo={self.repository_location}', 'create', 'test2', 'input', '--files-cache=disabled') + list = self.cmd(f'--repo={self.repository_location}', 'list', 'test1', 'input/large_file', '--format', '{num_chunks} {unique_chunks}') num_chunks, unique_chunks = map(int, list.split(' ')) # test1 and test2 do not deduplicate assert num_chunks == unique_chunks - self.cmd('recreate', self.repository_location, '--chunker-params', 'default') + self.cmd(f'--repo={self.repository_location}', 'recreate', '--chunker-params', 'default') self.check_cache() # test1 and test2 do deduplicate after recreate - assert int(self.cmd('list', self.repository_location + '::test1', 'input/large_file', '--format={size}')) - assert not int(self.cmd('list', self.repository_location + '::test1', 'input/large_file', + assert int(self.cmd(f'--repo={self.repository_location}', 'list', 'test1', 'input/large_file', '--format={size}')) + assert not int(self.cmd(f'--repo={self.repository_location}', 'list', 'test1', 'input/large_file', '--format', '{unique_chunks}')) def test_recreate_recompress(self): self.create_regular_file('compressible', size=10000) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input', '-C', 'none') - file_list = self.cmd('list', self.repository_location + '::test', 'input/compressible', + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input', '-C', 'none') + file_list = self.cmd(f'--repo={self.repository_location}', 'list', 'test', 'input/compressible', '--format', '{size} {sha256}') size, sha256_before = file_list.split(' ') - self.cmd('recreate', self.repository_location, '-C', 'lz4', '--recompress') + self.cmd(f'--repo={self.repository_location}', 'recreate', '-C', 'lz4', '--recompress') self.check_cache() - file_list = self.cmd('list', self.repository_location + '::test', 'input/compressible', + file_list = self.cmd(f'--repo={self.repository_location}', 'list', 'test', 'input/compressible', '--format', '{size} {sha256}') size, sha256_after = file_list.split(' ') assert sha256_before == sha256_after @@ -2996,12 +2993,11 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_recreate_timestamp(self): local_timezone = datetime.now(timezone(timedelta(0))).astimezone().tzinfo self.create_test_files() - self.cmd('init', '--encryption=repokey', self.repository_location) - archive = self.repository_location + '::test0' - self.cmd('create', archive, 'input') - self.cmd('recreate', '--timestamp', "1970-01-02T00:00:00", '--comment', - 'test', archive) - info = self.cmd('info', archive).splitlines() + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test0', 'input') + self.cmd(f'--repo={self.repository_location}', 'recreate', 'test0', '--timestamp', "1970-01-02T00:00:00", + '--comment', 'test') + info = self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test0').splitlines() dtime = datetime(1970, 1, 2) + local_timezone.utcoffset(None) s_time = dtime.strftime("%Y-%m-%d") assert any([re.search(r'Time \(start\).+ %s' % s_time, item) for item in info]) @@ -3009,70 +3005,70 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_recreate_dry_run(self): self.create_regular_file('compressible', size=10000) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') - archives_before = self.cmd('list', self.repository_location + '::test') - self.cmd('recreate', self.repository_location, '-n', '-e', 'input/compressible') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + archives_before = self.cmd(f'--repo={self.repository_location}', 'list', 'test') + self.cmd(f'--repo={self.repository_location}', 'recreate', '-n', '-e', 'input/compressible') self.check_cache() - archives_after = self.cmd('list', self.repository_location + '::test') + archives_after = self.cmd(f'--repo={self.repository_location}', 'list', 'test') assert archives_after == archives_before def test_recreate_skips_nothing_to_do(self): self.create_regular_file('file1', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') - info_before = self.cmd('info', self.repository_location + '::test') - self.cmd('recreate', self.repository_location, '--chunker-params', 'default') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + info_before = self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test') + self.cmd(f'--repo={self.repository_location}', 'recreate', '--chunker-params', 'default') self.check_cache() - info_after = self.cmd('info', self.repository_location + '::test') + info_after = self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test') assert info_before == info_after # includes archive ID def test_with_lock(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') lock_path = os.path.join(self.repository_path, 'lock.exclusive') cmd = 'python3', '-c', 'import os, sys; sys.exit(42 if os.path.exists("%s") else 23)' % lock_path - self.cmd('with-lock', self.repository_location, *cmd, fork=True, exit_code=42) + self.cmd(f'--repo={self.repository_location}', 'with-lock', *cmd, fork=True, exit_code=42) def test_recreate_list_output(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('file1', size=0) self.create_regular_file('file2', size=0) self.create_regular_file('file3', size=0) self.create_regular_file('file4', size=0) self.create_regular_file('file5', size=0) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') - output = self.cmd('recreate', '--list', '--info', self.repository_location + '::test', '-e', 'input/file2') + output = self.cmd(f'--repo={self.repository_location}', 'recreate', '-a', 'test', '--list', '--info', '-e', 'input/file2') self.check_cache() self.assert_in("input/file1", output) self.assert_in("x input/file2", output) - output = self.cmd('recreate', '--list', self.repository_location + '::test', '-e', 'input/file3') + output = self.cmd(f'--repo={self.repository_location}', 'recreate', '-a', 'test', '--list', '-e', 'input/file3') self.check_cache() self.assert_in("input/file1", output) self.assert_in("x input/file3", output) - output = self.cmd('recreate', self.repository_location + '::test', '-e', 'input/file4') + output = self.cmd(f'--repo={self.repository_location}', 'recreate', '-a', 'test', '-e', 'input/file4') self.check_cache() self.assert_not_in("input/file1", output) self.assert_not_in("x input/file4", output) - output = self.cmd('recreate', '--info', self.repository_location + '::test', '-e', 'input/file5') + output = self.cmd(f'--repo={self.repository_location}', 'recreate', '-a', 'test', '--info', '-e', 'input/file5') self.check_cache() self.assert_not_in("input/file1", output) self.assert_not_in("x input/file5", output) def test_bad_filters(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') - self.cmd('delete', '--first', '1', '--last', '1', self.repository_location, fork=True, exit_code=2) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + self.cmd(f'--repo={self.repository_location}', 'delete', '--first', '1', '--last', '1', fork=True, exit_code=2) def test_key_export_keyfile(self): export_file = self.output_path + '/exported' - self.cmd('init', self.repository_location, '--encryption', 'keyfile') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption', 'keyfile') repo_id = self._extract_repository_id(self.repository_path) - self.cmd('key', 'export', self.repository_location, export_file) + self.cmd(f'--repo={self.repository_location}', 'key', 'export', export_file) with open(export_file) as fd: export_contents = fd.read() @@ -3088,7 +3084,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): os.unlink(key_file) - self.cmd('key', 'import', self.repository_location, export_file) + self.cmd(f'--repo={self.repository_location}', 'key', 'import', export_file) with open(key_file) as fd: key_contents2 = fd.read() @@ -3096,10 +3092,10 @@ class ArchiverTestCase(ArchiverTestCaseBase): assert key_contents2 == key_contents def test_key_import_keyfile_with_borg_key_file(self): - self.cmd('init', self.repository_location, '--encryption', 'keyfile') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption', 'keyfile') exported_key_file = os.path.join(self.output_path, 'exported') - self.cmd('key', 'export', self.repository_location, exported_key_file) + self.cmd(f'--repo={self.repository_location}', 'key', 'export', exported_key_file) key_file = os.path.join(self.keys_path, os.listdir(self.keys_path)[0]) with open(key_file) as fd: @@ -3108,7 +3104,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): imported_key_file = os.path.join(self.output_path, 'imported') with environment_variable(BORG_KEY_FILE=imported_key_file): - self.cmd('key', 'import', self.repository_location, exported_key_file) + self.cmd(f'--repo={self.repository_location}', 'key', 'import', exported_key_file) assert not os.path.isfile(key_file), '"borg key import" should respect BORG_KEY_FILE' with open(imported_key_file) as fd: @@ -3117,9 +3113,9 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_key_export_repokey(self): export_file = self.output_path + '/exported' - self.cmd('init', self.repository_location, '--encryption', 'repokey') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption', 'repokey') repo_id = self._extract_repository_id(self.repository_path) - self.cmd('key', 'export', self.repository_location, export_file) + self.cmd(f'--repo={self.repository_location}', 'key', 'export', export_file) with open(export_file) as fd: export_contents = fd.read() @@ -3138,7 +3134,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): with Repository(self.repository_path) as repository: repository.save_key(b'') - self.cmd('key', 'import', self.repository_location, export_file) + self.cmd(f'--repo={self.repository_location}', 'key', 'import', export_file) with Repository(self.repository_path) as repository: repo_key2 = RepoKey(repository) @@ -3148,9 +3144,9 @@ class ArchiverTestCase(ArchiverTestCaseBase): def test_key_export_qr(self): export_file = self.output_path + '/exported.html' - self.cmd('init', self.repository_location, '--encryption', 'repokey') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption', 'repokey') repo_id = self._extract_repository_id(self.repository_path) - self.cmd('key', 'export', '--qr-html', self.repository_location, export_file) + self.cmd(f'--repo={self.repository_location}', 'key', 'export', '--qr-html', export_file) with open(export_file, encoding='utf-8') as fd: export_contents = fd.read() @@ -3163,39 +3159,39 @@ class ArchiverTestCase(ArchiverTestCaseBase): export_directory = self.output_path + '/exported' os.mkdir(export_directory) - self.cmd('init', self.repository_location, '--encryption', 'repokey') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption', 'repokey') - self.cmd('key', 'export', self.repository_location, export_directory, exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}', 'key', 'export', export_directory, exit_code=EXIT_ERROR) def test_key_import_errors(self): export_file = self.output_path + '/exported' - self.cmd('init', self.repository_location, '--encryption', 'keyfile') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption', 'keyfile') - self.cmd('key', 'import', self.repository_location, export_file, exit_code=EXIT_ERROR) + self.cmd(f'--repo={self.repository_location}', 'key', 'import', export_file, exit_code=EXIT_ERROR) with open(export_file, 'w') as fd: fd.write('something not a key\n') if self.FORK_DEFAULT: - self.cmd('key', 'import', self.repository_location, export_file, exit_code=2) + self.cmd(f'--repo={self.repository_location}', 'key', 'import', export_file, exit_code=2) else: with pytest.raises(NotABorgKeyFile): - self.cmd('key', 'import', self.repository_location, export_file) + self.cmd(f'--repo={self.repository_location}', 'key', 'import', export_file) with open(export_file, 'w') as fd: fd.write('BORG_KEY a0a0a0\n') if self.FORK_DEFAULT: - self.cmd('key', 'import', self.repository_location, export_file, exit_code=2) + self.cmd(f'--repo={self.repository_location}', 'key', 'import', export_file, exit_code=2) else: with pytest.raises(RepoIdMismatch): - self.cmd('key', 'import', self.repository_location, export_file) + self.cmd(f'--repo={self.repository_location}', 'key', 'import', export_file) def test_key_export_paperkey(self): repo_id = 'e294423506da4e1ea76e8dcdf1a3919624ae3ae496fddf905610c351d3f09239' export_file = self.output_path + '/exported' - self.cmd('init', self.repository_location, '--encryption', 'keyfile') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption', 'keyfile') self._set_repository_id(self.repository_path, unhexlify(repo_id)) key_file = self.keys_path + '/' + os.listdir(self.keys_path)[0] @@ -3204,7 +3200,7 @@ class ArchiverTestCase(ArchiverTestCaseBase): fd.write(KeyfileKey.FILE_ID + ' ' + repo_id + '\n') fd.write(b2a_base64(b'abcdefghijklmnopqrstu').decode()) - self.cmd('key', 'export', '--paper', self.repository_location, export_file) + self.cmd(f'--repo={self.repository_location}', 'key', 'export', '--paper', export_file) with open(export_file) as fd: export_contents = fd.read() @@ -3219,7 +3215,7 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 def test_key_import_paperkey(self): repo_id = 'e294423506da4e1ea76e8dcdf1a3919624ae3ae496fddf905610c351d3f09239' - self.cmd('init', self.repository_location, '--encryption', 'keyfile') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption', 'keyfile') self._set_repository_id(self.repository_path, unhexlify(repo_id)) key_file = self.keys_path + '/' + os.listdir(self.keys_path)[0] @@ -3254,20 +3250,20 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 # print(i.to_bytes(2, 'big')) # break - self.cmd('key', 'import', '--paper', self.repository_location, input=typed_input) + self.cmd(f'--repo={self.repository_location}', 'key', 'import', '--paper', input=typed_input) # Test abort paths typed_input = b'\ny\n' - self.cmd('key', 'import', '--paper', self.repository_location, input=typed_input) + self.cmd(f'--repo={self.repository_location}', 'key', 'import', '--paper', input=typed_input) typed_input = b'2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02\n\ny\n' - self.cmd('key', 'import', '--paper', self.repository_location, input=typed_input) + self.cmd(f'--repo={self.repository_location}', 'key', 'import', '--paper', input=typed_input) def test_debug_dump_manifest(self): self.create_regular_file('file1', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') dump_file = self.output_path + '/dump' - output = self.cmd('debug', 'dump-manifest', self.repository_location, dump_file) + output = self.cmd(f'--repo={self.repository_location}', 'debug', 'dump-manifest', dump_file) assert output == "" with open(dump_file) as f: result = json.load(f) @@ -3279,10 +3275,10 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 def test_debug_dump_archive(self): self.create_regular_file('file1', size=1024 * 80) - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') dump_file = self.output_path + '/dump' - output = self.cmd('debug', 'dump-archive', self.repository_location + "::test", dump_file) + output = self.cmd(f'--repo={self.repository_location}', 'debug', 'dump-archive', 'test', dump_file) assert output == "" with open(dump_file) as f: result = json.load(f) @@ -3292,17 +3288,17 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 assert '_items' in result def test_debug_refcount_obj(self): - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('debug', 'refcount-obj', self.repository_location, '0' * 64).strip() + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'debug', 'refcount-obj', '0' * 64).strip() assert output == 'object 0000000000000000000000000000000000000000000000000000000000000000 not found [info from chunks cache].' - create_json = json.loads(self.cmd('create', '--json', self.repository_location + '::test', 'input')) + create_json = json.loads(self.cmd(f'--repo={self.repository_location}', 'create', '--json', 'test', 'input')) archive_id = create_json['archive']['id'] - output = self.cmd('debug', 'refcount-obj', self.repository_location, archive_id).strip() + output = self.cmd(f'--repo={self.repository_location}', 'debug', 'refcount-obj', archive_id).strip() assert output == 'object ' + archive_id + ' has 1 referrers [info from chunks cache].' # Invalid IDs do not abort or return an error - output = self.cmd('debug', 'refcount-obj', self.repository_location, '124', 'xyza').strip() + output = self.cmd(f'--repo={self.repository_location}', 'debug', 'refcount-obj', '124', 'xyza').strip() assert output == 'object id 124 is invalid.\nobject id xyza is invalid.' def test_debug_info(self): @@ -3311,15 +3307,15 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 assert 'Python' in output def test_benchmark_crud(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') with environment_variable(_BORG_BENCHMARK_CRUD_TEST='YES'): - self.cmd('benchmark', 'crud', self.repository_location, self.input_path) + self.cmd(f'--repo={self.repository_location}', 'benchmark', 'crud', self.input_path) def test_config(self): self.create_test_files() os.unlink('input/flagfile') - self.cmd('init', '--encryption=repokey', self.repository_location) - output = self.cmd('config', '--list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + output = self.cmd(f'--repo={self.repository_location}', 'config', '--list') self.assert_in('[repository]', output) self.assert_in('version', output) self.assert_in('segments_per_dir', output) @@ -3329,30 +3325,30 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 self.assert_in('id', output) self.assert_not_in('last_segment_checked', output) - output = self.cmd('config', self.repository_location, 'last_segment_checked', exit_code=1) + output = self.cmd(f'--repo={self.repository_location}', 'config', 'last_segment_checked', exit_code=1) self.assert_in('No option ', output) - self.cmd('config', self.repository_location, 'last_segment_checked', '123') - output = self.cmd('config', self.repository_location, 'last_segment_checked') + self.cmd(f'--repo={self.repository_location}', 'config', 'last_segment_checked', '123') + output = self.cmd(f'--repo={self.repository_location}', 'config', 'last_segment_checked') assert output == '123' + '\n' - output = self.cmd('config', '--list', self.repository_location) + output = self.cmd(f'--repo={self.repository_location}', 'config', '--list') self.assert_in('last_segment_checked', output) - self.cmd('config', '--delete', self.repository_location, 'last_segment_checked') + self.cmd(f'--repo={self.repository_location}', 'config', '--delete', 'last_segment_checked') for cfg_key, cfg_value in [ ('additional_free_space', '2G'), ('repository.append_only', '1'), ]: - output = self.cmd('config', self.repository_location, cfg_key) + output = self.cmd(f'--repo={self.repository_location}', 'config', cfg_key) assert output == '0' + '\n' - self.cmd('config', self.repository_location, cfg_key, cfg_value) - output = self.cmd('config', self.repository_location, cfg_key) + self.cmd(f'--repo={self.repository_location}', 'config', cfg_key, cfg_value) + output = self.cmd(f'--repo={self.repository_location}', 'config', cfg_key) assert output == cfg_value + '\n' - self.cmd('config', '--delete', self.repository_location, cfg_key) - self.cmd('config', self.repository_location, cfg_key, exit_code=1) + self.cmd(f'--repo={self.repository_location}', 'config', '--delete', cfg_key) + self.cmd(f'--repo={self.repository_location}', 'config', cfg_key, exit_code=1) - self.cmd('config', '--list', '--delete', self.repository_location, exit_code=2) - self.cmd('config', self.repository_location, exit_code=2) - self.cmd('config', self.repository_location, 'invalid-option', exit_code=1) + self.cmd(f'--repo={self.repository_location}', 'config', '--list', '--delete', exit_code=2) + self.cmd(f'--repo={self.repository_location}', 'config', exit_code=2) + self.cmd(f'--repo={self.repository_location}', 'config', 'invalid-option', exit_code=1) requires_gnutar = pytest.mark.skipif(not have_gnutar(), reason='GNU tar must be installed for this test.') requires_gzip = pytest.mark.skipif(not shutil.which('gzip'), reason='gzip must be installed for this test.') @@ -3361,9 +3357,9 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 def test_export_tar(self): self.create_test_files() os.unlink('input/flagfile') - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') - self.cmd('export-tar', self.repository_location + '::test', 'simple.tar', '--progress', '--tar-format=GNU') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + self.cmd(f'--repo={self.repository_location}', 'export-tar', 'test', 'simple.tar', '--progress', '--tar-format=GNU') with changedir('output'): # This probably assumes GNU tar. Note -p switch to extract permissions regardless of umask. subprocess.check_call(['tar', 'xpf', '../simple.tar', '--warning=no-timestamp']) @@ -3376,9 +3372,9 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 pytest.skip('gzip is not installed') self.create_test_files() os.unlink('input/flagfile') - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') - list = self.cmd('export-tar', self.repository_location + '::test', 'simple.tar.gz', + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + list = self.cmd(f'--repo={self.repository_location}', 'export-tar', 'test', 'simple.tar.gz', '--list', '--tar-format=GNU') assert 'input/file1\n' in list assert 'input/dir2\n' in list @@ -3392,9 +3388,9 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 pytest.skip('gzip is not installed') self.create_test_files() os.unlink('input/flagfile') - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') - list = self.cmd('export-tar', self.repository_location + '::test', 'simple.tar', + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') + list = self.cmd(f'--repo={self.repository_location}', 'export-tar', 'test', 'simple.tar', '--strip-components=1', '--list', '--tar-format=GNU') # --list's path are those before processing with --strip-components assert 'input/file1\n' in list @@ -3407,7 +3403,7 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 @requires_gnutar def test_export_tar_strip_components_links(self): self._extract_hardlinks_setup() - self.cmd('export-tar', self.repository_location + '::test', 'output.tar', + self.cmd(f'--repo={self.repository_location}', 'export-tar', 'test', 'output.tar', '--strip-components=2', '--tar-format=GNU') with changedir('output'): subprocess.check_call(['tar', 'xpf', '../output.tar', '--warning=no-timestamp']) @@ -3420,7 +3416,7 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 @requires_gnutar def test_extract_hardlinks_tar(self): self._extract_hardlinks_setup() - self.cmd('export-tar', self.repository_location + '::test', 'output.tar', 'input/dir1', '--tar-format=GNU') + self.cmd(f'--repo={self.repository_location}', 'export-tar', 'test', 'output.tar', 'input/dir1', '--tar-format=GNU') with changedir('output'): subprocess.check_call(['tar', 'xpf', '../output.tar', '--warning=no-timestamp']) assert os.stat('input/dir1/hardlink').st_nlink == 2 @@ -3431,12 +3427,12 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 def test_import_tar(self, tar_format='PAX'): self.create_test_files(create_hardlinks=False) # hardlinks become separate files os.unlink('input/flagfile') - self.cmd('init', '--encryption=none', self.repository_location) - self.cmd('create', self.repository_location + '::src', 'input') - self.cmd('export-tar', self.repository_location + '::src', 'simple.tar', f'--tar-format={tar_format}') - self.cmd('import-tar', self.repository_location + '::dst', 'simple.tar') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=none') + self.cmd(f'--repo={self.repository_location}', 'create', 'src', 'input') + self.cmd(f'--repo={self.repository_location}', 'export-tar', 'src', 'simple.tar', f'--tar-format={tar_format}') + self.cmd(f'--repo={self.repository_location}', 'import-tar', 'dst', 'simple.tar') with changedir(self.output_path): - self.cmd('extract', self.repository_location + '::dst') + self.cmd(f'--repo={self.repository_location}', 'extract', 'dst') self.assert_dirs_equal('input', 'output/input', ignore_ns=True, ignore_xattrs=True) @requires_gzip @@ -3445,22 +3441,22 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 pytest.skip('gzip is not installed') self.create_test_files(create_hardlinks=False) # hardlinks become separate files os.unlink('input/flagfile') - self.cmd('init', '--encryption=none', self.repository_location) - self.cmd('create', self.repository_location + '::src', 'input') - self.cmd('export-tar', self.repository_location + '::src', 'simple.tgz', f'--tar-format={tar_format}') - self.cmd('import-tar', self.repository_location + '::dst', 'simple.tgz') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=none') + self.cmd(f'--repo={self.repository_location}', 'create', 'src', 'input') + self.cmd(f'--repo={self.repository_location}', 'export-tar', 'src', 'simple.tgz', f'--tar-format={tar_format}') + self.cmd(f'--repo={self.repository_location}', 'import-tar', 'dst', 'simple.tgz') with changedir(self.output_path): - self.cmd('extract', self.repository_location + '::dst') + self.cmd(f'--repo={self.repository_location}', 'extract', 'dst') self.assert_dirs_equal('input', 'output/input', ignore_ns=True, ignore_xattrs=True) def test_roundtrip_pax_borg(self): self.create_test_files() - self.cmd('init', '--encryption=none', self.repository_location) - self.cmd('create', self.repository_location + '::src', 'input') - self.cmd('export-tar', self.repository_location + '::src', 'simple.tar', '--tar-format=BORG') - self.cmd('import-tar', self.repository_location + '::dst', 'simple.tar') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=none') + self.cmd(f'--repo={self.repository_location}', 'create', 'src', 'input') + self.cmd(f'--repo={self.repository_location}', 'export-tar', 'src', 'simple.tar', '--tar-format=BORG') + self.cmd(f'--repo={self.repository_location}', 'import-tar', 'dst', 'simple.tar') with changedir(self.output_path): - self.cmd('extract', self.repository_location + '::dst') + self.cmd(f'--repo={self.repository_location}', 'extract', 'dst') self.assert_dirs_equal('input', 'output/input') # derived from test_extract_xattrs_errors() @@ -3473,11 +3469,11 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 self.create_regular_file('file') xattr.setxattr(b'input/file', b'user.attribute%p', b'value') - self.cmd('init', self.repository_location, '-e' 'none') - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '-e' 'none') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir('output'): with patch.object(xattr, 'setxattr', patched_setxattr_EACCES): - self.cmd('extract', self.repository_location + '::test', exit_code=EXIT_WARNING) + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', exit_code=EXIT_WARNING) # derived from test_extract_xattrs_errors() @pytest.mark.skipif(not xattr.XATTR_FAKEROOT, reason='xattr not supported on this system or on this version of' @@ -3489,16 +3485,16 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 os.makedirs(os.path.join(self.input_path, 'dir%p')) xattr.setxattr(b'input/dir%p', b'user.attribute', b'value') - self.cmd('init', self.repository_location, '-e' 'none') - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '-e' 'none') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') with changedir('output'): with patch.object(xattr, 'setxattr', patched_setxattr_EACCES): - self.cmd('extract', self.repository_location + '::test', exit_code=EXIT_WARNING) + self.cmd(f'--repo={self.repository_location}', 'extract', 'test', exit_code=EXIT_WARNING) def test_do_not_mention_archive_if_you_can_not_find_repo(self): """https://github.com/borgbackup/borg/issues/6014""" - archive = self.repository_location + '-this-repository-does-not-exist' + '::test' - output = self.cmd('info', archive, exit_code=2, fork=True) + output = self.cmd(f'--repo={self.repository_location}-this-repository-does-not-exist', 'info', '-a', 'test', + exit_code=2, fork=True) self.assert_in('this-repository-does-not-exist', output) self.assert_not_in('this-repository-does-not-exist::test', output) @@ -3508,8 +3504,8 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 It should be possible to retrieve the data from an archive even if both the client and the server forget the nonce""" self.create_regular_file('file1', contents=b'Hello, borg') - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') # Oops! We have removed the repo-side memory of the nonce! # See https://github.com/borgbackup/borg/issues/5858 os.remove(os.path.join(self.repository_path, 'nonce')) @@ -3517,18 +3513,18 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 os.remove(os.path.join(self.get_security_dir(), 'nonce')) # The repo should still be readable - repo_info = self.cmd('info', self.repository_location) + repo_info = self.cmd(f'--repo={self.repository_location}', 'rinfo') assert 'All archives:' in repo_info - repo_list = self.cmd('list', self.repository_location) + repo_list = self.cmd(f'--repo={self.repository_location}', 'rlist') assert 'test' in repo_list # The archive should still be readable - archive_info = self.cmd('info', self.repository_location + '::test') + archive_info = self.cmd(f'--repo={self.repository_location}', 'info', '-a', 'test') assert 'Archive name: test\n' in archive_info - archive_list = self.cmd('list', self.repository_location + '::test') + archive_list = self.cmd(f'--repo={self.repository_location}', 'list', 'test') assert 'file1' in archive_list # Extracting the archive should work with changedir('output'): - self.cmd('extract', self.repository_location + '::test') + self.cmd(f'--repo={self.repository_location}', 'extract', 'test') self.assert_dirs_equal('input', 'output/input') def test_recovery_from_deleted_repo_nonce(self): @@ -3539,35 +3535,35 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 repo. Otherwise we can just use our own copy of the nonce. """ self.create_regular_file('file1', contents=b'Hello, borg') - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') # Oops! We have removed the repo-side memory of the nonce! # See https://github.com/borgbackup/borg/issues/5858 nonce = os.path.join(self.repository_path, 'nonce') os.remove(nonce) - self.cmd('create', self.repository_location + '::test2', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test2', 'input') assert os.path.exists(nonce) def test_init_defaults_to_argon2(self): """https://github.com/borgbackup/borg/issues/747#issuecomment-1076160401""" - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') with Repository(self.repository_path) as repository: key = msgpack.unpackb(a2b_base64(repository.load_key())) assert key['algorithm'] == 'argon2 chacha20-poly1305' def test_init_with_explicit_key_algorithm(self): """https://github.com/borgbackup/borg/issues/747#issuecomment-1076160401""" - self.cmd('init', '--encryption=repokey', '--key-algorithm=pbkdf2', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey', '--key-algorithm=pbkdf2') with Repository(self.repository_path) as repository: key = msgpack.unpackb(a2b_base64(repository.load_key())) assert key['algorithm'] == 'sha256' def verify_change_passphrase_does_not_change_algorithm(self, given_algorithm, expected_algorithm): - self.cmd('init', '--encryption=repokey', '--key-algorithm', given_algorithm, self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey', '--key-algorithm', given_algorithm) os.environ['BORG_NEW_PASSPHRASE'] = 'newpassphrase' - self.cmd('key', 'change-passphrase', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'key', 'change-passphrase') with Repository(self.repository_path) as repository: key = msgpack.unpackb(a2b_base64(repository.load_key())) @@ -3580,9 +3576,9 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 self.verify_change_passphrase_does_not_change_algorithm('pbkdf2', 'sha256') def verify_change_location_does_not_change_algorithm(self, given_algorithm, expected_algorithm): - self.cmd('init', '--encryption=keyfile', '--key-algorithm', given_algorithm, self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=keyfile', '--key-algorithm', given_algorithm) - self.cmd('key', 'change-location', self.repository_location, 'repokey') + self.cmd(f'--repo={self.repository_location}', 'key', 'change-location', 'repokey') with Repository(self.repository_path) as repository: key = msgpack.unpackb(a2b_base64(repository.load_key())) @@ -3595,14 +3591,14 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02 self.verify_change_location_does_not_change_algorithm('pbkdf2', 'sha256') def test_key_change_algorithm(self): - self.cmd('init', '--encryption=repokey', '--key-algorithm=pbkdf2', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey', '--key-algorithm=pbkdf2') - self.cmd('key', 'change-algorithm', self.repository_location, 'argon2') + self.cmd(f'--repo={self.repository_location}', 'key', 'change-algorithm', 'argon2') with Repository(self.repository_path) as repository: _, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK) assert key._encrypted_key_algorithm == 'argon2 chacha20-poly1305' - self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rinfo') @unittest.skipUnless('binary' in BORG_EXES, 'no borg.exe available') @@ -3654,30 +3650,30 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase): def setUp(self): super().setUp() with patch.object(ChunkBuffer, 'BUFFER_SIZE', 10): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('archive1') self.create_src_archive('archive2') def test_check_usage(self): - output = self.cmd('check', '-v', '--progress', self.repository_location, exit_code=0) + output = self.cmd(f'--repo={self.repository_location}', 'check', '-v', '--progress', exit_code=0) self.assert_in('Starting repository check', output) self.assert_in('Starting archive consistency check', output) self.assert_in('Checking segments', output) # reset logging to new process default to avoid need for fork=True on next check logging.getLogger('borg.output.progress').setLevel(logging.NOTSET) - output = self.cmd('check', '-v', '--repository-only', self.repository_location, exit_code=0) + output = self.cmd(f'--repo={self.repository_location}', 'check', '-v', '--repository-only', exit_code=0) self.assert_in('Starting repository check', output) self.assert_not_in('Starting archive consistency check', output) self.assert_not_in('Checking segments', output) - output = self.cmd('check', '-v', '--archives-only', self.repository_location, exit_code=0) + output = self.cmd(f'--repo={self.repository_location}', 'check', '-v', '--archives-only', exit_code=0) self.assert_not_in('Starting repository check', output) self.assert_in('Starting archive consistency check', output) - output = self.cmd('check', '-v', '--archives-only', '--prefix=archive2', self.repository_location, exit_code=0) + output = self.cmd(f'--repo={self.repository_location}', 'check', '-v', '--archives-only', '--prefix=archive2', exit_code=0) self.assert_not_in('archive1', output) - output = self.cmd('check', '-v', '--archives-only', '--first=1', self.repository_location, exit_code=0) + output = self.cmd(f'--repo={self.repository_location}', 'check', '-v', '--archives-only', '--first=1', exit_code=0) self.assert_in('archive1', output) self.assert_not_in('archive2', output) - output = self.cmd('check', '-v', '--archives-only', '--last=1', self.repository_location, exit_code=0) + output = self.cmd(f'--repo={self.repository_location}', 'check', '-v', '--archives-only', '--last=1', exit_code=0) self.assert_not_in('archive1', output) self.assert_in('archive2', output) @@ -3693,11 +3689,11 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase): else: self.fail('should not happen') repository.commit(compact=False) - self.cmd('check', self.repository_location, exit_code=1) - output = self.cmd('check', '--repair', self.repository_location, exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=1) + output = self.cmd(f'--repo={self.repository_location}', 'check', '--repair', exit_code=0) self.assert_in('New missing file chunk detected', output) - self.cmd('check', self.repository_location, exit_code=0) - output = self.cmd('list', '--format={health}#{path}{LF}', self.repository_location + '::archive1', exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=0) + output = self.cmd(f'--repo={self.repository_location}', 'list', 'archive1', '--format={health}#{path}{LF}', exit_code=0) self.assert_in('broken#', output) # check that the file in the old archives has now a different chunk list without the killed chunk for archive_name in ('archive1', 'archive2'): @@ -3714,7 +3710,7 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase): with patch.object(ChunkBuffer, 'BUFFER_SIZE', 10): self.create_src_archive('archive3') # check should be able to heal the file now: - output = self.cmd('check', '-v', '--repair', self.repository_location, exit_code=0) + output = self.cmd(f'--repo={self.repository_location}', 'check', '-v', '--repair', exit_code=0) self.assert_in('Healed previously missing file chunk', output) self.assert_in('testsuite/archiver.py: Completely healed previously damaged file!', output) # check that the file in the old archives has the correct chunks again @@ -3728,7 +3724,7 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase): else: self.fail('should not happen') # list is also all-healthy again - output = self.cmd('list', '--format={health}#{path}{LF}', self.repository_location + '::archive1', exit_code=0) + output = self.cmd(f'--repo={self.repository_location}', 'list', 'archive1', '--format={health}#{path}{LF}', exit_code=0) self.assert_not_in('broken#', output) def test_missing_archive_item_chunk(self): @@ -3736,29 +3732,29 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase): with repository: repository.delete(archive.metadata.items[0]) repository.commit(compact=False) - self.cmd('check', self.repository_location, exit_code=1) - self.cmd('check', '--repair', self.repository_location, exit_code=0) - self.cmd('check', self.repository_location, exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=1) + self.cmd(f'--repo={self.repository_location}', 'check', '--repair', exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=0) def test_missing_archive_metadata(self): archive, repository = self.open_archive('archive1') with repository: repository.delete(archive.id) repository.commit(compact=False) - self.cmd('check', self.repository_location, exit_code=1) - self.cmd('check', '--repair', self.repository_location, exit_code=0) - self.cmd('check', self.repository_location, exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=1) + self.cmd(f'--repo={self.repository_location}', 'check', '--repair', exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=0) def test_missing_manifest(self): archive, repository = self.open_archive('archive1') with repository: repository.delete(Manifest.MANIFEST_ID) repository.commit(compact=False) - self.cmd('check', self.repository_location, exit_code=1) - output = self.cmd('check', '-v', '--repair', self.repository_location, exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=1) + output = self.cmd(f'--repo={self.repository_location}', 'check', '-v', '--repair', exit_code=0) self.assert_in('archive1', output) self.assert_in('archive2', output) - self.cmd('check', self.repository_location, exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=0) def test_corrupted_manifest(self): archive, repository = self.open_archive('archive1') @@ -3767,11 +3763,11 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase): corrupted_manifest = manifest + b'corrupted!' repository.put(Manifest.MANIFEST_ID, corrupted_manifest) repository.commit(compact=False) - self.cmd('check', self.repository_location, exit_code=1) - output = self.cmd('check', '-v', '--repair', self.repository_location, exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=1) + output = self.cmd(f'--repo={self.repository_location}', 'check', '-v', '--repair', exit_code=0) self.assert_in('archive1', output) self.assert_in('archive2', output) - self.cmd('check', self.repository_location, exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=0) def test_manifest_rebuild_corrupted_chunk(self): archive, repository = self.open_archive('archive1') @@ -3784,10 +3780,10 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase): corrupted_chunk = chunk + b'corrupted!' repository.put(archive.id, corrupted_chunk) repository.commit(compact=False) - self.cmd('check', self.repository_location, exit_code=1) - output = self.cmd('check', '-v', '--repair', self.repository_location, exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=1) + output = self.cmd(f'--repo={self.repository_location}', 'check', '-v', '--repair', exit_code=0) self.assert_in('archive2', output) - self.cmd('check', self.repository_location, exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=0) def test_manifest_rebuild_duplicate_archive(self): archive, repository = self.open_archive('archive1') @@ -3809,27 +3805,27 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase): archive_id = key.id_hash(archive) repository.put(archive_id, key.encrypt(archive_id, archive)) repository.commit(compact=False) - self.cmd('check', self.repository_location, exit_code=1) - self.cmd('check', '--repair', self.repository_location, exit_code=0) - output = self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=1) + self.cmd(f'--repo={self.repository_location}', 'check', '--repair', exit_code=0) + output = self.cmd(f'--repo={self.repository_location}', 'rlist') self.assert_in('archive1', output) self.assert_in('archive1.1', output) self.assert_in('archive2', output) def test_extra_chunks(self): - self.cmd('check', self.repository_location, exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=0) with Repository(self.repository_location, exclusive=True) as repository: repository.put(b'01234567890123456789012345678901', b'xxxx') repository.commit(compact=False) - self.cmd('check', self.repository_location, exit_code=1) - self.cmd('check', self.repository_location, exit_code=1) - self.cmd('check', '--repair', self.repository_location, exit_code=0) - self.cmd('check', self.repository_location, exit_code=0) - self.cmd('extract', '--dry-run', self.repository_location + '::archive1', exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=1) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=1) + self.cmd(f'--repo={self.repository_location}', 'check', '--repair', exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=0) + self.cmd(f'--repo={self.repository_location}', 'extract', 'archive1', '--dry-run', exit_code=0) def _test_verify_data(self, *init_args): shutil.rmtree(self.repository_path) - self.cmd('init', self.repository_location, *init_args) + self.cmd(f'--repo={self.repository_location}', 'rcreate', *init_args) self.create_src_archive('archive1') archive, repository = self.open_archive('archive1') with repository: @@ -3840,11 +3836,11 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase): repository.put(chunk.id, data) break repository.commit(compact=False) - self.cmd('check', self.repository_location, exit_code=0) - output = self.cmd('check', '--verify-data', self.repository_location, exit_code=1) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=0) + output = self.cmd(f'--repo={self.repository_location}', 'check', '--verify-data', exit_code=1) assert bin_to_hex(chunk.id) + ', integrity error' in output # repair (heal is tested in another test) - output = self.cmd('check', '--repair', '--verify-data', self.repository_location, exit_code=0) + output = self.cmd(f'--repo={self.repository_location}', 'check', '--repair', '--verify-data', exit_code=0) assert bin_to_hex(chunk.id) + ', integrity error' in output assert 'testsuite/archiver.py: New missing file chunk detected' in output @@ -3859,7 +3855,7 @@ class ArchiverCheckTestCase(ArchiverTestCaseBase): for id_ in repository.list(): repository.delete(id_) repository.commit(compact=False) - self.cmd('check', self.repository_location, exit_code=1) + self.cmd(f'--repo={self.repository_location}', 'check', exit_code=1) class ManifestAuthenticationTest(ArchiverTestCaseBase): @@ -3875,7 +3871,7 @@ class ManifestAuthenticationTest(ArchiverTestCaseBase): repository.commit(compact=False) def test_fresh_init_tam_required(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') repository = Repository(self.repository_path, exclusive=True) with repository: manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK) @@ -3887,10 +3883,10 @@ class ManifestAuthenticationTest(ArchiverTestCaseBase): repository.commit(compact=False) with pytest.raises(TAMRequiredError): - self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rlist') def test_not_required(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('archive1234') repository = Repository(self.repository_path, exclusive=True) with repository: @@ -3903,39 +3899,39 @@ class ManifestAuthenticationTest(ArchiverTestCaseBase): del manifest['tam'] repository.put(Manifest.MANIFEST_ID, key.encrypt(Manifest.MANIFEST_ID, msgpack.packb(manifest))) repository.commit(compact=False) - output = self.cmd('list', '--debug', self.repository_location) + output = self.cmd(f'--repo={self.repository_location}', 'rlist', '--debug') assert 'archive1234' in output assert 'TAM not found and not required' in output # Run upgrade - self.cmd('upgrade', '--tam', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'upgrade', '--tam') # Manifest must be authenticated now - output = self.cmd('list', '--debug', self.repository_location) + output = self.cmd(f'--repo={self.repository_location}', 'rlist', '--debug') assert 'archive1234' in output assert 'TAM-verified manifest' in output # Try to spoof / modify pre-1.0.9 self.spoof_manifest(repository) # Fails with pytest.raises(TAMRequiredError): - self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rlist') # Force upgrade - self.cmd('upgrade', '--tam', '--force', self.repository_location) - self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'upgrade', '--tam', '--force') + self.cmd(f'--repo={self.repository_location}', 'rlist') def test_disable(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('archive1234') - self.cmd('upgrade', '--disable-tam', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'upgrade', '--disable-tam') repository = Repository(self.repository_path, exclusive=True) self.spoof_manifest(repository) - assert not self.cmd('list', self.repository_location) + assert not self.cmd(f'--repo={self.repository_location}', 'rlist') def test_disable2(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_src_archive('archive1234') repository = Repository(self.repository_path, exclusive=True) self.spoof_manifest(repository) - self.cmd('upgrade', '--disable-tam', self.repository_location) - assert not self.cmd('list', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'upgrade', '--disable-tam') + assert not self.cmd(f'--repo={self.repository_location}', 'rlist') class RemoteArchiverTestCase(ArchiverTestCase): @@ -3947,32 +3943,32 @@ class RemoteArchiverTestCase(ArchiverTestCase): def test_remote_repo_restrict_to_path(self): # restricted to repo directory itself: with patch.object(RemoteRepository, 'extra_test_args', ['--restrict-to-path', self.repository_path]): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') # restricted to repo directory itself, fail for other directories with same prefix: with patch.object(RemoteRepository, 'extra_test_args', ['--restrict-to-path', self.repository_path]): with pytest.raises(PathNotAllowed): - self.cmd('init', '--encryption=repokey', self.repository_location + '_0') + self.cmd(f'--repo={self.repository_location}_0', 'rcreate', '--encryption=repokey') # restricted to a completely different path: with patch.object(RemoteRepository, 'extra_test_args', ['--restrict-to-path', '/foo']): with pytest.raises(PathNotAllowed): - self.cmd('init', '--encryption=repokey', self.repository_location + '_1') + self.cmd(f'--repo={self.repository_location}_1', 'rcreate', '--encryption=repokey') path_prefix = os.path.dirname(self.repository_path) # restrict to repo directory's parent directory: with patch.object(RemoteRepository, 'extra_test_args', ['--restrict-to-path', path_prefix]): - self.cmd('init', '--encryption=repokey', self.repository_location + '_2') + self.cmd(f'--repo={self.repository_location}_2', 'rcreate', '--encryption=repokey') # restrict to repo directory's parent directory and another directory: with patch.object(RemoteRepository, 'extra_test_args', ['--restrict-to-path', '/foo', '--restrict-to-path', path_prefix]): - self.cmd('init', '--encryption=repokey', self.repository_location + '_3') + self.cmd(f'--repo={self.repository_location}_3', 'rcreate', '--encryption=repokey') def test_remote_repo_restrict_to_repository(self): # restricted to repo directory itself: with patch.object(RemoteRepository, 'extra_test_args', ['--restrict-to-repository', self.repository_path]): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') parent_path = os.path.join(self.repository_path, '..') with patch.object(RemoteRepository, 'extra_test_args', ['--restrict-to-repository', parent_path]): with pytest.raises(PathNotAllowed): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') @unittest.skip('only works locally') def test_debug_put_get_delete_obj(self): @@ -3987,25 +3983,25 @@ class RemoteArchiverTestCase(ArchiverTestCase): pass def test_remote_repo_strip_components_doesnt_leak(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('dir/file', contents=b"test file contents 1") self.create_regular_file('dir/file2', contents=b"test file contents 2") self.create_regular_file('skipped-file1', contents=b"test file contents 3") self.create_regular_file('skipped-file2', contents=b"test file contents 4") self.create_regular_file('skipped-file3', contents=b"test file contents 5") - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') marker = 'cached responses left in RemoteRepository' with changedir('output'): - res = self.cmd('extract', "--debug", self.repository_location + '::test', '--strip-components', '3') + res = self.cmd(f'--repo={self.repository_location}', 'extract', 'test', "--debug", '--strip-components', '3') assert marker not in res with self.assert_creates_file('file'): - res = self.cmd('extract', "--debug", self.repository_location + '::test', '--strip-components', '2') + res = self.cmd(f'--repo={self.repository_location}', 'extract', 'test', "--debug", '--strip-components', '2') assert marker not in res with self.assert_creates_file('dir/file'): - res = self.cmd('extract', "--debug", self.repository_location + '::test', '--strip-components', '1') + res = self.cmd(f'--repo={self.repository_location}', 'extract', 'test', "--debug", '--strip-components', '1') assert marker not in res with self.assert_creates_file('input/dir/file'): - res = self.cmd('extract', "--debug", self.repository_location + '::test', '--strip-components', '0') + res = self.cmd(f'--repo={self.repository_location}', 'extract', 'test', "--debug", '--strip-components', '0') assert marker not in res @@ -4013,8 +4009,8 @@ class ArchiverCorruptionTestCase(ArchiverTestCaseBase): def setUp(self): super().setUp() self.create_test_files() - self.cmd('init', '--encryption=repokey', self.repository_location) - self.cache_path = json.loads(self.cmd('info', self.repository_location, '--json'))['cache']['path'] + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') + self.cache_path = json.loads(self.cmd(f'--repo={self.repository_location}', 'rinfo', '--json'))['cache']['path'] def corrupt(self, file, amount=1): with open(file, 'r+b') as fd: @@ -4027,28 +4023,28 @@ class ArchiverCorruptionTestCase(ArchiverTestCaseBase): self.corrupt(os.path.join(self.cache_path, 'chunks')) if self.FORK_DEFAULT: - out = self.cmd('info', self.repository_location, exit_code=2) + out = self.cmd(f'--repo={self.repository_location}', 'rinfo', exit_code=2) assert 'failed integrity check' in out else: with pytest.raises(FileIntegrityError): - self.cmd('info', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rinfo') def test_cache_files(self): - self.cmd('create', self.repository_location + '::test', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test', 'input') self.corrupt(os.path.join(self.cache_path, 'files')) - out = self.cmd('create', self.repository_location + '::test1', 'input') + out = self.cmd(f'--repo={self.repository_location}', 'create', 'test1', 'input') # borg warns about the corrupt files cache, but then continues without files cache. assert 'files cache is corrupted' in out def test_chunks_archive(self): - self.cmd('create', self.repository_location + '::test1', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test1', 'input') # Find ID of test1 so we can corrupt it later :) - target_id = self.cmd('list', self.repository_location, '--format={id}{LF}').strip() - self.cmd('create', self.repository_location + '::test2', 'input') + target_id = self.cmd(f'--repo={self.repository_location}', 'rlist', '--format={id}{LF}').strip() + self.cmd(f'--repo={self.repository_location}', 'create', 'test2', 'input') # Force cache sync, creating archive chunks of test1 and test2 in chunks.archive.d - self.cmd('delete', '--cache-only', self.repository_location) - self.cmd('info', self.repository_location, '--json') + self.cmd(f'--repo={self.repository_location}', 'rdelete', '--cache-only') + self.cmd(f'--repo={self.repository_location}', 'rinfo', '--json') chunks_archive = os.path.join(self.cache_path, 'chunks.archive.d') assert len(os.listdir(chunks_archive)) == 4 # two archives, one chunks cache and one .integrity file each @@ -4064,7 +4060,7 @@ class ArchiverCorruptionTestCase(ArchiverTestCaseBase): config.write(fd) # Cache sync notices corrupted archive chunks, but automatically recovers. - out = self.cmd('create', '-v', self.repository_location + '::test3', 'input', exit_code=1) + out = self.cmd(f'--repo={self.repository_location}', 'create', '-v', 'test3', 'input', exit_code=1) assert 'Reading cached archive chunk index for test1' in out assert 'Cached archive chunk index of test1 is corrupted' in out assert 'Fetching and building archive index for test1' in out @@ -4079,7 +4075,7 @@ class ArchiverCorruptionTestCase(ArchiverTestCaseBase): with open(config_path, 'w') as fd: config.write(fd) - out = self.cmd('info', self.repository_location) + out = self.cmd(f'--repo={self.repository_location}', 'rinfo') assert 'Cache integrity data not available: old Borg version modified the cache.' in out @@ -4107,10 +4103,10 @@ class DiffArchiverTestCase(ArchiverTestCaseBase): os.link('input/file_removed', 'input/hardlink_removed') os.link('input/file_removed2', 'input/hardlink_target_removed') - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') # Create the first snapshot - self.cmd('create', self.repository_location + '::test0', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test0', 'input') # Setup files for the second snapshot self.create_regular_file('file_added', size=2048) @@ -4141,8 +4137,8 @@ class DiffArchiverTestCase(ArchiverTestCaseBase): fd.write(b'appended_data') # Create the second snapshot - self.cmd('create', self.repository_location + '::test1a', 'input') - self.cmd('create', '--chunker-params', '16,18,17,4095', self.repository_location + '::test1b', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test1a', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test1b', 'input', '--chunker-params', '16,18,17,4095') def do_asserts(output, can_compare_ids): # File contents changed (deleted and replaced with a new file) @@ -4287,19 +4283,19 @@ class DiffArchiverTestCase(ArchiverTestCaseBase): if are_hardlinks_supported(): assert not any(get_changes('input/hardlink_target_replaced', joutput)) - do_asserts(self.cmd('diff', self.repository_location + '::test0', 'test1a'), True) + do_asserts(self.cmd(f'--repo={self.repository_location}', 'diff', 'test0', 'test1a'), True) # We expect exit_code=1 due to the chunker params warning - do_asserts(self.cmd('diff', self.repository_location + '::test0', 'test1b', exit_code=1), False) - do_json_asserts(self.cmd('diff', self.repository_location + '::test0', 'test1a', '--json-lines'), True) + do_asserts(self.cmd(f'--repo={self.repository_location}', 'diff', 'test0', 'test1b', exit_code=1), False) + do_json_asserts(self.cmd(f'--repo={self.repository_location}', 'diff', 'test0', 'test1a', '--json-lines'), True) def test_sort_option(self): - self.cmd('init', '--encryption=repokey', self.repository_location) + self.cmd(f'--repo={self.repository_location}', 'rcreate', '--encryption=repokey') self.create_regular_file('a_file_removed', size=8) self.create_regular_file('f_file_removed', size=16) self.create_regular_file('c_file_changed', size=32) self.create_regular_file('e_file_changed', size=64) - self.cmd('create', self.repository_location + '::test0', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test0', 'input') os.unlink('input/a_file_removed') os.unlink('input/f_file_removed') @@ -4309,9 +4305,10 @@ class DiffArchiverTestCase(ArchiverTestCaseBase): self.create_regular_file('e_file_changed', size=1024) self.create_regular_file('b_file_added', size=128) self.create_regular_file('d_file_added', size=256) - self.cmd('create', self.repository_location + '::test1', 'input') + self.cmd(f'--repo={self.repository_location}', 'create', 'test1', 'input') - output = self.cmd('diff', '--sort', self.repository_location + '::test0', 'test1') + output = self.cmd(f'--repo={self.repository_location}', 'diff', 'test0', 'test1', + '--sort') expected = [ 'a_file_removed', 'b_file_added', @@ -4353,7 +4350,7 @@ def test_get_args(): assert args.restrict_to_repositories == ['/r1', '/r2'] # trying to cheat - try to execute different subcommand args = archiver.get_args(['borg', 'serve', '--restrict-to-path=/p1', '--restrict-to-path=/p2', ], - 'borg init --encryption=repokey /') + 'borg --repo=/ rcreate --encryption=repokey') assert args.func == archiver.do_serve # Check that environment variables in the forced command don't cause issues. If the command diff --git a/src/borg/testsuite/benchmark.py b/src/borg/testsuite/benchmark.py index f3ec06f2a..b23a7d800 100644 --- a/src/borg/testsuite/benchmark.py +++ b/src/borg/testsuite/benchmark.py @@ -28,7 +28,7 @@ def repo_url(request, tmpdir, monkeypatch): @pytest.fixture(params=["none", "repokey"]) def repo(request, cmd, repo_url): - cmd('init', '--encryption', request.param, repo_url) + cmd(f'--repo={repo_url}', 'rcreate', '--encryption', request.param) return repo_url @@ -55,46 +55,52 @@ def testdata(request, tmpdir_factory): @pytest.fixture(params=['none', 'lz4']) -def archive(request, cmd, repo, testdata): - archive_url = repo + '::test' - cmd('create', '--compression', request.param, archive_url, testdata) - return archive_url +def repo_archive(request, cmd, repo, testdata): + archive = 'test' + cmd(f'--repo={repo}', 'create', '--compression', request.param, archive, testdata) + return repo, archive def test_create_none(benchmark, cmd, repo, testdata): - result, out = benchmark.pedantic(cmd, ('create', '--compression', 'none', repo + '::test', testdata)) + result, out = benchmark.pedantic(cmd, (f'--repo={repo}', 'create', '--compression', 'none', + 'test', testdata)) assert result == 0 def test_create_lz4(benchmark, cmd, repo, testdata): - result, out = benchmark.pedantic(cmd, ('create', '--compression', 'lz4', repo + '::test', testdata)) + result, out = benchmark.pedantic(cmd, (f'--repo={repo}', 'create', '--compression', 'lz4', + 'test', testdata)) assert result == 0 -def test_extract(benchmark, cmd, archive, tmpdir): +def test_extract(benchmark, cmd, repo_archive, tmpdir): + repo, archive = repo_archive with changedir(str(tmpdir)): - result, out = benchmark.pedantic(cmd, ('extract', archive)) + result, out = benchmark.pedantic(cmd, (f'--repo={repo}', 'extract', archive)) assert result == 0 -def test_delete(benchmark, cmd, archive): - result, out = benchmark.pedantic(cmd, ('delete', archive)) +def test_delete(benchmark, cmd, repo_archive): + repo, archive = repo_archive + result, out = benchmark.pedantic(cmd, (f'--repo={repo}', 'delete', '-a', archive)) assert result == 0 -def test_list(benchmark, cmd, archive): - result, out = benchmark(cmd, 'list', archive) +def test_list(benchmark, cmd, repo_archive): + repo, archive = repo_archive + result, out = benchmark(cmd, f'--repo={repo}', 'list', archive) assert result == 0 -def test_info(benchmark, cmd, archive): - result, out = benchmark(cmd, 'info', archive) +def test_info(benchmark, cmd, repo_archive): + repo, archive = repo_archive + result, out = benchmark(cmd, f'--repo={repo}', 'info', '-a', archive) assert result == 0 -def test_check(benchmark, cmd, archive): - repo = archive.split('::')[0] - result, out = benchmark(cmd, 'check', repo) +def test_check(benchmark, cmd, repo_archive): + repo, archive = repo_archive + result, out = benchmark(cmd, f'--repo={repo}', 'check') assert result == 0 diff --git a/src/borg/testsuite/helpers.py b/src/borg/testsuite/helpers.py index fd0414116..52bbd207b 100644 --- a/src/borg/testsuite/helpers.py +++ b/src/borg/testsuite/helpers.py @@ -54,65 +54,63 @@ class TestLocationWithoutEnv: def test_ssh(self, monkeypatch, keys_dir): monkeypatch.delenv('BORG_REPO', raising=False) - assert repr(Location('ssh://user@host:1234/some/path::archive')) == \ - "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive='archive')" - assert Location('ssh://user@host:1234/some/path::archive').to_key_filename() == keys_dir + 'host__some_path' assert repr(Location('ssh://user@host:1234/some/path')) == \ - "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive=None)" + "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path')" + assert Location('ssh://user@host:1234/some/path').to_key_filename() == keys_dir + 'host__some_path' + assert repr(Location('ssh://user@host:1234/some/path')) == \ + "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path')" assert repr(Location('ssh://user@host/some/path')) == \ - "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive=None)" - assert repr(Location('ssh://user@[::]:1234/some/path::archive')) == \ - "Location(proto='ssh', user='user', host='::', port=1234, path='/some/path', archive='archive')" + "Location(proto='ssh', user='user', host='host', port=None, path='/some/path')" assert repr(Location('ssh://user@[::]:1234/some/path')) == \ - "Location(proto='ssh', user='user', host='::', port=1234, path='/some/path', archive=None)" + "Location(proto='ssh', user='user', host='::', port=1234, path='/some/path')" + assert repr(Location('ssh://user@[::]:1234/some/path')) == \ + "Location(proto='ssh', user='user', host='::', port=1234, path='/some/path')" assert Location('ssh://user@[::]:1234/some/path').to_key_filename() == keys_dir + '____some_path' assert repr(Location('ssh://user@[::]/some/path')) == \ - "Location(proto='ssh', user='user', host='::', port=None, path='/some/path', archive=None)" - assert repr(Location('ssh://user@[2001:db8::]:1234/some/path::archive')) == \ - "Location(proto='ssh', user='user', host='2001:db8::', port=1234, path='/some/path', archive='archive')" + "Location(proto='ssh', user='user', host='::', port=None, path='/some/path')" assert repr(Location('ssh://user@[2001:db8::]:1234/some/path')) == \ - "Location(proto='ssh', user='user', host='2001:db8::', port=1234, path='/some/path', archive=None)" + "Location(proto='ssh', user='user', host='2001:db8::', port=1234, path='/some/path')" + assert repr(Location('ssh://user@[2001:db8::]:1234/some/path')) == \ + "Location(proto='ssh', user='user', host='2001:db8::', port=1234, path='/some/path')" assert Location('ssh://user@[2001:db8::]:1234/some/path').to_key_filename() == keys_dir + '2001_db8____some_path' assert repr(Location('ssh://user@[2001:db8::]/some/path')) == \ - "Location(proto='ssh', user='user', host='2001:db8::', port=None, path='/some/path', archive=None)" - assert repr(Location('ssh://user@[2001:db8::c0:ffee]:1234/some/path::archive')) == \ - "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=1234, path='/some/path', archive='archive')" + "Location(proto='ssh', user='user', host='2001:db8::', port=None, path='/some/path')" assert repr(Location('ssh://user@[2001:db8::c0:ffee]:1234/some/path')) == \ - "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=1234, path='/some/path', archive=None)" + "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=1234, path='/some/path')" + assert repr(Location('ssh://user@[2001:db8::c0:ffee]:1234/some/path')) == \ + "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=1234, path='/some/path')" assert repr(Location('ssh://user@[2001:db8::c0:ffee]/some/path')) == \ - "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=None, path='/some/path', archive=None)" - assert repr(Location('ssh://user@[2001:db8::192.0.2.1]:1234/some/path::archive')) == \ - "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=1234, path='/some/path', archive='archive')" + "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=None, path='/some/path')" assert repr(Location('ssh://user@[2001:db8::192.0.2.1]:1234/some/path')) == \ - "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=1234, path='/some/path', archive=None)" + "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=1234, path='/some/path')" + assert repr(Location('ssh://user@[2001:db8::192.0.2.1]:1234/some/path')) == \ + "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=1234, path='/some/path')" assert repr(Location('ssh://user@[2001:db8::192.0.2.1]/some/path')) == \ - "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=None, path='/some/path', archive=None)" + "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=None, path='/some/path')" assert Location('ssh://user@[2001:db8::192.0.2.1]/some/path').to_key_filename() == keys_dir + '2001_db8__192_0_2_1__some_path' assert repr(Location('ssh://user@[2a02:0001:0002:0003:0004:0005:0006:0007]/some/path')) == \ - "Location(proto='ssh', user='user', host='2a02:0001:0002:0003:0004:0005:0006:0007', port=None, path='/some/path', archive=None)" + "Location(proto='ssh', user='user', host='2a02:0001:0002:0003:0004:0005:0006:0007', port=None, path='/some/path')" assert repr(Location('ssh://user@[2a02:0001:0002:0003:0004:0005:0006:0007]:1234/some/path')) == \ - "Location(proto='ssh', user='user', host='2a02:0001:0002:0003:0004:0005:0006:0007', port=1234, path='/some/path', archive=None)" + "Location(proto='ssh', user='user', host='2a02:0001:0002:0003:0004:0005:0006:0007', port=1234, path='/some/path')" def test_file(self, monkeypatch, keys_dir): monkeypatch.delenv('BORG_REPO', raising=False) - assert repr(Location('file:///some/path::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')" assert repr(Location('file:///some/path')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive=None)" + "Location(proto='file', user=None, host=None, port=None, path='/some/path')" + assert repr(Location('file:///some/path')) == \ + "Location(proto='file', user=None, host=None, port=None, path='/some/path')" assert Location('file:///some/path').to_key_filename() == keys_dir + 'some_path' def test_smb(self, monkeypatch, keys_dir): monkeypatch.delenv('BORG_REPO', raising=False) - assert repr(Location('file:////server/share/path::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='//server/share/path', archive='archive')" - assert Location('file:////server/share/path::archive').to_key_filename() == keys_dir + 'server_share_path' + assert repr(Location('file:////server/share/path')) == \ + "Location(proto='file', user=None, host=None, port=None, path='//server/share/path')" + assert Location('file:////server/share/path').to_key_filename() == keys_dir + 'server_share_path' def test_folder(self, monkeypatch, keys_dir): monkeypatch.delenv('BORG_REPO', raising=False) - assert repr(Location('path::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='path', archive='archive')" assert repr(Location('path')) == \ - "Location(proto='file', user=None, host=None, port=None, path='path', archive=None)" + "Location(proto='file', user=None, host=None, port=None, path='path')" assert Location('path').to_key_filename() == keys_dir + 'path' def test_long_path(self, monkeypatch, keys_dir): @@ -121,168 +119,61 @@ class TestLocationWithoutEnv: def test_abspath(self, monkeypatch, keys_dir): monkeypatch.delenv('BORG_REPO', raising=False) - assert repr(Location('/some/absolute/path::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive='archive')" assert repr(Location('/some/absolute/path')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive=None)" + "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path')" + assert repr(Location('/some/absolute/path')) == \ + "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path')" assert Location('/some/absolute/path').to_key_filename() == keys_dir + 'some_absolute_path' assert repr(Location('ssh://user@host/some/path')) == \ - "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive=None)" + "Location(proto='ssh', user='user', host='host', port=None, path='/some/path')" assert Location('ssh://user@host/some/path').to_key_filename() == keys_dir + 'host__some_path' def test_relpath(self, monkeypatch, keys_dir): monkeypatch.delenv('BORG_REPO', raising=False) - assert repr(Location('some/relative/path::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive='archive')" assert repr(Location('some/relative/path')) == \ - "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive=None)" + "Location(proto='file', user=None, host=None, port=None, path='some/relative/path')" + assert repr(Location('some/relative/path')) == \ + "Location(proto='file', user=None, host=None, port=None, path='some/relative/path')" assert Location('some/relative/path').to_key_filename() == keys_dir + 'some_relative_path' assert repr(Location('ssh://user@host/./some/path')) == \ - "Location(proto='ssh', user='user', host='host', port=None, path='/./some/path', archive=None)" + "Location(proto='ssh', user='user', host='host', port=None, path='/./some/path')" assert Location('ssh://user@host/./some/path').to_key_filename() == keys_dir + 'host__some_path' assert repr(Location('ssh://user@host/~/some/path')) == \ - "Location(proto='ssh', user='user', host='host', port=None, path='/~/some/path', archive=None)" + "Location(proto='ssh', user='user', host='host', port=None, path='/~/some/path')" assert Location('ssh://user@host/~/some/path').to_key_filename() == keys_dir + 'host__some_path' assert repr(Location('ssh://user@host/~user/some/path')) == \ - "Location(proto='ssh', user='user', host='host', port=None, path='/~user/some/path', archive=None)" + "Location(proto='ssh', user='user', host='host', port=None, path='/~user/some/path')" assert Location('ssh://user@host/~user/some/path').to_key_filename() == keys_dir + 'host__user_some_path' def test_with_colons(self, monkeypatch, keys_dir): monkeypatch.delenv('BORG_REPO', raising=False) - assert repr(Location('/abs/path:w:cols::arch:col')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/abs/path:w:cols', archive='arch:col')" - assert repr(Location('/abs/path:with:colons::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/abs/path:with:colons', archive='archive')" + assert repr(Location('/abs/path:w:cols')) == \ + "Location(proto='file', user=None, host=None, port=None, path='/abs/path:w:cols')" assert repr(Location('/abs/path:with:colons')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/abs/path:with:colons', archive=None)" + "Location(proto='file', user=None, host=None, port=None, path='/abs/path:with:colons')" + assert repr(Location('/abs/path:with:colons')) == \ + "Location(proto='file', user=None, host=None, port=None, path='/abs/path:with:colons')" assert Location('/abs/path:with:colons').to_key_filename() == keys_dir + 'abs_path_with_colons' def test_user_parsing(self): # see issue #1930 - assert repr(Location('ssh://host/path::2016-12-31@23:59:59')) == \ - "Location(proto='ssh', user=None, host='host', port=None, path='/path', archive='2016-12-31@23:59:59')" - - def test_with_timestamp(self): - assert repr(Location('path::archive-{utcnow}').with_timestamp(datetime(2002, 9, 19, tzinfo=timezone.utc))) == \ - "Location(proto='file', user=None, host=None, port=None, path='path', archive='archive-2002-09-19T00:00:00')" - - def test_underspecified(self, monkeypatch): - monkeypatch.delenv('BORG_REPO', raising=False) - with pytest.raises(ValueError): - Location('::archive') - with pytest.raises(ValueError): - Location('::') - with pytest.raises(ValueError): - Location() - - def test_no_slashes(self, monkeypatch): - monkeypatch.delenv('BORG_REPO', raising=False) - with pytest.raises(ValueError): - Location('/some/path/to/repo::archive_name_with/slashes/is_invalid') + assert repr(Location('ssh://host/path')) == \ + "Location(proto='ssh', user=None, host='host', port=None, path='/path')" def test_canonical_path(self, monkeypatch): monkeypatch.delenv('BORG_REPO', raising=False) - locations = ['some/path::archive', 'file://some/path::archive', 'host:some/path::archive', - 'host:~user/some/path::archive', 'ssh://host/some/path::archive', - 'ssh://user@host:1234/some/path::archive'] + locations = ['some/path', 'file://some/path', 'host:some/path', + 'host:~user/some/path', 'ssh://host/some/path', + 'ssh://user@host:1234/some/path'] for location in locations: assert Location(location).canonical_path() == \ Location(Location(location).canonical_path()).canonical_path(), "failed: %s" % location - def test_format_path(self, monkeypatch): - monkeypatch.delenv('BORG_REPO', raising=False) - test_pid = os.getpid() - assert repr(Location('/some/path::archive{pid}')) == \ - f"Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive{test_pid}')" - location_time1 = Location('/some/path::archive{now:%s}') - sleep(1.1) - location_time2 = Location('/some/path::archive{now:%s}') - assert location_time1.archive != location_time2.archive - def test_bad_syntax(self): with pytest.raises(ValueError): # this is invalid due to the 2nd colon, correct: 'ssh://user@host/path' Location('ssh://user@host:/path') - def test_omit_archive(self): - from borg.platform import hostname - loc = Location('ssh://user@host:1234/repos/{hostname}::archive') - loc_without_archive = loc.omit_archive() - assert loc_without_archive.archive is None - assert loc_without_archive.raw == "ssh://user@host:1234/repos/{hostname}" - assert loc_without_archive.processed == "ssh://user@host:1234/repos/%s" % hostname - - -class TestLocationWithEnv: - def test_ssh(self, monkeypatch): - monkeypatch.setenv('BORG_REPO', 'ssh://user@host:1234/some/path') - assert repr(Location('::archive')) == \ - "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive='archive')" - assert repr(Location('::')) == \ - "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive=None)" - assert repr(Location()) == \ - "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive=None)" - - def test_ssh_placeholder(self, monkeypatch): - from borg.platform import hostname - monkeypatch.setenv('BORG_REPO', 'ssh://user@host:1234/{hostname}') - assert repr(Location('::archive')) == \ - f"Location(proto='ssh', user='user', host='host', port=1234, path='/{hostname}', archive='archive')" - assert repr(Location('::')) == \ - f"Location(proto='ssh', user='user', host='host', port=1234, path='/{hostname}', archive=None)" - assert repr(Location()) == \ - f"Location(proto='ssh', user='user', host='host', port=1234, path='/{hostname}', archive=None)" - - def test_file(self, monkeypatch): - monkeypatch.setenv('BORG_REPO', 'file:///some/path') - assert repr(Location('::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')" - assert repr(Location('::')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive=None)" - assert repr(Location()) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive=None)" - - def test_folder(self, monkeypatch): - monkeypatch.setenv('BORG_REPO', 'path') - assert repr(Location('::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='path', archive='archive')" - assert repr(Location('::')) == \ - "Location(proto='file', user=None, host=None, port=None, path='path', archive=None)" - assert repr(Location()) == \ - "Location(proto='file', user=None, host=None, port=None, path='path', archive=None)" - - def test_abspath(self, monkeypatch): - monkeypatch.setenv('BORG_REPO', '/some/absolute/path') - assert repr(Location('::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive='archive')" - assert repr(Location('::')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive=None)" - assert repr(Location()) == \ - "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive=None)" - - def test_relpath(self, monkeypatch): - monkeypatch.setenv('BORG_REPO', 'some/relative/path') - assert repr(Location('::archive')) == \ - "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive='archive')" - assert repr(Location('::')) == \ - "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive=None)" - assert repr(Location()) == \ - "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive=None)" - - def test_with_colons(self, monkeypatch): - monkeypatch.setenv('BORG_REPO', '/abs/path:w:cols') - assert repr(Location('::arch:col')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/abs/path:w:cols', archive='arch:col')" - assert repr(Location('::')) == \ - "Location(proto='file', user=None, host=None, port=None, path='/abs/path:w:cols', archive=None)" - assert repr(Location()) == \ - "Location(proto='file', user=None, host=None, port=None, path='/abs/path:w:cols', archive=None)" - - def test_no_slashes(self, monkeypatch): - monkeypatch.setenv('BORG_REPO', '/some/absolute/path') - with pytest.raises(ValueError): - Location('::archive_name_with/slashes/is_invalid') - class FormatTimedeltaTestCase(BaseTestCase):