diff --git a/.gitignore b/.gitignore index 8ca016534..58175f953 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ coverage.xml dist venv doc/source/main_help.rst +doc/source/subcommands Dockerfile b2/licenses_output.txt *.spec \ No newline at end of file diff --git a/b2/_internal/console_tool.py b/b2/_internal/console_tool.py index 51f9affd7..499087a00 100644 --- a/b2/_internal/console_tool.py +++ b/b2/_internal/console_tool.py @@ -221,6 +221,8 @@ def resolve_b2_bin_call_name(argv: list[str] | None = None) -> str: if call_name.endswith('.py'): version_name = re.search(r'[\\/]b2[\\/]_internal[\\/](_?b2v\d+)[\\/]__main__.py', call_name) call_name = version_name.group(1) if version_name else 'b2' + if 'b2' not in call_name: # prevent silliness when calling b2 from under different process + return 'b2' return call_name diff --git a/doc/source/conf.py b/doc/source/conf.py index 294596d9a..d313eec0a 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -26,11 +26,17 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # +from __future__ import annotations + +import tempfile import datetime import importlib import os +import filecmp +import pathlib import re +import shutil import sys import textwrap from os import path @@ -213,6 +219,7 @@ def setup(_): reasonable way to piggy back that behaviour. Checking if the new file contents would the same as the old one (if any) is important, so that the automatic file-watcher/doc-builder doesn't fall into an endless loop. """ + regenerate_subcommands_help() main_help_text = str(B2.lazy_get_description(NAME='b2')) main_help_text = textwrap.dedent(main_help_text) @@ -223,3 +230,59 @@ def setup(_): return with open(main_help_path, 'w') as main_help_file: main_help_file.write(main_help_text) + + +def regenerate_subcommands_help(): + tmpl = textwrap.dedent("""\ + .. _{slug}: + + {HUMAN_NAME} + ************************************************ + + .. argparse:: + :module: b2._internal.console_tool + :func: get_parser + :prog: b2 + :path: {COMMAND} + """) + + all_commands: list[tuple[tuple[str, ...], type]] = [] + + def _add_cmd(path, cmd_cls): + if getattr(cmd_cls, "deprecated", False): + return + + registry = cmd_cls.subcommands_registry + subcommands = registry.values() if registry else None + if subcommands: + for subcommand in subcommands: + _add_cmd(path + (subcommand.name_and_alias()[0],), subcommand) + else: + all_commands.append((path, cmd_cls)) + + _add_cmd((), B2) + + subcommands_dir_target = pathlib.Path(__file__).parent / "subcommands" + with tempfile.TemporaryDirectory() as temp_dir: + subcommands_dir = pathlib.Path(temp_dir) / "subcommands" + subcommands_dir.mkdir() + for command_path, cmd_cls in sorted(all_commands): + full_command = " ".join(command_path) + slug = full_command.replace(" ", "_") + (subcommands_dir / f"{slug}.rst").write_text( + tmpl.format( + HUMAN_NAME=full_command, + COMMAND=full_command, + slug=f"subcommand_{slug}", + ) + ) + + # prevent infinite loop due sphinx autobuild file watcher + try: + dir_cmp = filecmp.dircmp(subcommands_dir_target, subcommands_dir) + is_diff = bool(dir_cmp.diff_files or dir_cmp.left_only or dir_cmp.right_only) + except FileNotFoundError: + is_diff = True + if is_diff: + shutil.rmtree(subcommands_dir_target, ignore_errors=True) + shutil.copytree(subcommands_dir, subcommands_dir_target) diff --git a/doc/source/replication.rst b/doc/source/replication.rst index d87b64aed..bdfa7b8f3 100644 --- a/doc/source/replication.rst +++ b/doc/source/replication.rst @@ -4,7 +4,7 @@ Replication ######################## -If you have access to accounts hosting both source and destination bucket (it can be the same account), we recommend using ``replication-setup`` command described below. Otherwise use :ref:`manual setup `. +If you have access to accounts hosting both source and destination bucket (it can be the same account), we recommend using ``replication setup`` command described below. Otherwise use :ref:`manual setup `. *********************** Automatic setup @@ -15,12 +15,12 @@ Setup replication .. code-block:: sh - $ b2 replication-setup --destination-profile myprofile2 my-bucket my-bucket2 + $ b2 replication setup --destination-profile myprofile2 my-bucket my-bucket2 -You can optionally choose source rule priority and source rule name. See :ref:`replication-setup command `. +You can optionally choose source rule priority and source rule name. See :ref:`replication setup command `. .. note:: - ``replication-setup`` will reuse or provision a source key with no prefix and full reading capabilities and a destination key with no prefix and full writing capabilities + ``replication setup`` will reuse or provision a source key with no prefix and full reading capabilities and a destination key with no prefix and full writing capabilities .. _replication_manual_setup: diff --git a/doc/source/subcommands/authorize_account.rst b/doc/source/subcommands/authorize_account.rst deleted file mode 100644 index ebf642d6c..000000000 --- a/doc/source/subcommands/authorize_account.rst +++ /dev/null @@ -1,8 +0,0 @@ -Authorize-account command -************************* - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: authorize-account diff --git a/doc/source/subcommands/cancel_all_unfinished_large_files.rst b/doc/source/subcommands/cancel_all_unfinished_large_files.rst deleted file mode 100644 index b097dd2cd..000000000 --- a/doc/source/subcommands/cancel_all_unfinished_large_files.rst +++ /dev/null @@ -1,8 +0,0 @@ -Cancel-all-unfinished-large-files command -***************************************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: cancel-all-unfinished-large-files diff --git a/doc/source/subcommands/cancel_large_file.rst b/doc/source/subcommands/cancel_large_file.rst deleted file mode 100644 index ec12bc025..000000000 --- a/doc/source/subcommands/cancel_large_file.rst +++ /dev/null @@ -1,8 +0,0 @@ -Cancel-large-file command -************************* - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: cancel-large-file diff --git a/doc/source/subcommands/cat.rst b/doc/source/subcommands/cat.rst deleted file mode 100644 index 1712fa6c7..000000000 --- a/doc/source/subcommands/cat.rst +++ /dev/null @@ -1,8 +0,0 @@ -Cat command -**************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: cat diff --git a/doc/source/subcommands/clear_account.rst b/doc/source/subcommands/clear_account.rst deleted file mode 100644 index f9758b5f0..000000000 --- a/doc/source/subcommands/clear_account.rst +++ /dev/null @@ -1,8 +0,0 @@ -Clear-account command -********************* - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: clear-account diff --git a/doc/source/subcommands/copy_file_by_id.rst b/doc/source/subcommands/copy_file_by_id.rst deleted file mode 100644 index 681774feb..000000000 --- a/doc/source/subcommands/copy_file_by_id.rst +++ /dev/null @@ -1,8 +0,0 @@ -Copy-file-by-id command -*********************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: copy-file-by-id diff --git a/doc/source/subcommands/create_bucket.rst b/doc/source/subcommands/create_bucket.rst deleted file mode 100644 index fc8128fde..000000000 --- a/doc/source/subcommands/create_bucket.rst +++ /dev/null @@ -1,8 +0,0 @@ -Create-bucket command -********************* - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: create-bucket diff --git a/doc/source/subcommands/create_key.rst b/doc/source/subcommands/create_key.rst deleted file mode 100644 index b0d38ec70..000000000 --- a/doc/source/subcommands/create_key.rst +++ /dev/null @@ -1,8 +0,0 @@ -Create-key command -****************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: create-key diff --git a/doc/source/subcommands/delete_bucket.rst b/doc/source/subcommands/delete_bucket.rst deleted file mode 100644 index 7338d05bb..000000000 --- a/doc/source/subcommands/delete_bucket.rst +++ /dev/null @@ -1,8 +0,0 @@ -Delete-bucket command -********************* - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: delete-bucket diff --git a/doc/source/subcommands/delete_file_version.rst b/doc/source/subcommands/delete_file_version.rst deleted file mode 100644 index a8b6ef8bc..000000000 --- a/doc/source/subcommands/delete_file_version.rst +++ /dev/null @@ -1,8 +0,0 @@ -Delete-file-version command -*************************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: delete-file-version diff --git a/doc/source/subcommands/delete_key.rst b/doc/source/subcommands/delete_key.rst deleted file mode 100644 index 71d124ffd..000000000 --- a/doc/source/subcommands/delete_key.rst +++ /dev/null @@ -1,8 +0,0 @@ -Delete-key command -****************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: delete-key diff --git a/doc/source/subcommands/download_file.rst b/doc/source/subcommands/download_file.rst deleted file mode 100644 index 656aa87e2..000000000 --- a/doc/source/subcommands/download_file.rst +++ /dev/null @@ -1,8 +0,0 @@ -Download-file command -*************************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: download-file diff --git a/doc/source/subcommands/download_file_by_id.rst b/doc/source/subcommands/download_file_by_id.rst deleted file mode 100644 index eaf42f01c..000000000 --- a/doc/source/subcommands/download_file_by_id.rst +++ /dev/null @@ -1,8 +0,0 @@ -Download-file-by-id command -*************************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: download-file-by-id diff --git a/doc/source/subcommands/download_file_by_name.rst b/doc/source/subcommands/download_file_by_name.rst deleted file mode 100644 index 4210473d2..000000000 --- a/doc/source/subcommands/download_file_by_name.rst +++ /dev/null @@ -1,8 +0,0 @@ -Download-file-by-name command -***************************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: download-file-by-name diff --git a/doc/source/subcommands/file_info.rst b/doc/source/subcommands/file_info.rst deleted file mode 100644 index 262f9bebe..000000000 --- a/doc/source/subcommands/file_info.rst +++ /dev/null @@ -1,8 +0,0 @@ -File-info command -********************* - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: file-info diff --git a/doc/source/subcommands/get_account_info.rst b/doc/source/subcommands/get_account_info.rst deleted file mode 100644 index fd156793b..000000000 --- a/doc/source/subcommands/get_account_info.rst +++ /dev/null @@ -1,8 +0,0 @@ -Get-account-info command -************************ - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: get-account-info diff --git a/doc/source/subcommands/get_bucket.rst b/doc/source/subcommands/get_bucket.rst deleted file mode 100644 index 39930b17b..000000000 --- a/doc/source/subcommands/get_bucket.rst +++ /dev/null @@ -1,8 +0,0 @@ -Get-bucket command -****************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: get-bucket diff --git a/doc/source/subcommands/get_download_auth.rst b/doc/source/subcommands/get_download_auth.rst deleted file mode 100644 index 06677d1cf..000000000 --- a/doc/source/subcommands/get_download_auth.rst +++ /dev/null @@ -1,8 +0,0 @@ -Get-download-auth command -************************* - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: get-download-auth diff --git a/doc/source/subcommands/get_download_url_with_auth.rst b/doc/source/subcommands/get_download_url_with_auth.rst deleted file mode 100644 index 4d0e5632f..000000000 --- a/doc/source/subcommands/get_download_url_with_auth.rst +++ /dev/null @@ -1,8 +0,0 @@ -Get-download-url-with-auth command -********************************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: get-download-url-with-auth diff --git a/doc/source/subcommands/get_file_info.rst b/doc/source/subcommands/get_file_info.rst deleted file mode 100644 index abeb969c5..000000000 --- a/doc/source/subcommands/get_file_info.rst +++ /dev/null @@ -1,8 +0,0 @@ -Get-file-info command -********************* - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: get-file-info diff --git a/doc/source/subcommands/get_url.rst b/doc/source/subcommands/get_url.rst deleted file mode 100644 index 4a71b2d46..000000000 --- a/doc/source/subcommands/get_url.rst +++ /dev/null @@ -1,8 +0,0 @@ -Get-url command -**************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: get-url diff --git a/doc/source/subcommands/hide_file.rst b/doc/source/subcommands/hide_file.rst deleted file mode 100644 index bf258a2b1..000000000 --- a/doc/source/subcommands/hide_file.rst +++ /dev/null @@ -1,8 +0,0 @@ -Hide-file command -***************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: hide-file diff --git a/doc/source/subcommands/install_autocomplete.rst b/doc/source/subcommands/install_autocomplete.rst deleted file mode 100644 index 48dac2e0c..000000000 --- a/doc/source/subcommands/install_autocomplete.rst +++ /dev/null @@ -1,8 +0,0 @@ -install-autocomplete command -**************************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: install-autocomplete diff --git a/doc/source/subcommands/list_buckets.rst b/doc/source/subcommands/list_buckets.rst deleted file mode 100644 index 1ec1118fc..000000000 --- a/doc/source/subcommands/list_buckets.rst +++ /dev/null @@ -1,8 +0,0 @@ -List-buckets command -******************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: list-buckets diff --git a/doc/source/subcommands/list_keys.rst b/doc/source/subcommands/list_keys.rst deleted file mode 100644 index c6d9026bb..000000000 --- a/doc/source/subcommands/list_keys.rst +++ /dev/null @@ -1,8 +0,0 @@ -List-keys command -***************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: list-keys diff --git a/doc/source/subcommands/list_parts.rst b/doc/source/subcommands/list_parts.rst deleted file mode 100644 index 688bc25e8..000000000 --- a/doc/source/subcommands/list_parts.rst +++ /dev/null @@ -1,8 +0,0 @@ -List-parts command -****************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: list-parts diff --git a/doc/source/subcommands/list_unfinished_large_files.rst b/doc/source/subcommands/list_unfinished_large_files.rst deleted file mode 100644 index 2fd6d700b..000000000 --- a/doc/source/subcommands/list_unfinished_large_files.rst +++ /dev/null @@ -1,8 +0,0 @@ -List-unfinished-large-files command -*********************************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: list-unfinished-large-files diff --git a/doc/source/subcommands/ls.rst b/doc/source/subcommands/ls.rst deleted file mode 100644 index 51f11fcb0..000000000 --- a/doc/source/subcommands/ls.rst +++ /dev/null @@ -1,8 +0,0 @@ -Ls command -********** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: ls diff --git a/doc/source/subcommands/make_friendly_url.rst b/doc/source/subcommands/make_friendly_url.rst deleted file mode 100644 index dc38ffd92..000000000 --- a/doc/source/subcommands/make_friendly_url.rst +++ /dev/null @@ -1,8 +0,0 @@ -Make-friendly-url command -************************* - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: make-friendly-url diff --git a/doc/source/subcommands/make_url.rst b/doc/source/subcommands/make_url.rst deleted file mode 100644 index 055d67caf..000000000 --- a/doc/source/subcommands/make_url.rst +++ /dev/null @@ -1,8 +0,0 @@ -Make-url command -**************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: make-url diff --git a/doc/source/subcommands/replication-setup.rst b/doc/source/subcommands/replication-setup.rst deleted file mode 100644 index 960a0ce1e..000000000 --- a/doc/source/subcommands/replication-setup.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. _replication_setup_command: - -replication-setup command -************************* - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: replication-setup diff --git a/doc/source/subcommands/rm.rst b/doc/source/subcommands/rm.rst deleted file mode 100644 index 39705a850..000000000 --- a/doc/source/subcommands/rm.rst +++ /dev/null @@ -1,8 +0,0 @@ -Rm command -********** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: rm diff --git a/doc/source/subcommands/sync.rst b/doc/source/subcommands/sync.rst deleted file mode 100644 index 61241b06f..000000000 --- a/doc/source/subcommands/sync.rst +++ /dev/null @@ -1,8 +0,0 @@ -Sync command -************ - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: sync diff --git a/doc/source/subcommands/update_bucket.rst b/doc/source/subcommands/update_bucket.rst deleted file mode 100644 index a1a726c7f..000000000 --- a/doc/source/subcommands/update_bucket.rst +++ /dev/null @@ -1,8 +0,0 @@ -Update-bucket command -********************* - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: update-bucket diff --git a/doc/source/subcommands/update_file_legal_hold.rst b/doc/source/subcommands/update_file_legal_hold.rst deleted file mode 100644 index 42615aa60..000000000 --- a/doc/source/subcommands/update_file_legal_hold.rst +++ /dev/null @@ -1,8 +0,0 @@ -Update-file-legal-hold command -****************************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: update-file-legal-hold diff --git a/doc/source/subcommands/update_file_retention.rst b/doc/source/subcommands/update_file_retention.rst deleted file mode 100644 index 05811bdf2..000000000 --- a/doc/source/subcommands/update_file_retention.rst +++ /dev/null @@ -1,8 +0,0 @@ -Update-file-retention command -***************************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: update-file-retention diff --git a/doc/source/subcommands/upload_file.rst b/doc/source/subcommands/upload_file.rst deleted file mode 100644 index 0b3315126..000000000 --- a/doc/source/subcommands/upload_file.rst +++ /dev/null @@ -1,8 +0,0 @@ -Upload-file command -******************* - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: upload-file diff --git a/doc/source/subcommands/version.rst b/doc/source/subcommands/version.rst deleted file mode 100644 index 20a8d5263..000000000 --- a/doc/source/subcommands/version.rst +++ /dev/null @@ -1,8 +0,0 @@ -Version command -*************** - -.. argparse:: - :module: b2._internal.console_tool - :func: get_parser - :prog: b2 - :path: version diff --git a/noxfile.py b/noxfile.py index dcae3158c..9f2f9aa58 100644 --- a/noxfile.py +++ b/noxfile.py @@ -475,7 +475,8 @@ def doc(session): # session.notify('doc_cover') # disabled due to https://github.com/sphinx-doc/sphinx/issues/11678 else: sphinx_args[-2:-2] = [ - '-E', '--open-browser', '--watch', '../b2', '--ignore', '*.pyc', '--ignore', '*~' + '-E', '--open-browser', '--watch', '../b2', '--ignore', '*.pyc', '--ignore', '*~', + '--ignore', 'source/subcommands/*' ] session.run('sphinx-autobuild', *sphinx_args) diff --git a/test/integration/test_b2_command_line.py b/test/integration/test_b2_command_line.py index 605611b67..605e325a5 100755 --- a/test/integration/test_b2_command_line.py +++ b/test/integration/test_b2_command_line.py @@ -796,23 +796,10 @@ def encryption_summary(sse_dict, file_info): return encryption -def test_sync_up(b2_tool, bucket_name): - sync_up_helper(b2_tool, bucket_name, 'sync') - - -def test_sync_up_sse_b2(b2_tool, bucket_name): - sync_up_helper(b2_tool, bucket_name, 'sync', encryption=SSE_B2_AES) - - -def test_sync_up_sse_c(b2_tool, bucket_name): - sync_up_helper(b2_tool, bucket_name, 'sync', encryption=SSE_C_AES) - - -def test_sync_up_no_prefix(b2_tool, bucket_name): - sync_up_helper(b2_tool, bucket_name, '') - - -def sync_up_helper(b2_tool, bucket_name, dir_, encryption=None): +@pytest.mark.parametrize( + "dir_, encryption", [('sync', None), ('sync', SSE_B2_AES), ('sync', SSE_C_AES), ('', None)] +) +def test_sync_up(b2_tool, bucket_name, apiver_int, dir_, encryption): sync_point_parts = [bucket_name] if dir_: sync_point_parts.append(dir_) @@ -843,7 +830,7 @@ def sync_up_helper(b2_tool, bucket_name, dir_, encryption=None): # but it didn't work for me, so I just ran it as admin. A guide that I've found # recommended to go to Control Panel, Administrative Tools, Local Security Policy, # Local Policies, User Rights Assignment and there you can find a permission to - # create symbilic links. Add your user to it (or a group that the user is in). + # create symbolic links. Add your user to it (or a group that the user is in). # # Finally in order to apply the new policy, run `cmd` and execute # ``gpupdate /force``. @@ -884,9 +871,9 @@ def sync_up_helper(b2_tool, bucket_name, dir_, encryption=None): else: raise NotImplementedError('unsupported encryption mode: %s' % encryption) - b2_tool.should_succeed( - command, expected_pattern="d could not be accessed", additional_env=additional_env - ) + status, stdout, stderr = b2_tool.execute(command, additional_env=additional_env) + assert re.search(r'd[\'"]? could not be accessed', stdout) + assert status == (1 if apiver_int >= 4 else 0) file_versions = b2_tool.list_file_versions(bucket_name) should_equal( @@ -915,9 +902,11 @@ def sync_up_helper(b2_tool, bucket_name, dir_, encryption=None): os.unlink(dir_path / 'b') write_file(dir_path / 'c', b'hello world') - b2_tool.should_succeed( + status, stdout, stderr = b2_tool.execute( ['sync', '--no-progress', '--keep-days', '10', dir_path, b2_sync_point] ) + assert re.search(r'd[\'"]? could not be accessed', stdout) + assert status == (1 if apiver_int >= 4 else 0) file_versions = b2_tool.list_file_versions(bucket_name) should_equal( [ @@ -930,6 +919,7 @@ def sync_up_helper(b2_tool, bucket_name, dir_, encryption=None): ) os.unlink(dir_path / 'a') + os.unlink(dir_path / 'd') # remove broken symlink to get status 0 on >=b2v4 b2_tool.should_succeed(['sync', '--no-progress', '--delete', dir_path, b2_sync_point]) file_versions = b2_tool.list_file_versions(bucket_name)