codecov-io: # [Codecov](https://codecov.io/gh/repobee/repobee/pull/283?src=pr&el=h1) Report
> Merging [#283](https://codecov.io/gh/repobee/repobee/pull/283?src=pr&el=desc) into [master](https://codecov.io/gh/repobee/repobee/commit/e9ad6a984900c9ea298b080d8834a1d969cb6a03?src=pr&el=desc) will **decrease** coverage by `0.02%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/repobee/repobee/pull/283/graphs/tree.svg?width=650&token=5vOKgkphZe&height=150&src=pr)](https://codecov.io/gh/repobee/repobee/pull/283?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #283      +/-   ##
==========================================
- Coverage   89.86%   89.83%   -0.03%     
==========================================
  Files          19       19              
  Lines        1411     1427      +16     
  Branches      256      256              
==========================================
+ Hits         1268     1282      +14     
- Misses        120      123       +3     
+ Partials       23       22       -1
```


| [Impacted Files](https://codecov.io/gh/repobee/repobee/pull/283?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [repobee/cli.py](https://codecov.io/gh/repobee/repobee/pull/283/diff?src=pr&el=tree#diff-cmVwb2JlZS9jbGkucHk=) | `97.8% <100%> (-0.64%)` | :arrow_down: |
| [repobee/main.py](https://codecov.io/gh/repobee/repobee/pull/283/diff?src=pr&el=tree#diff-cmVwb2JlZS9tYWluLnB5) | `100% <100%> (ø)` | :arrow_up: |
| [repobee/ext/gitlab.py](https://codecov.io/gh/repobee/repobee/pull/283/diff?src=pr&el=tree#diff-cmVwb2JlZS9leHQvZ2l0bGFiLnB5) | `64.59% <0%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/repobee/repobee/pull/283?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/repobee/repobee/pull/283?src=pr&el=footer). Last update [e9ad6a9...3052e1b](https://codecov.io/gh/repobee/repobee/pull/283?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

diff --git a/repobee/cli.py b/repobee/cli.py
index 7ff3eae..a775d90 100644
--- a/repobee/cli.py
+++ b/repobee/cli.py
@@ -112,9 +112,17 @@ DEPRECATED_PARSERS = {
     ),
 }
 
+# any pre-parser options go here
+PRE_PARSER_PLUG_OPTS = ["-p", "--plug"]
+PRE_PARSER_NO_PLUGS = "--no-plugins"
+PRE_PARSER_SHOW_ALL_OPTS = "--show-all-opts"
+
+# this list should include all pre-parser flags
+PRE_PARSER_FLAGS = [PRE_PARSER_NO_PLUGS, PRE_PARSER_SHOW_ALL_OPTS]
+
 
 def parse_args(
-    sys_args: Iterable[str]
+    sys_args: Iterable[str], show_all_opts=False
 ) -> (tuples.Args, Optional[apimeta.API]):
     """Parse the command line arguments and initialize an API.
 
@@ -125,7 +133,7 @@ def parse_args(
         a tuples.Args namedtuple with the arguments, and an initialized
         apimeta.API instance (or None of testing connection).
     """
-    parser = _create_parser()
+    parser = _create_parser(show_all_opts)
     args = parser.parse_args(_handle_deprecation(sys_args))
 
     if getattr(args, SUB) == SHOW_CONFIG_PARSER:
@@ -555,7 +563,7 @@ class _OrderedFormatter(argparse.HelpFormatter):
         super().add_arguments(actions)
 
 
-def _create_parser():
+def _create_parser(show_all_opts):
     """Create the parser."""
 
     parser = argparse.ArgumentParser(
@@ -574,19 +582,19 @@ def _create_parser():
         action="version",
         version="{} v{}".format(repobee.__package__, repobee.__version__),
     )
-    _add_subparsers(parser)
+    _add_subparsers(parser, show_all_opts)
     return parser
 
 
-def _add_subparsers(parser):
+def _add_subparsers(parser, show_all_opts):
     """Add all of the subparsers to the parser. Note that the parsers prefixed
     with `base_` do not have any parent parsers, so any parser inheriting from
     them must also inherit from the required `base_parser` (unless it is a
     `base_` prefixed parser, of course).
     """
 
-    base_parser, base_student_parser, master_org_parser = (
-        _create_base_parsers()
+    base_parser, base_student_parser, master_org_parser = _create_base_parsers(
+        show_all_opts
     )
 
     repo_name_parser = argparse.ArgumentParser(add_help=False)
@@ -701,11 +709,20 @@ def _add_subparsers(parser):
     )
 
 
-def _create_base_parsers():
+def _create_base_parsers(show_all_opts):
     """Create the base parsers."""
     configured_defaults = config.get_configured_defaults()
     config.execute_config_hooks()
 
+    if not show_all_opts and configured_defaults:
+        LOGGER.info(
+            "CLI options that are set in the config file are suppressed in "
+            "help sections, run with pre-parser option {all_opts_arg} to "
+            "unsuppress. Example: gitlab {all_opts_arg} setup -h".format(
+                all_opts_arg=PRE_PARSER_SHOW_ALL_OPTS
+            )
+        )
+
     def default(arg_name):
         return (
             configured_defaults[arg_name]
@@ -719,11 +736,67 @@ def _create_base_parsers():
     def api_requires(arg_name):
         return arg_name in plug.manager.hook.api_init_requires()
 
+    def hide_api_arg(arg_name):
+        # note that API args that are not required should not be shown even
+        # when show_all_opts is True, as they are not relevant.
+        return not api_requires(arg_name) or (
+            not show_all_opts and configured(arg_name)
+        )
+
+    def hide_configurable_arg(arg_name):
+        return not show_all_opts and configured(arg_name)
+
+    # API args help sections
+    user_help = argparse.SUPPRESS if hide_api_arg("user") else "Your username."
+    org_name_help = (
+        argparse.SUPPRESS
+        if hide_api_arg("org_name")
+        else "Name of the target organization"
+    )
+    base_url_help = (
+        argparse.SUPPRESS
+        if hide_api_arg("base_url")
+        else (
+            "Base url to a platform API. Must be HTTPS. For example, with "
+            "github.com, the base url is https://api.github.com, and with "
+            "GitHub enterprise, the url is https://<ENTERPRISE_HOST>/api/v3"
+        )
+    )
+    token_help = (
+        argparse.SUPPRESS
+        if hide_api_arg("token")
+        else (
+            "OAUTH token for the platform instance. Can also be specified in "
+            "the `REPOBEE_OAUTH` environment variable."
+        )
+    )
+
+    # other configurable args help sections
+    # these should not be checked against the api_requires function
+    students_file_help = (
+        argparse.SUPPRESS
+        if hide_configurable_arg("students_file")
+        else (
+            "Path to a list of student usernames. Put multiple usernames on "
+            "each line to form groups."
+        )
+    )
+    master_org_help = (
+        argparse.SUPPRESS
+        if hide_configurable_arg("master_org_name")
+        else (
+            "Name of the organization containing the master repos. "
+            "Defaults to the same value as `-o|--org-name` if left "
+            "unspecified. Note that config values take precedence "
+            "over this default."
+        )
+    )
+
     base_parser = argparse.ArgumentParser(add_help=False)
     base_parser.add_argument(
         "-u",
         "--user",
-        help=("Your username."),
+        help=user_help,
         type=str,
         required=not configured("user") and api_requires("user"),
         default=default("user"),
@@ -732,7 +805,7 @@ def _create_base_parsers():
     base_parser.add_argument(
         "-o",
         "--org-name",
-        help="Name of the target organization",
+        help=org_name_help,
         type=str,
         required=not configured("org_name") and api_requires("org_name"),
         default=default("org_name"),
@@ -740,22 +813,13 @@ def _create_base_parsers():
     base_parser.add_argument(
         "-bu",
         "--base-url",
-        help=(
-            "Base url to a platform API. Must be HTTPS. For example, with "
-            "github.com, the base url is https://api.github.com, and with "
-            "GitHub enterprise, the url is https://<ENTERPRISE_HOST>/api/v3"
-        ),
+        help=base_url_help,
         type=str,
         required=not configured("base_url") and api_requires("base_url"),
         default=default("base_url"),
     )
     base_parser.add_argument(
-        "-t",
-        "--token",
-        help="OAUTH token for the platform instance. Can also be specified in "
-        "the `REPOBEE_OAUTH` environment variable.",
-        type=str,
-        default=default("token"),
+        "-t", "--token", help=token_help, type=str, default=default("token")
     )
 
     base_parser.add_argument(
@@ -772,7 +836,7 @@ def _create_base_parsers():
     students.add_argument(
         "-sf",
         "--students-file",
-        help="Path to a list of student usernames.",
+        help=students_file_help,
         type=str,
         default=default("students_file"),
     )
@@ -788,9 +852,7 @@ def _create_base_parsers():
     master_org_parser.add_argument(
         "-mo",
         "--master-org-name",
-        help="Name of the organization containing the master repos. "
-        "Defaults to the same value as `-o|--org-name` if left unspecified. "
-        "Note that config values take precedence over this default.",
+        help=master_org_help,
         default=default("master_org_name"),
     )
 
@@ -915,8 +977,9 @@ def _repo_names_to_urls(
     return non_local_urls + local_uris
 
 
-def parse_plugins(sys_args: Tuple[str]):
-    """Parse all plugin arguments.
+def parse_preparser_options(sys_args: Tuple[str]):
+    """Parse all arguments that can somehow alter the end-user CLI, such
+    as plugins.
 
     Args:
         sys_args: Command line arguments.
@@ -925,16 +988,21 @@ def parse_plugins(sys_args: Tuple[str]):
         prog="repobee", description="plugin pre-parser for repobee."
     )
 
-    mutex_grp = parser.add_mutually_exclusive_group(required=True)
+    mutex_grp = parser.add_mutually_exclusive_group()
     mutex_grp.add_argument(
-        "-p",
-        "--plug",
+        *PRE_PARSER_PLUG_OPTS,
         help="Specify the name of a plugin to use.",
         type=str,
         action="append",
+        default=None
     )
     mutex_grp.add_argument(
-        "--no-plugins", help="Disable plugins.", action="store_true"
+        PRE_PARSER_NO_PLUGS, help="Disable plugins.", action="store_true"
+    )
+    mutex_grp.add_argument(
+        PRE_PARSER_SHOW_ALL_OPTS,
+        help="Unsuppress all options in help menus",
+        action="store_true",
     )
 
     args = parser.parse_args(sys_args)
diff --git a/repobee/main.py b/repobee/main.py
index 90e78cb..ce30b43 100644
--- a/repobee/main.py
+++ b/repobee/main.py
@@ -28,20 +28,20 @@ and supply the stack trace below.""".replace(
 
 
 def _separate_args(args: List[str]) -> (List[str], List[str]):
-    """Separate args into plugin args and repobee args."""
-    plugin_args = []
-    if args and (args[0].startswith("-p") or "plug" in args[0]):
+    """Separate args into preparser args and repobee args."""
+    preparser_args = []
+    if args and args[0].startswith("-"):
         cur = 0
         while cur < len(args) and args[cur].startswith("-"):
-            if args[cur].startswith("-p"):
-                plugin_args += args[cur : cur + 2]
+            if args[cur] in cli.PRE_PARSER_PLUG_OPTS:
+                preparser_args += args[cur : cur + 2]
                 cur += 2
-            elif args[cur] == "--no-plugins":
-                plugin_args.append(args[cur])
+            elif args[cur] in cli.PRE_PARSER_FLAGS:
+                preparser_args.append(args[cur])
                 cur += 1
             else:
                 break
-    return plugin_args, args[len(plugin_args) :]
+    return preparser_args, args[len(preparser_args) :]
 
 
 def main(sys_args: List[str]):
@@ -50,17 +50,18 @@ def main(sys_args: List[str]):
     traceback = False
     pre_init = True
     try:
-        plugin_args, app_args = _separate_args(args)
+        preparser_args, app_args = _separate_args(args)
+        parsed_preparser_args = cli.parse_preparser_options(preparser_args)
 
-        if plugin_args:
-            parsed_plugin_args = cli.parse_plugins(plugin_args)
-            if parsed_plugin_args.no_plugins:
-                LOGGER.info("Plugins disabled")
-            else:
-                plugin.initialize_plugins(parsed_plugin_args.plug)
-        else:
+        if parsed_preparser_args.no_plugins:
+            LOGGER.info("Non-default plugins disabled")
             plugin.initialize_plugins()
-        parsed_args, api = cli.parse_args(app_args)
+        else:
+            plugin.initialize_plugins(parsed_preparser_args.plug or [])
+
+        parsed_args, api = cli.parse_args(
+            app_args, show_all_opts=parsed_preparser_args.show_all_opts
+        )
         traceback = parsed_args.traceback
         pre_init = False
         cli.dispatch_command(parsed_args, api)

CLI help sections cleanup
The CLI `-h` sections are a bit bloated with stuff that you rarely put on the command line, such as the base url and the API token. It would possibly be a good idea to hide these if there are defaults, and add little note that they are hidden.

It would be even better if it was possible to specify a pre-parser option for showing them anyway.

This can also be a 2.0 feature as it's a bit tricky to implement and not crucial to current operation.
diff --git a/tests/unit_tests/test_cli.py b/tests/unit_tests/test_cli.py
index b685b70..2ceb94c 100644
--- a/tests/unit_tests/test_cli.py
+++ b/tests/unit_tests/test_cli.py
@@ -313,6 +313,36 @@ def test_help_calls_add_arguments(monkeypatch, parser):
 class TestBaseParsing:
     """Test the basic functionality of parsing."""
 
+    def test_show_all_opts_true_shows_configured_args(
+        self, config_mock, capsys
+    ):
+        """Test that configured args are shown when show_all_opts is True."""
+        with pytest.raises(SystemExit):
+            cli.parse_args([cli.SETUP_PARSER, "-h"], show_all_opts=True)
+
+        captured = capsys.readouterr()
+        assert "--user" in captured.out
+        assert "--base-url" in captured.out
+        assert "--org-name" in captured.out
+        assert "--master-org-name" in captured.out
+        assert "--students-file" in captured.out
+        assert "--token" in captured.out
+
+    def test_show_all_opts_false_hides_configured_args(
+        self, config_mock, capsys
+    ):
+        """Test that configured args are hidden when show_all_opts is False."""
+        with pytest.raises(SystemExit):
+            cli.parse_args([cli.SETUP_PARSER, "-h"], show_all_opts=False)
+
+        captured = capsys.readouterr()
+        assert "--user" not in captured.out
+        assert "--base-url" not in captured.out
+        assert "--org-name" not in captured.out
+        assert "--master-org-name" not in captured.out
+        assert "--students-file" not in captured.out
+        assert "--token" not in captured.out
+
     def test_raises_on_invalid_org(self, api_class_mock, students_file):
         """Test that an appropriate error is raised when the organization is
         not found.
diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py
index b7a845f..fa6cc37 100644
--- a/tests/unit_tests/test_main.py
+++ b/tests/unit_tests/test_main.py
@@ -1,3 +1,4 @@
+import argparse
 from unittest.mock import MagicMock
 from collections import namedtuple
 
@@ -45,10 +46,13 @@ def api_instance_mock(mocker):
 
 @pytest.fixture
 def init_plugins_mock(mocker):
+    def init_plugins(plugs=None):
+        list(map(module, plugs or []))
+
     return mocker.patch(
         "repobee.plugin.initialize_plugins",
         autospec=True,
-        side_effect=lambda plugs: list(map(module, plugs)),
+        side_effect=init_plugins,
     )
 
 
@@ -62,14 +66,8 @@ def parse_args_mock(mocker, api_instance_mock):
 
 
 @pytest.fixture
-def parse_plugins_mock(mocker):
-    return mocker.patch(
-        "repobee.cli.parse_plugins",
-        autospec=True,
-        side_effect=lambda args: [
-            arg for arg in args if not arg.startswith("-")
-        ],
-    )
+def parse_preparser_options_mock(mocker):
+    return mocker.patch("repobee.cli.parse_preparser_options", autospec=True)
 
 
 @pytest.fixture
@@ -85,7 +83,7 @@ def test_happy_path(
 
     main.main(sys_args)
 
-    parse_args_mock.assert_called_once_with(sys_args[1:])
+    parse_args_mock.assert_called_once_with(sys_args[1:], show_all_opts=False)
     dispatch_command_mock.assert_called_once_with(
         PARSED_ARGS, api_instance_mock
     )
@@ -101,7 +99,7 @@ def test_does_not_raise_on_exception_in_parsing(
 
     main.main(sys_args)
 
-    parse_args_mock.assert_called_once_with(sys_args[1:])
+    parse_args_mock.assert_called_once_with(sys_args[1:], show_all_opts=False)
     assert not dispatch_command_mock.called
 
 
@@ -125,18 +123,21 @@ def test_plugins_args(
     main.main(sys_args)
 
     init_plugins_mock.assert_called_once_with(["javac", "pylint"])
-    parse_args_mock.assert_called_once_with(CLONE_ARGS)
+    parse_args_mock.assert_called_once_with(CLONE_ARGS, show_all_opts=False)
 
 
 def test_no_plugins_arg(
     parse_args_mock, dispatch_command_mock, init_plugins_mock
 ):
+    """Default plugins still need to be loaded, so initialize_plugins should be
+    called without arguments.
+    """
     sys_args = ["repobee", "--no-plugins", *CLONE_ARGS]
 
     main.main(sys_args)
 
-    assert not init_plugins_mock.called
-    parse_args_mock.assert_called_once_with(CLONE_ARGS)
+    init_plugins_mock.assert_called_once_with()
+    parse_args_mock.assert_called_once_with(CLONE_ARGS, show_all_opts=False)
 
 
 def test_plugin_with_subparser_name(
@@ -147,7 +148,7 @@ def test_plugin_with_subparser_name(
     main.main(sys_args)
 
     init_plugins_mock.assert_called_once_with(["javac", "clone"])
-    parse_args_mock.assert_called_once_with(CLONE_ARGS)
+    parse_args_mock.assert_called_once_with(CLONE_ARGS, show_all_opts=False)
 
 
 def test_plug_arg_incompatible_with_no_plugins(
@@ -215,4 +216,37 @@ def test_logs_traceback_on_exception_in_dispatch_if_traceback(
     main.main(sys_args)
 
     assert logger_exception_mock.called
-    parse_args_mock.assert_called_once_with([*CLONE_ARGS, "--traceback"])
+    parse_args_mock.assert_called_once_with(
+        [*CLONE_ARGS, "--traceback"], show_all_opts=False
+    )
+
+
+def test_show_all_opts_correctly_separated(
+    parse_args_mock, parse_preparser_options_mock, no_config_mock
+):
+    msg = "expected exit"
+
+    def _raise_sysexit(*args, **kwargs):
+        raise SystemExit(msg)
+
+    parse_preparser_options_mock.return_value = argparse.Namespace(
+        show_all_opts=True, no_plugins=False, plug=None
+    )
+    parse_args_mock.side_effect = _raise_sysexit
+    sys_args = [
+        "repobee",
+        cli.PRE_PARSER_SHOW_ALL_OPTS,
+        cli.SETUP_PARSER,
+        "-h",
+    ]
+
+    with pytest.raises(SystemExit) as exc_info:
+        main.main(sys_args)
+
+    assert msg in str(exc_info.value)
+    parse_args_mock.assert_called_once_with(
+        [cli.SETUP_PARSER, "-h"], show_all_opts=True
+    )
+    parse_preparser_options_mock.assert_called_once_with(
+        [cli.PRE_PARSER_SHOW_ALL_OPTS]
+    )

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-mock
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
appdirs==1.4.4
certifi==2025.1.31
cffi==1.17.1
charset-normalizer==3.4.1
colored==2.3.0
coverage==7.8.0
cryptography==44.0.2
daiquiri==3.2.5.1
Deprecated==1.2.18
exceptiongroup==1.2.2
idna==3.10
iniconfig==2.1.0
packaging==24.2
pluggy==1.5.0
pycparser==2.22
PyGithub==2.6.1
PyJWT==2.10.1
PyNaCl==1.5.0
pytest==8.3.5
pytest-cov==6.0.0
pytest-mock==3.14.0
python-gitlab==1.8.0
python-json-logger==3.3.0
-e git+https://github.com/repobee/repobee.git@e9ad6a984900c9ea298b080d8834a1d969cb6a03#egg=repobee
repobee-plug==0.6.0
requests==2.32.3
six==1.17.0
tomli==2.2.1
typing_extensions==4.13.0
urllib3==2.3.0
wrapt==1.17.2

name: repobee
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - appdirs==1.4.4
      - certifi==2025.1.31
      - cffi==1.17.1
      - charset-normalizer==3.4.1
      - colored==2.3.0
      - coverage==7.8.0
      - cryptography==44.0.2
      - daiquiri==3.2.5.1
      - deprecated==1.2.18
      - exceptiongroup==1.2.2
      - idna==3.10
      - iniconfig==2.1.0
      - packaging==24.2
      - pluggy==1.5.0
      - pycparser==2.22
      - pygithub==2.6.1
      - pyjwt==2.10.1
      - pynacl==1.5.0
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - python-gitlab==1.8.0
      - python-json-logger==3.3.0
      - repobee==1.6.0
      - repobee-plug==0.6.0
      - requests==2.32.3
      - six==1.17.0
      - tomli==2.2.1
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - wrapt==1.17.2
prefix: /opt/conda/envs/repobee

tests/unit_tests/test_cli.py::TestBaseParsing::test_show_all_opts_true_shows_configured_args tests/unit_tests/test_cli.py::TestBaseParsing::test_show_all_opts_false_hides_configured_args tests/unit_tests/test_main.py::test_happy_path tests/unit_tests/test_main.py::test_does_not_raise_on_exception_in_parsing tests/unit_tests/test_main.py::test_plugins_args tests/unit_tests/test_main.py::test_no_plugins_arg tests/unit_tests/test_main.py::test_plugin_with_subparser_name tests/unit_tests/test_main.py::test_logs_traceback_on_exception_in_dispatch_if_traceback tests/unit_tests/test_main.py::test_show_all_opts_correctly_separated
tests/unit_tests/test_cli.py::TestDispatchCommand::test_raises_on_invalid_subparser_value tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[setup] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[update] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[clone] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[migrate] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[open-issues] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[close-issues] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[list-issues] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[verify-settings] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[assign-reviews] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[purge-review-teams] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[show-config] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[check-reviews] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[setup-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[setup-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[setup-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[setup-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[update-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[update-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[update-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[update-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[clone-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[clone-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[clone-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[clone-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[migrate-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[migrate-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[migrate-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[migrate-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[open-issues-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[open-issues-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[open-issues-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[open-issues-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[close-issues-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[close-issues-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[close-issues-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[close-issues-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[list-issues-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[list-issues-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[list-issues-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[list-issues-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[verify-settings-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[verify-settings-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[verify-settings-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[verify-settings-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[assign-reviews-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[assign-reviews-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[assign-reviews-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[assign-reviews-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[purge-review-teams-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[purge-review-teams-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[purge-review-teams-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[purge-review-teams-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[show-config-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[show-config-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[show-config-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[show-config-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[check-reviews-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[check-reviews-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[check-reviews-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[check-reviews-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_setup_student_repos_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_update_student_repos_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_open_issue_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_close_issue_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_migrate_repos_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_clone_repos_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_verify_settings_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_verify_settings_called_with_master_org_name tests/unit_tests/test_cli.py::test_help_calls_add_arguments[setup] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[update] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[clone] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[migrate] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[open-issues] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[close-issues] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[list-issues] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[verify-settings] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[assign-reviews] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[purge-review-teams] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[show-config] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[check-reviews] tests/unit_tests/test_cli.py::TestBaseParsing::test_raises_on_invalid_org tests/unit_tests/test_cli.py::TestBaseParsing::test_raises_on_bad_credentials tests/unit_tests/test_cli.py::TestBaseParsing::test_raises_on_invalid_base_url tests/unit_tests/test_cli.py::TestBaseParsing::test_master_org_overrides_target_org_for_master_repos[setup] tests/unit_tests/test_cli.py::TestBaseParsing::test_master_org_overrides_target_org_for_master_repos[update] tests/unit_tests/test_cli.py::TestBaseParsing::test_master_org_name_defaults_to_org_name[setup] tests/unit_tests/test_cli.py::TestBaseParsing::test_master_org_name_defaults_to_org_name[update] tests/unit_tests/test_cli.py::TestBaseParsing::test_token_env_variable_picked_up[setup] tests/unit_tests/test_cli.py::TestBaseParsing::test_token_env_variable_picked_up[update] tests/unit_tests/test_cli.py::TestBaseParsing::test_token_cli_arg_picked_up[setup] tests/unit_tests/test_cli.py::TestBaseParsing::test_token_cli_arg_picked_up[update] tests/unit_tests/test_cli.py::TestBaseParsing::test_raises_on_non_tls_api_url[http://some_enterprise_host/api/v3] tests/unit_tests/test_cli.py::TestBaseParsing::test_raises_on_non_tls_api_url[ftp://some_enterprise_host/api/v3] tests/unit_tests/test_cli.py::TestBaseParsing::test_raises_on_non_tls_api_url[some_enterprise_host/api/v3] tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_students_file_is_not_a_file[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_students_file_is_not_a_file[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_students_file_is_not_a_file[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_students_file_is_not_a_file[open-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_listing_students[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_listing_students[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_listing_students[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_listing_students[open-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_student_file[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_student_file[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_student_file[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_student_file[open-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_parsers_raise_on_empty_student_file[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_parsers_raise_on_empty_student_file[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_parsers_raise_on_empty_student_file[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_parsers_raise_on_empty_student_file[open-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parsers_raise_if_both_file_and_listing[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parsers_raise_if_both_file_and_listing[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parsers_raise_if_both_file_and_listing[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parsers_raise_if_both_file_and_listing[open-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_groups_parsed_correcly[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_groups_parsed_correcly[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_groups_parsed_correcly[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_groups_parsed_correcly[open-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_generated_team_name_too_long[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_generated_team_name_too_long[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_generated_team_name_too_long[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_generated_team_name_too_long[open-issues|['-mn', tests/unit_tests/test_cli.py::TestConfig::test_full_config[setup-extra_args0] tests/unit_tests/test_cli.py::TestConfig::test_full_config[update-extra_args1] tests/unit_tests/test_cli.py::TestConfig::test_full_config[open-issues-extra_args2] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_is_required[-bu] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_is_required[-u] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_is_required[-sf] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_is_required[-o] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_is_required[-mo] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_can_be_specified[-bu] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_can_be_specified[-u] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_can_be_specified[-sf] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_can_be_specified[-o] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_can_be_specified[-mo] tests/unit_tests/test_cli.py::TestSetupAndUpdateParsers::test_happy_path[setup] tests/unit_tests/test_cli.py::TestSetupAndUpdateParsers::test_happy_path[update] tests/unit_tests/test_cli.py::TestSetupAndUpdateParsers::test_finds_local_repo[setup] tests/unit_tests/test_cli.py::TestSetupAndUpdateParsers::test_finds_local_repo[update] tests/unit_tests/test_cli.py::TestMigrateParser::test_happy_path tests/unit_tests/test_cli.py::TestVerifyParser::test_happy_path tests/unit_tests/test_cli.py::TestCloneParser::test_happy_path tests/unit_tests/test_cli.py::TestCloneParser::test_no_other_parser_gets_parse_hook[setup-extra_args0] tests/unit_tests/test_cli.py::TestCloneParser::test_no_other_parser_gets_parse_hook[update-extra_args1] tests/unit_tests/test_cli.py::TestCloneParser::test_no_other_parser_gets_parse_hook[open-issues-extra_args2] tests/unit_tests/test_cli.py::TestCloneParser::test_no_other_parser_gets_parse_hook[close-issues-extra_args3] tests/unit_tests/test_cli.py::TestCloneParser::test_no_other_parser_gets_parse_hook[verify-settings-extra_args4] tests/unit_tests/test_cli.py::TestCloneParser::test_no_other_parser_gets_parse_hook[migrate-extra_args5] tests/unit_tests/test_cli.py::TestShowConfigParser::test_happy_path tests/unit_tests/test_cli.py::TestCommandDeprecation::test_deprecated_commands_parsed_to_current_commands[assign-peer-reviews-assign-reviews-sys_args0] tests/unit_tests/test_cli.py::TestCommandDeprecation::test_deprecated_commands_parsed_to_current_commands[purge-peer-review-teams-purge-review-teams-sys_args1] tests/unit_tests/test_cli.py::TestCommandDeprecation::test_deprecated_commands_parsed_to_current_commands[check-peer-review-progress-check-reviews-sys_args2] tests/unit_tests/test_main.py::test_does_not_raise_on_exception_in_handling_parsed_args tests/unit_tests/test_main.py::test_plug_arg_incompatible_with_no_plugins tests/unit_tests/test_main.py::test_invalid_plug_options tests/unit_tests/test_main.py::test_does_not_raise_on_exception_in_dispatch
repobee/cli.py repobee/main.py
repobee/cli.py repobee/main.py