mirror of
https://github.com/borgbackup/borg.git
synced 2026-05-23 10:36:32 -04:00
Merge b3a573a1f0 into e016a51cf2
This commit is contained in:
commit
ed7bf18ee1
2 changed files with 268 additions and 1 deletions
|
|
@ -308,6 +308,155 @@ class Archiver(
|
|||
self.build_parser_version(subparsers, common_parser, mid_common_parser)
|
||||
return parser
|
||||
|
||||
@staticmethod
|
||||
def _first_positional_index(args, parser):
|
||||
option_actions = {}
|
||||
for action in parser._actions:
|
||||
for option_string in getattr(action, "option_strings", ()):
|
||||
option_actions[option_string] = action
|
||||
|
||||
i = 0
|
||||
while i < len(args):
|
||||
token = args[i]
|
||||
if token == "--":
|
||||
return i + 1 if i + 1 < len(args) else len(args)
|
||||
if not token.startswith("-") or token == "-":
|
||||
return i
|
||||
|
||||
option_name, has_value, _ = token.partition("=")
|
||||
action = option_actions.get(option_name)
|
||||
if action is None:
|
||||
return None
|
||||
if has_value:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
nargs = getattr(action, "nargs", None)
|
||||
if nargs in (0, "0"):
|
||||
i += 1
|
||||
elif nargs == "?":
|
||||
if i + 1 < len(args) and not args[i + 1].startswith("-"):
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
elif isinstance(nargs, int):
|
||||
i += 1 + nargs
|
||||
else:
|
||||
i += 2
|
||||
|
||||
return len(args)
|
||||
|
||||
@staticmethod
|
||||
def _first_toplevel_command_index(args, parser):
|
||||
index = Archiver._first_positional_index(args, parser)
|
||||
if index is None or index == len(args):
|
||||
return None
|
||||
return index
|
||||
|
||||
def _legacy_command_hint(self, args, parser):
|
||||
command_index = self._first_toplevel_command_index(args, parser)
|
||||
if command_index is None or args[command_index] != "init":
|
||||
return None
|
||||
|
||||
corrected_args = list(args)
|
||||
corrected_args[command_index] = "repo-create"
|
||||
corrected_command = shlex.join([self.prog or "borg", *corrected_args])
|
||||
return "\n".join(
|
||||
[
|
||||
"init is not a borg2 command; use repo-create.",
|
||||
"Corrected command:",
|
||||
corrected_command,
|
||||
"Use `borg help` to see the list of valid commands.",
|
||||
]
|
||||
)
|
||||
|
||||
def _legacy_option_hint(self, args, parser):
|
||||
command_index = self._first_toplevel_command_index(args, parser)
|
||||
if command_index is None or args[command_index] != "list":
|
||||
return None
|
||||
|
||||
if not any(arg == "--glob-archives" or arg.startswith("--glob-archives=") for arg in args[command_index + 1 :]):
|
||||
return None
|
||||
|
||||
prog = self.prog or "borg"
|
||||
example = shlex.join([prog, "list", "ARCHIVE", "--match-archives", "sh:my*"])
|
||||
return "\n".join(
|
||||
[
|
||||
"--glob-archives is a borg1 option and is not used in borg2.",
|
||||
"Use --match-archives in borg2. It defaults to exact `id:` matching, "
|
||||
"so use `sh:` for borg1-style globbing.",
|
||||
"Example:",
|
||||
example,
|
||||
f"tip: For details of accepted options run: {prog} list --help",
|
||||
]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _option_value(args, option_strings):
|
||||
for i, arg in enumerate(args):
|
||||
for option_string in option_strings:
|
||||
if arg == option_string:
|
||||
return args[i + 1] if i + 1 < len(args) else None
|
||||
if arg.startswith(option_string + "="):
|
||||
return arg.split("=", 1)[1]
|
||||
return None
|
||||
|
||||
def _legacy_repo_archive_hint(self, args, parser):
|
||||
command_index = self._first_toplevel_command_index(args, parser)
|
||||
if command_index is None or args[command_index] != "list":
|
||||
return None
|
||||
|
||||
repo_value = self._option_value(args, ("-r", "--repo"))
|
||||
if not repo_value or "::" not in repo_value:
|
||||
return None
|
||||
|
||||
repo, archive = repo_value.split("::", 1)
|
||||
if not repo or not archive:
|
||||
return None
|
||||
|
||||
prog = self.prog or "borg"
|
||||
corrected = shlex.join([prog, "--repo", repo, "list", f"::{archive}"])
|
||||
export_cmd = f"export BORG_REPO={shlex.quote(repo)}"
|
||||
positional = shlex.join([prog, "list", f"::{archive}"])
|
||||
return "\n".join(
|
||||
[
|
||||
"Borg2 does not accept repo::archive in --repo.",
|
||||
"Use one of these borg2 forms instead:",
|
||||
corrected,
|
||||
export_cmd,
|
||||
positional,
|
||||
f"tip: For details of accepted options run: {prog} list --help",
|
||||
]
|
||||
)
|
||||
|
||||
def _missing_list_name_hint(self, args, parser):
|
||||
command_index = self._first_toplevel_command_index(args, parser)
|
||||
if command_index is None or args[command_index] != "list":
|
||||
return None
|
||||
|
||||
commands = getattr(parser, "_subcommands_action", None)
|
||||
commands = commands._name_parser_map if commands else {}
|
||||
list_parser = commands.get("list")
|
||||
if list_parser is None:
|
||||
return None
|
||||
|
||||
subcommand_args = args[command_index + 1 :]
|
||||
positional_index = self._first_positional_index(subcommand_args, list_parser)
|
||||
if positional_index is None or positional_index != len(subcommand_args):
|
||||
return None
|
||||
|
||||
prog = self.prog or "borg"
|
||||
repo_value = self._option_value(args, ("-r", "--repo")) or "REPO"
|
||||
repo_list_command = shlex.join([prog, "-r", repo_value, "repo-list"])
|
||||
return "\n".join(
|
||||
[
|
||||
"borg list NAME lists contents of an archive and needs an archive NAME.",
|
||||
"If you meant to list archives in a repository, use repo-list:",
|
||||
repo_list_command,
|
||||
f"tip: For details of accepted options run: {prog} list --help",
|
||||
]
|
||||
)
|
||||
|
||||
def get_args(self, argv, cmd):
|
||||
"""Usually just returns argv, except when dealing with an SSH forced command for borg serve."""
|
||||
result = self.parse_args(argv[1:])
|
||||
|
|
@ -345,6 +494,19 @@ class Archiver(
|
|||
if args:
|
||||
args = self.preprocess_args(args)
|
||||
parser = self.build_parser()
|
||||
if args:
|
||||
legacy_hint = self._legacy_command_hint(args, parser)
|
||||
if legacy_hint:
|
||||
parser.exit(EXIT_ERROR, legacy_hint + "\n")
|
||||
legacy_hint = self._legacy_option_hint(args, parser)
|
||||
if legacy_hint:
|
||||
parser.exit(EXIT_ERROR, legacy_hint + "\n")
|
||||
legacy_hint = self._legacy_repo_archive_hint(args, parser)
|
||||
if legacy_hint:
|
||||
parser.exit(EXIT_ERROR, legacy_hint + "\n")
|
||||
legacy_hint = self._missing_list_name_hint(args, parser)
|
||||
if legacy_hint:
|
||||
parser.exit(EXIT_ERROR, legacy_hint + "\n")
|
||||
args = parser.parse_args(args or ["-h"])
|
||||
args = flatten_namespace(args)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import pytest
|
|||
|
||||
from ...constants import * # NOQA
|
||||
from ...helpers.nanorst import RstToTextLazy, rst_to_terminal
|
||||
from . import Archiver, cmd
|
||||
from . import Archiver, cmd, exec_cmd
|
||||
|
||||
|
||||
def get_all_parsers():
|
||||
|
|
@ -43,6 +43,111 @@ def test_help(archiver):
|
|||
assert "creates a new, empty repository" not in cmd(archiver, "help", "repo-create", "--usage-only")
|
||||
|
||||
|
||||
def test_borg1_init_shows_repo_create_hint(archiver):
|
||||
ret, output = exec_cmd(
|
||||
"--repo",
|
||||
archiver.repository_location,
|
||||
"init",
|
||||
"-e",
|
||||
"repokey-aes-ocb",
|
||||
archiver=archiver.archiver,
|
||||
fork=archiver.FORK_DEFAULT,
|
||||
exe=archiver.EXE,
|
||||
)
|
||||
|
||||
assert ret == 2
|
||||
assert "init is not a borg2 command; use repo-create." in output
|
||||
assert "Corrected command:" in output
|
||||
assert f"borg --repo {archiver.repository_location} repo-create -e repokey-aes-ocb" in output
|
||||
assert "Use `borg help` to see the list of valid commands." in output
|
||||
|
||||
|
||||
def test_borg1_glob_archives_shows_match_archives_hint(archiver):
|
||||
ret, output = exec_cmd(
|
||||
"--repo",
|
||||
archiver.repository_location,
|
||||
"list",
|
||||
"--glob-archives",
|
||||
"my*",
|
||||
archiver=archiver.archiver,
|
||||
fork=archiver.FORK_DEFAULT,
|
||||
exe=archiver.EXE,
|
||||
)
|
||||
|
||||
assert ret == 2
|
||||
assert "--glob-archives is a borg1 option and is not used in borg2." in output
|
||||
assert (
|
||||
"Use --match-archives in borg2. It defaults to exact `id:` matching, "
|
||||
"so use `sh:` for borg1-style globbing." in output
|
||||
)
|
||||
assert "Example:" in output
|
||||
assert "borg list ARCHIVE --match-archives 'sh:my*'" in output
|
||||
assert "tip: For details of accepted options run: borg list --help" in output
|
||||
|
||||
|
||||
def test_borg1_repo_archive_in_repo_shows_borg2_forms(archiver):
|
||||
ret, output = exec_cmd(
|
||||
"--repo",
|
||||
f"{archiver.repository_location}::test1",
|
||||
"list",
|
||||
archiver=archiver.archiver,
|
||||
fork=archiver.FORK_DEFAULT,
|
||||
exe=archiver.EXE,
|
||||
)
|
||||
|
||||
assert ret == 2
|
||||
assert "Borg2 does not accept repo::archive in --repo." in output
|
||||
assert "Use one of these borg2 forms instead:" in output
|
||||
assert f"borg --repo {archiver.repository_location} list ::test1" in output
|
||||
assert f"export BORG_REPO={archiver.repository_location}" in output
|
||||
assert "borg list ::test1" in output
|
||||
assert "tip: For details of accepted options run: borg list --help" in output
|
||||
|
||||
|
||||
def test_borg1_repo_archive_in_repo_shows_borg2_forms_when_repo_is_after_command(archiver):
|
||||
ret, output = exec_cmd(
|
||||
"list",
|
||||
"--repo",
|
||||
f"{archiver.repository_location}::test1",
|
||||
archiver=archiver.archiver,
|
||||
fork=archiver.FORK_DEFAULT,
|
||||
exe=archiver.EXE,
|
||||
)
|
||||
|
||||
assert ret == 2
|
||||
assert "Borg2 does not accept repo::archive in --repo." in output
|
||||
assert f"borg --repo {archiver.repository_location} list ::test1" in output
|
||||
assert f"export BORG_REPO={archiver.repository_location}" in output
|
||||
assert "borg list ::test1" in output
|
||||
|
||||
|
||||
def test_list_without_name_suggests_repo_list(archiver):
|
||||
ret, output = exec_cmd("list", archiver=archiver.archiver, fork=archiver.FORK_DEFAULT, exe=archiver.EXE)
|
||||
|
||||
assert ret == 2
|
||||
assert "borg list NAME lists contents of an archive and needs an archive NAME." in output
|
||||
assert "If you meant to list archives in a repository, use repo-list:" in output
|
||||
assert "borg -r REPO repo-list" in output
|
||||
assert "tip: For details of accepted options run: borg list --help" in output
|
||||
|
||||
|
||||
def test_list_without_name_with_repo_suggests_repo_list(archiver):
|
||||
ret, output = exec_cmd(
|
||||
"--repo",
|
||||
archiver.repository_location,
|
||||
"list",
|
||||
archiver=archiver.archiver,
|
||||
fork=archiver.FORK_DEFAULT,
|
||||
exe=archiver.EXE,
|
||||
)
|
||||
|
||||
assert ret == 2
|
||||
assert "borg list NAME lists contents of an archive and needs an archive NAME." in output
|
||||
assert "If you meant to list archives in a repository, use repo-list:" in output
|
||||
assert f"borg -r {archiver.repository_location} repo-list" in output
|
||||
assert "tip: For details of accepted options run: borg list --help" in output
|
||||
|
||||
|
||||
@pytest.mark.parametrize("command, parser", list(get_all_parsers().items()))
|
||||
def test_help_formatting(command, parser):
|
||||
if isinstance(parser.epilog, RstToTextLazy):
|
||||
|
|
|
|||
Loading…
Reference in a new issue