Repository: pallets/click (python)

Issue:
context.forward inconsistently removes undeclared options
### Expected Behavior

Three commands. The first takes a subset of the options that the last two take. Each prints the arguments they were called with. All but the first call the one before it via `context.forward`.

```python
import click
from pprint import pprint

@click.group()
def main():
    pass

@main.command()
@click.option('-a')
def first(**kwargs):
    print('first')
    pprint(kwargs)

@main.command()
@click.option('-a')
@click.option('-b')
@click.pass_context
def second(context, **kwargs):
    print('second')
    pprint(kwargs)
    context.forward(first)

@main.command()
@click.option('-a')
@click.option('-b')
@click.pass_context
def third(context, **kwargs):
    print('third')
    pprint(kwargs)
    context.forward(second)

main()
```

Here's the behavior I expect from each command. Notably, `first` is called with the same arguments, its declared options, each time.

```
$ python main.py first
first
{'a': None}
$ python main.py second
second
{'a': None, 'b': None}
first
{'a': None}
$ python main.py third
third
{'a': None, 'b': None}
second
{'a': None, 'b': None}
first
{'a': None}
```


### Actual Behavior

```
$ python main.py first
first
{'a': None}
$ python main.py second
second
{'a': None, 'b': None}
first
{'a': None, 'b': None}     # 'first' is called with 'b' here
$ python main.py third
third
{'a': None, 'b': None}
second
{'a': None, 'b': None}
first
{'a': None}                # but not here
```


### Environment

* Python version: 3.7.5
* Click version: 7.1.2


Pull request:
Fixed inconsistency in forwarding params in Context.forward
`Context.forward` uses `Context.params` to fill in the options and passes the `kwargs` to `Context.invoke` which creates a new `Context` for the sub-command but doesn't pass the params to the new `Context` nor does it remove unused `kwargs`.

From the example mentioned in the [issue](https://github.com/pallets/click/issues/1568), now a union of params from the context and sub-context will be forwarded. 

For e.g. - If `command_1` has options `a and b` and `command_2` has options `a and c`, command_1 forwards to command_2, then `command_2` will receive `a, b and c` as `kwargs`. 

I was unsure whether or not to remove the unused options, so I've kept them for now. 

- fixes #1568

Checklist:

- [x] Add tests that demonstrate the correct behaviour 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 c6aa810..a94f5f7 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -203,6 +203,9 @@ Unreleased
     does not raise a ``UnicodeEncodeError``. :issue:`848`
 -   Fix an issue where ``readline`` would clear the entire ``prompt()``
     line instead of only the input when pressing backspace. :issue:`665`
+-   Add all kwargs passed to ``Context.invoke()`` to ``ctx.params``.
+    Fixes an inconsistency when nesting ``Context.forward()`` calls.
+    :issue:`1568`
 
 
 Version 7.1.2
diff --git a/src/click/core.py b/src/click/core.py
index a5e7a79..2a170c9 100644
--- a/src/click/core.py
+++ b/src/click/core.py
@@ -678,6 +678,10 @@ class Context:
         in against the intention of this code and no context was created.  For
         more information about this change and why it was done in a bugfix
         release see :ref:`upgrade-to-3.2`.
+
+        .. versionchanged:: 8.0
+            All ``kwargs`` are tracked in :attr:`params` so they will be
+            passed if :meth:`forward` is called at multiple levels.
         """
         self, callback = args[:2]
         ctx = self
@@ -700,6 +704,10 @@ class Context:
                 if param.name not in kwargs and param.expose_value:
                     kwargs[param.name] = param.get_default(ctx)
 
+            # Track all kwargs as params, so that forward() will pass
+            # them on in subsequent calls.
+            ctx.params.update(kwargs)
+
         args = args[2:]
         with augment_usage_errors(self):
             with ctx:
@@ -709,6 +717,10 @@ class Context:
         """Similar to :meth:`invoke` but fills in default keyword
         arguments from the current context if the other command expects
         it.  This cannot invoke callbacks directly, only other commands.
+
+        .. versionchanged:: 8.0
+            All ``kwargs`` are tracked in :attr:`params` so they will be
+            passed if ``forward`` is called at multiple levels.
         """
         self, cmd = args[:2]
 


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
index 205e0f8..79f87fa 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -39,6 +39,28 @@ def test_other_command_forward(runner):
     assert result.output == "Count: 1\nCount: 42\n"
 
 
+def test_forwarded_params_consistency(runner):
+    cli = click.Group()
+
+    @cli.command()
+    @click.option("-a")
+    @click.pass_context
+    def first(ctx, **kwargs):
+        click.echo(f"{ctx.params}")
+
+    @cli.command()
+    @click.option("-a")
+    @click.option("-b")
+    @click.pass_context
+    def second(ctx, **kwargs):
+        click.echo(f"{ctx.params}")
+        ctx.forward(first)
+
+    result = runner.invoke(cli, ["second", "-a", "foo", "-b", "bar"])
+    assert not result.exception
+    assert result.output == "{'a': 'foo', 'b': 'bar'}\n{'a': 'foo', 'b': 'bar'}\n"
+
+
 def test_auto_shorthelp(runner):
     @click.group()
     def cli():