Repository: pallets/click (python)

Issue:
Click 8.0.0 `group.add_command(..)` does not replace name in description and when looking up defaults
With click 8.0.0 `click.group.add_command(..)` changed the behavior in how new command names are used:
Both the help text for the command as well as when looking up default values in `default_map` the previous command name instead of the one added by the group are shown.

Minimal example:

```
# test.py
import click

@click.command()
@click.option(
    "--qux",
    required=True
)
def foo(qux):
    print(qux)

@click.group()
def group():
    pass

group.add_command(foo, "bar")

group(default_map={"bar": {"qux": "val"}})
```
Run the above:
* with click 7.x: 
> $ python3 test.py bar --help
> Usage: test.py bar [OPTIONS]
> // and
> $ python3 test.py bar
> val
* with click 8.0.0:
> $ python3 test.py bar --help
> Usage: test.py foo [OPTIONS]
> // and
> $ python3 test.py bar
> Usage: test.py foo [OPTIONS]
> Try 'test.py foo --help' for help.
>
> Error: Missing option '--qux'.

I would have expected that both the help shows the name of the command as "bar" (as using "foo" will result in an error)
and the lookup for the default using "bar" as a key.

Environment:

- Python version: 3.9.5
- Click version: 8.0.0


Pull request:
return resolved name, not original name
Reverts #1422, but updates the documentation to get the desired behavior instead. Always returning the command's original name ignored that the command could be registered with a different name like `group.add_command(cmd, name)`.

<!--
Link to relevant issues or previous PRs, one per line. Use "fixes" to
automatically close an issue.
-->

- fixes #1895 

<!--
Ensure each step in CONTRIBUTING.rst is complete by adding an "x" to
each box below.

If only docs were changed, these aren't relevant and can be removed.
-->

Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [x] Add or update relevant docs, in the docs folder and in code.
- [x] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [x] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index b59fc44..6d807d1 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -20,6 +20,11 @@ Unreleased
     regular options do from ``default``. :issue:`1886`
 -   Added documentation that custom parameter types may be passed
     already valid values in addition to strings. :issue:`1898`
+-   Resolving commands returns the name that was given, not
+    ``command.name``, fixing an unintended change to help text and
+    ``default_map`` lookups. When using patterns like ``AliasedGroup``,
+    override ``resolve_command`` to change the name that is returned if
+    needed. :issue:`1895`
 
 
 Version 8.0.0
diff --git a/docs/advanced.rst b/docs/advanced.rst
index 7f17e0c..3df492a 100644
--- a/docs/advanced.rst
+++ b/docs/advanced.rst
@@ -35,7 +35,6 @@ it would accept ``pus`` as an alias (so long as it was unique):
 .. click:example::
 
     class AliasedGroup(click.Group):
-
         def get_command(self, ctx, cmd_name):
             rv = click.Group.get_command(self, ctx, cmd_name)
             if rv is not None:
@@ -48,6 +47,11 @@ it would accept ``pus`` as an alias (so long as it was unique):
                 return click.Group.get_command(self, ctx, matches[0])
             ctx.fail(f"Too many matches: {', '.join(sorted(matches))}")
 
+        def resolve_command(self, ctx, args):
+            # always return the full command name
+            _, cmd, args = super().resolve_command(ctx, args)
+            return cmd.name, cmd, args
+
 And it can then be used like this:
 
 .. click:example::
diff --git a/examples/aliases/aliases.py b/examples/aliases/aliases.py
index c3da657..af3caa6 100644
--- a/examples/aliases/aliases.py
+++ b/examples/aliases/aliases.py
@@ -67,6 +67,11 @@ class AliasedGroup(click.Group):
             return click.Group.get_command(self, ctx, matches[0])
         ctx.fail(f"Too many matches: {', '.join(sorted(matches))}")
 
+    def resolve_command(self, ctx, args):
+        # always return the command's name, not the alias
+        _, cmd, args = super().resolve_command(ctx, args)
+        return cmd.name, cmd, args
+
 
 def read_config(ctx, param, value):
     """Callback that is used whenever --config is passed.  We use this to
diff --git a/src/click/core.py b/src/click/core.py
index 63506ba..3a7533b 100644
--- a/src/click/core.py
+++ b/src/click/core.py
@@ -1717,7 +1717,7 @@ class MultiCommand(Command):
             if split_opt(cmd_name)[0]:
                 self.parse_args(ctx, ctx.args)
             ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name))
-        return cmd.name if cmd else None, cmd, args[1:]
+        return cmd_name if cmd else None, cmd, args[1:]
 
     def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]:
         """Given a context and a command name, this returns a


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
index 79f87fa..9ebf612 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -285,6 +285,10 @@ def test_aliased_command_canonical_name(runner):
         def get_command(self, ctx, cmd_name):
             return push
 
+        def resolve_command(self, ctx, args):
+            _, command, args = super().resolve_command(ctx, args)
+            return command.name, command, args
+
     cli = AliasedGroup()
 
     @cli.command()
@@ -296,6 +300,16 @@ def test_aliased_command_canonical_name(runner):
     assert result.output.startswith("Usage: root push [OPTIONS]")
 
 
+def test_group_add_command_name(runner):
+    cli = click.Group("cli")
+    cmd = click.Command("a", params=[click.Option(["-x"], required=True)])
+    cli.add_command(cmd, "b")
+    # Check that the command is accessed through the registered name,
+    # not the original name.
+    result = runner.invoke(cli, ["b"], default_map={"b": {"x": 3}})
+    assert result.exit_code == 0
+
+
 def test_unprocessed_options(runner):
     @click.command(context_settings=dict(ignore_unknown_options=True))
     @click.argument("args", nargs=-1, type=click.UNPROCESSED)