codecov[bot]: # [Codecov](https://codecov.io/gh/repobee/repobee/pull/440?src=pr&el=h1) Report
> Merging [#440](https://codecov.io/gh/repobee/repobee/pull/440?src=pr&el=desc) into [master](https://codecov.io/gh/repobee/repobee/commit/241bcf066d987e3598a631ff7c8772a484006782&el=desc) will **decrease** coverage by `4.71%`.
> The diff coverage is `89.28%`.

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

```diff
@@            Coverage Diff             @@
##           master     #440      +/-   ##
==========================================
- Coverage   91.42%   86.70%   -4.72%     
==========================================
  Files          28       28              
  Lines        1819     1813       -6     
  Branches      339      338       -1     
==========================================
- Hits         1663     1572      -91     
- Misses        124      206      +82     
- Partials       32       35       +3     
```


| [Impacted Files](https://codecov.io/gh/repobee/repobee/pull/440?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/\_repobee/plugin.py](https://codecov.io/gh/repobee/repobee/pull/440/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL3BsdWdpbi5weQ==) | `91.42% <ø> (-0.17%)` | :arrow_down: |
| [src/\_repobee/command/repos.py](https://codecov.io/gh/repobee/repobee/pull/440/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2NvbW1hbmQvcmVwb3MucHk=) | `91.80% <25.00%> (-3.32%)` | :arrow_down: |
| [src/\_repobee/cli/dispatch.py](https://codecov.io/gh/repobee/repobee/pull/440/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2NsaS9kaXNwYXRjaC5weQ==) | `100.00% <100.00%> (ø)` | |
| [src/\_repobee/cli/mainparser.py](https://codecov.io/gh/repobee/repobee/pull/440/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2NsaS9tYWlucGFyc2VyLnB5) | `96.42% <100.00%> (+0.02%)` | :arrow_up: |
| [src/\_repobee/cli/parsing.py](https://codecov.io/gh/repobee/repobee/pull/440/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2NsaS9wYXJzaW5nLnB5) | `92.26% <100.00%> (-1.20%)` | :arrow_down: |
| [src/\_repobee/cli/preparser.py](https://codecov.io/gh/repobee/repobee/pull/440/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2NsaS9wcmVwYXJzZXIucHk=) | `100.00% <100.00%> (ø)` | |
| [src/\_repobee/config.py](https://codecov.io/gh/repobee/repobee/pull/440/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2NvbmZpZy5weQ==) | `98.52% <100.00%> (ø)` | |
| [src/\_repobee/main.py](https://codecov.io/gh/repobee/repobee/pull/440/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL21haW4ucHk=) | `91.30% <100.00%> (+4.07%)` | :arrow_up: |
| [src/\_repobee/ext/gitlab.py](https://codecov.io/gh/repobee/repobee/pull/440/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2V4dC9naXRsYWIucHk=) | `63.86% <0.00%> (-28.56%)` | :arrow_down: |
| [src/\_repobee/git.py](https://codecov.io/gh/repobee/repobee/pull/440/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2dpdC5weQ==) | `96.62% <0.00%> (-3.38%)` | :arrow_down: |
| ... and [6 more](https://codecov.io/gh/repobee/repobee/pull/440/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/repobee/repobee/pull/440?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/440?src=pr&el=footer). Last update [241bcf0...0de498e](https://codecov.io/gh/repobee/repobee/pull/440?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

diff --git a/src/_repobee/cli/dispatch.py b/src/_repobee/cli/dispatch.py
index b600d98..bcf99e8 100644
--- a/src/_repobee/cli/dispatch.py
+++ b/src/_repobee/cli/dispatch.py
@@ -36,6 +36,7 @@ from _repobee.cli.mainparser import (
 def dispatch_command(
     args: argparse.Namespace,
     api: plug.API,
+    config_file: pathlib.Path,
     ext_commands: Optional[List[plug.ExtensionCommand]] = None,
 ):
     """Handle parsed CLI arguments and dispatch commands to the appropriate
@@ -45,6 +46,7 @@ def dispatch_command(
     Args:
         args: A namespace of parsed command line arguments.
         api: An initialized plug.API instance.
+        config_file: Path to the config file.
         ext_commands: A list of active extension commands.
     """
     hook_results = {}
@@ -101,7 +103,7 @@ def dispatch_command(
     elif args.subparser == PURGE_REVIEW_TEAMS_PARSER:
         command.purge_review_teams(args.master_repo_names, args.students, api)
     elif args.subparser == SHOW_CONFIG_PARSER:
-        command.show_config()
+        command.show_config(config_file)
     elif args.subparser == CHECK_REVIEW_PROGRESS_PARSER:
         command.check_peer_review_progress(
             args.master_repo_names,
diff --git a/src/_repobee/cli/mainparser.py b/src/_repobee/cli/mainparser.py
index 0320e86..2523bd1 100644
--- a/src/_repobee/cli/mainparser.py
+++ b/src/_repobee/cli/mainparser.py
@@ -8,6 +8,7 @@
 
 import types
 import argparse
+import pathlib
 
 import logging
 from typing import List, Optional
@@ -123,11 +124,17 @@ def create_parser_for_docs() -> argparse.ArgumentParser:
     # load default plugins
     plugin.initialize_plugins([_repobee.constants.DEFAULT_PLUGIN])
     ext_commands = plug.manager.hook.create_extension_command()
-    return create_parser(show_all_opts=True, ext_commands=ext_commands)
+    return create_parser(
+        show_all_opts=True,
+        ext_commands=ext_commands,
+        config_file=_repobee.constants.DEFAULT_CONFIG_FILE,
+    )
 
 
 def create_parser(
-    show_all_opts: bool, ext_commands: Optional[List[plug.ExtensionCommand]]
+    show_all_opts: bool,
+    ext_commands: Optional[List[plug.ExtensionCommand]],
+    config_file: pathlib.Path,
 ) -> argparse.ArgumentParser:
     """Create the primary parser.
 
@@ -135,6 +142,7 @@ def create_parser(
         show_all_opts: If False, help sections for options with configured
             defaults are suppressed. Otherwise, all options are shown.
         ext_commands: A list of extension commands.
+        config_file: Path to the config file.
     Returns:
         The primary parser.
     """
@@ -181,7 +189,7 @@ def create_parser(
         action="version",
         version="{}".format(_repobee.__version__),
     )
-    _add_subparsers(parser, show_all_opts, ext_commands)
+    _add_subparsers(parser, show_all_opts, ext_commands, config_file)
 
     return parser
 
@@ -475,7 +483,7 @@ def _add_extension_parsers(
     return ext_commands
 
 
-def _add_subparsers(parser, show_all_opts, ext_commands):
+def _add_subparsers(parser, show_all_opts, ext_commands, config_file):
     """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
@@ -483,7 +491,7 @@ def _add_subparsers(parser, show_all_opts, ext_commands):
     """
 
     base_parser, base_student_parser, master_org_parser = _create_base_parsers(
-        show_all_opts
+        show_all_opts, config_file
     )
 
     subparsers = parser.add_subparsers(dest=SUB)
@@ -619,9 +627,9 @@ def _add_subparsers(parser, show_all_opts, ext_commands):
     )
 
 
-def _create_base_parsers(show_all_opts):
+def _create_base_parsers(show_all_opts, config_file):
     """Create the base parsers."""
-    configured_defaults = config.get_configured_defaults()
+    configured_defaults = config.get_configured_defaults(config_file)
 
     def default(arg_name):
         return (
diff --git a/src/_repobee/cli/parsing.py b/src/_repobee/cli/parsing.py
index 1364074..1cec0a2 100644
--- a/src/_repobee/cli/parsing.py
+++ b/src/_repobee/cli/parsing.py
@@ -46,6 +46,7 @@ def handle_args(
     sys_args: Iterable[str],
     show_all_opts: bool = False,
     ext_commands: Optional[List[plug.ExtensionCommand]] = None,
+    config_file: pathlib.Path = constants.DEFAULT_CONFIG_FILE,
 ) -> Tuple[argparse.Namespace, Optional[plug.API]]:
     """Parse and process command line arguments and instantiate the platform
     API (if it's needed).
@@ -53,6 +54,7 @@ def handle_args(
 
     Args:
         sys_args: Raw command line arguments for the primary parser.
+        config_file: Path to the config file.
         show_all_opts: If False, help sections for options that have
             configured defaults are suppressed. Otherwise, all options are
             shown.
@@ -62,7 +64,9 @@ def handle_args(
         A tuple of a namespace with parsed and processed arguments, and an
         instance of the platform API if it is required for the command.
     """
-    args, processing = _parse_args(sys_args, show_all_opts, ext_commands)
+    args, processing = _parse_args(
+        sys_args, config_file, show_all_opts, ext_commands
+    )
     if processing == _ArgsProcessing.CORE:
         processed_args, api = _process_args(args, ext_commands)
         _handle_task_parsing(processed_args)
@@ -75,6 +79,7 @@ def handle_args(
 
 def _parse_args(
     sys_args: Iterable[str],
+    config_file: pathlib.Path,
     show_all_opts: bool = False,
     ext_commands: Optional[List[plug.ExtensionCommand]] = None,
 ) -> Tuple[argparse.Namespace, _ArgsProcessing]:
@@ -84,6 +89,7 @@ def _parse_args(
 
     Args:
         sys_args: A list of command line arguments.
+        config_file: Path to the config file.
         show_all_opts: If False, CLI arguments that are configure in the
             configuration file are not shown in help menus.
         ext_commands: A list of extension commands.
@@ -92,7 +98,7 @@ def _parse_args(
         A namespace of parsed arpuments and a boolean that specifies whether or
         not further processing is required.
     """
-    parser = create_parser(show_all_opts, ext_commands)
+    parser = create_parser(show_all_opts, ext_commands, config_file)
     args = parser.parse_args(_handle_deprecation(sys_args))
 
     ext_cmd = _resolve_extension_command(args.subparser, ext_commands)
diff --git a/src/_repobee/cli/preparser.py b/src/_repobee/cli/preparser.py
index d098610..c580802 100644
--- a/src/_repobee/cli/preparser.py
+++ b/src/_repobee/cli/preparser.py
@@ -14,13 +14,17 @@ possible if a separate parser runs before the primary parser.
 .. moduleauthor:: Simon Larsén
 """
 
-# any pre-parser options go here
 import argparse
+import pathlib
 from typing import List
 
 import _repobee.cli
+import _repobee.constants
 
 PRE_PARSER_PLUG_OPTS = ["-p", "--plug"]
+PRE_PARSER_CONFIG_OPTS = ["-c", "--config-file"]
+PRE_PARSER_OPTS = PRE_PARSER_PLUG_OPTS + PRE_PARSER_CONFIG_OPTS
+
 PRE_PARSER_NO_PLUGS = "--no-plugins"
 PRE_PARSER_SHOW_ALL_OPTS = "--show-all-opts"
 # this list should include all pre-parser flags
@@ -40,6 +44,13 @@ def parse_args(sys_args: List[str]) -> argparse.Namespace:
         prog="repobee", description="plugin pre-parser for _repobee."
     )
 
+    parser.add_argument(
+        *PRE_PARSER_CONFIG_OPTS,
+        help="Specify path to the config file to use.",
+        type=pathlib.Path,
+        default=_repobee.constants.DEFAULT_CONFIG_FILE,
+    )
+
     mutex_grp = parser.add_mutually_exclusive_group()
     mutex_grp.add_argument(
         *PRE_PARSER_PLUG_OPTS,
@@ -74,7 +85,7 @@ def separate_args(args: List[str]) -> (List[str], List[str]):
     if args and args[0].startswith("-"):
         cur = 0
         while cur < len(args) and args[cur].startswith("-"):
-            if args[cur] in _repobee.cli.preparser.PRE_PARSER_PLUG_OPTS:
+            if args[cur] in _repobee.cli.preparser.PRE_PARSER_OPTS:
                 preparser_args += args[cur : cur + 2]
                 cur += 2
             elif args[cur] in _repobee.cli.preparser.PRE_PARSER_FLAGS:
diff --git a/src/_repobee/command/repos.py b/src/_repobee/command/repos.py
index 0fa74e4..e959aa8 100644
--- a/src/_repobee/command/repos.py
+++ b/src/_repobee/command/repos.py
@@ -28,7 +28,6 @@ from _repobee import git
 from _repobee import util
 from _repobee import exception
 from _repobee import config
-from _repobee import constants
 from _repobee import plugin
 from _repobee.git import Push
 
@@ -351,16 +350,12 @@ def _create_push_tuples(
     return push_tuples
 
 
-def show_config() -> None:
+def show_config(config_file: pathlib.Path) -> None:
     """Print the configuration file to the log."""
-    config.check_config_integrity()
+    config.check_config_integrity(config_file)
 
-    LOGGER.info(
-        "Found valid config file at " + str(constants.DEFAULT_CONFIG_FILE)
-    )
-    with constants.DEFAULT_CONFIG_FILE.open(
-        encoding=sys.getdefaultencoding()
-    ) as f:
+    LOGGER.info(f"Found valid config file at {config_file}")
+    with config_file.open(encoding=sys.getdefaultencoding()) as f:
         config_contents = "".join(f.readlines())
 
     output = (
diff --git a/src/_repobee/config.py b/src/_repobee/config.py
index 409ceb2..1a1b194 100644
--- a/src/_repobee/config.py
+++ b/src/_repobee/config.py
@@ -23,9 +23,7 @@ from _repobee import constants
 LOGGER = daiquiri.getLogger(__file__)
 
 
-def get_configured_defaults(
-    config_file: Union[str, pathlib.Path] = constants.DEFAULT_CONFIG_FILE
-) -> dict:
+def get_configured_defaults(config_file: Union[str, pathlib.Path]) -> dict:
     """Access the config file and return a ConfigParser instance with
     its contents.
 
@@ -63,9 +61,7 @@ def check_defaults(
         )
 
 
-def get_plugin_names(
-    config_file: Union[str, pathlib.Path] = constants.DEFAULT_CONFIG_FILE
-) -> List[str]:
+def get_plugin_names(config_file: Union[str, pathlib.Path]) -> List[str]:
     """Return a list of unqualified names of plugins listed in the config. The
     order of the plugins is preserved.
 
@@ -90,9 +86,7 @@ def get_plugin_names(
     return [name.strip() for name in plugin_string.split(",") if name]
 
 
-def execute_config_hooks(
-    config_file: Union[str, pathlib.Path] = constants.DEFAULT_CONFIG_FILE
-) -> None:
+def execute_config_hooks(config_file: Union[str, pathlib.Path]) -> None:
     """Execute all config hooks.
 
     Args:
@@ -105,9 +99,7 @@ def execute_config_hooks(
     plug.manager.hook.config_hook(config_parser=config_parser)
 
 
-def check_config_integrity(
-    config_file: Union[str, pathlib.Path] = constants.DEFAULT_CONFIG_FILE
-) -> None:
+def check_config_integrity(config_file: Union[str, pathlib.Path]) -> None:
     """Raise an exception if the configuration file contains syntactical
     errors, or if the defaults are misconfigured. Note that plugin options are
     not checked.
@@ -158,9 +150,7 @@ def _fetch_token() -> Optional[str]:
     return token or token_from_old
 
 
-def _read_defaults(
-    config_file: pathlib.Path = constants.DEFAULT_CONFIG_FILE,
-) -> dict:
+def _read_defaults(config_file: pathlib.Path) -> dict:
     token = _fetch_token()
     if not config_file.is_file():
         return {} if not token else dict(token=token)
@@ -175,9 +165,7 @@ def _read_defaults(
     return defaults
 
 
-def _read_config(
-    config_file: pathlib.Path = constants.DEFAULT_CONFIG_FILE,
-) -> configparser.ConfigParser:
+def _read_config(config_file: pathlib.Path) -> configparser.ConfigParser:
     config_parser = configparser.ConfigParser()
     try:
         config_parser.read(str(config_file))
diff --git a/src/_repobee/main.py b/src/_repobee/main.py
index be5ce7b..1c9afde 100644
--- a/src/_repobee/main.py
+++ b/src/_repobee/main.py
@@ -44,13 +44,14 @@ def main(sys_args: List[str]):
         parsed_preparser_args = _repobee.cli.preparser.parse_args(
             preparser_args
         )
+        config_file = parsed_preparser_args.config_file
 
         if parsed_preparser_args.no_plugins:
             LOGGER.info("Non-default plugins disabled")
             plugin.initialize_plugins([constants.DEFAULT_PLUGIN])
         else:
             plugin_names = plugin.resolve_plugin_names(
-                parsed_preparser_args.plug, constants.DEFAULT_CONFIG_FILE
+                parsed_preparser_args.plug, config_file
             )
             # IMPORTANT: the default plugin MUST be loaded last to ensure that
             # any user-defined plugins override the firstresult hooks
@@ -58,16 +59,19 @@ def main(sys_args: List[str]):
                 plugin_names + [constants.DEFAULT_PLUGIN]
             )
 
-        config.execute_config_hooks()
+        config.execute_config_hooks(config_file)
         ext_commands = plug.manager.hook.create_extension_command()
         parsed_args, api = _repobee.cli.parsing.handle_args(
             app_args,
             show_all_opts=parsed_preparser_args.show_all_opts,
             ext_commands=ext_commands,
+            config_file=config_file,
         )
         traceback = parsed_args.traceback
         pre_init = False
-        _repobee.cli.dispatch.dispatch_command(parsed_args, api, ext_commands)
+        _repobee.cli.dispatch.dispatch_command(
+            parsed_args, api, config_file, ext_commands
+        )
     except exception.PluginLoadError as exc:
         LOGGER.error("{.__class__.__name__}: {}".format(exc, str(exc)))
         LOGGER.error(
diff --git a/src/_repobee/plugin.py b/src/_repobee/plugin.py
index 7605609..c0361b6 100644
--- a/src/_repobee/plugin.py
+++ b/src/_repobee/plugin.py
@@ -23,7 +23,6 @@ import daiquiri
 import _repobee
 from _repobee import config
 from _repobee import exception
-from _repobee import constants
 
 import repobee_plug as plug
 
@@ -129,8 +128,7 @@ def initialize_plugins(plugin_names: List[str] = None):
 
 
 def resolve_plugin_names(
-    plugin_names: Optional[List[str]] = None,
-    config_file: pathlib.Path = constants.DEFAULT_CONFIG_FILE,
+    plugin_names: Optional[List[str]], config_file: pathlib.Path,
 ) -> List[str]:
     """Return a list of plugin names to load into RepoBee given a list of
     externally specified plugin names, and a path to a configuration file.

The preparser needs a --config-file option
To specify which config file to use. It's really quite essential to do system testing of plugins.
diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py
index ae428fb..ac28544 100644
--- a/tests/unit_tests/conftest.py
+++ b/tests/unit_tests/conftest.py
@@ -18,7 +18,7 @@ import constants
 sys.modules["github"] = MagicMock()
 
 
-import _repobee  # noqa: F402
+import _repobee  # noqa: E402
 
 EXPECTED_ENV_VARIABLES = [
     _repobee.constants.TOKEN_ENV,
@@ -52,6 +52,15 @@ def _students_file(populate: bool = True):
         yield file
 
 
+@pytest.fixture
+def unused_path():
+    with tempfile.NamedTemporaryFile() as tmpfile:
+        unused_path = pathlib.Path(tmpfile.name)
+
+    # exiting the with block destroys the temporary file
+    return unused_path
+
+
 @pytest.fixture(autouse=True)
 def unregister_plugins():
     """All plugins should be unregistered after each function."""
diff --git a/tests/unit_tests/test_cli.py b/tests/unit_tests/test_cli.py
index 0d5fe5a..21a64a2 100644
--- a/tests/unit_tests/test_cli.py
+++ b/tests/unit_tests/test_cli.py
@@ -34,6 +34,8 @@ generate_repo_url = functions.generate_repo_url
 MASTER_ORG_NAME = constants.MASTER_ORG_NAME
 TOKEN = constants.TOKEN
 
+EMPTY_PATH = pathlib.Path(".")
+
 REPO_NAMES = ("week-1", "week-2", "week-3")
 REPO_URLS = tuple(map(lambda rn: generate_repo_url(rn, ORG_NAME), REPO_NAMES))
 
@@ -240,7 +242,9 @@ class TestDispatchCommand:
             command_func, autospec=True, return_value=hook_result_mapping
         ):
             args = argparse.Namespace(subparser=parser, **args_dict)
-            _repobee.cli.dispatch.dispatch_command(args, api_instance_mock)
+            _repobee.cli.dispatch.dispatch_command(
+                args, api_instance_mock, EMPTY_PATH
+            )
 
         write.assert_called_once_with(expected_json, expected_filepath)
 
@@ -261,7 +265,9 @@ class TestDispatchCommand:
             args = argparse.Namespace(
                 subparser=mainparser.CLONE_PARSER, **args_dict
             )
-            _repobee.cli.dispatch.dispatch_command(args, api_instance_mock)
+            _repobee.cli.dispatch.dispatch_command(
+                args, api_instance_mock, EMPTY_PATH
+            )
 
         assert not write.called
 
@@ -270,7 +276,9 @@ class TestDispatchCommand:
         args = argparse.Namespace(subparser=parser, **VALID_PARSED_ARGS)
 
         with pytest.raises(exception.ParseError) as exc_info:
-            _repobee.cli.dispatch.dispatch_command(args, api_instance_mock)
+            _repobee.cli.dispatch.dispatch_command(
+                args, api_instance_mock, EMPTY_PATH
+            )
         assert "Illegal value for subparser: {}".format(parser) in str(
             exc_info.value
         )
@@ -281,7 +289,7 @@ class TestDispatchCommand:
         """Test that valid arguments does not result in crash. Only validates
         that there are no crashes, does not validate any other behavior!"""
         _repobee.cli.dispatch.dispatch_command(
-            parsed_args_all_subparsers, api_instance_mock
+            parsed_args_all_subparsers, api_instance_mock, EMPTY_PATH
         )
 
     def test_setup_student_repos_called_with_correct_args(
@@ -291,7 +299,9 @@ class TestDispatchCommand:
             subparser=mainparser.SETUP_PARSER, **VALID_PARSED_ARGS
         )
 
-        _repobee.cli.dispatch.dispatch_command(args, api_instance_mock)
+        _repobee.cli.dispatch.dispatch_command(
+            args, api_instance_mock, EMPTY_PATH
+        )
 
         command_mock.setup_student_repos.assert_called_once_with(
             args.master_repo_urls, args.students, api_instance_mock
@@ -304,7 +314,9 @@ class TestDispatchCommand:
             subparser=mainparser.UPDATE_PARSER, **VALID_PARSED_ARGS
         )
 
-        _repobee.cli.dispatch.dispatch_command(args, api_instance_mock)
+        _repobee.cli.dispatch.dispatch_command(
+            args, api_instance_mock, EMPTY_PATH
+        )
 
         command_mock.update_student_repos.assert_called_once_with(
             args.master_repo_urls,
@@ -319,7 +331,9 @@ class TestDispatchCommand:
         )
         expected_arg = list(args.students)
 
-        _repobee.cli.dispatch.dispatch_command(args, api_instance_mock)
+        _repobee.cli.dispatch.dispatch_command(
+            args, api_instance_mock, EMPTY_PATH
+        )
 
         api_instance_mock.ensure_teams_and_members.assert_called_once_with(
             expected_arg
@@ -332,7 +346,9 @@ class TestDispatchCommand:
             subparser=mainparser.OPEN_ISSUE_PARSER, **VALID_PARSED_ARGS
         )
 
-        _repobee.cli.dispatch.dispatch_command(args, api_instance_mock)
+        _repobee.cli.dispatch.dispatch_command(
+            args, api_instance_mock, EMPTY_PATH
+        )
 
         command_mock.open_issue.assert_called_once_with(
             args.issue,
@@ -348,7 +364,9 @@ class TestDispatchCommand:
             subparser=mainparser.CLOSE_ISSUE_PARSER, **VALID_PARSED_ARGS
         )
 
-        _repobee.cli.dispatch.dispatch_command(args, api_instance_mock)
+        _repobee.cli.dispatch.dispatch_command(
+            args, api_instance_mock, EMPTY_PATH
+        )
 
         command_mock.close_issue.assert_called_once_with(
             args.title_regex, args.repos, api_instance_mock
@@ -361,7 +379,9 @@ class TestDispatchCommand:
             subparser=mainparser.MIGRATE_PARSER, **VALID_PARSED_ARGS
         )
 
-        _repobee.cli.dispatch.dispatch_command(args, api_instance_mock)
+        _repobee.cli.dispatch.dispatch_command(
+            args, api_instance_mock, EMPTY_PATH
+        )
 
         command_mock.migrate_repos.assert_called_once_with(
             args.master_repo_urls, api_instance_mock
@@ -374,7 +394,9 @@ class TestDispatchCommand:
             subparser=mainparser.CLONE_PARSER, **VALID_PARSED_ARGS
         )
 
-        _repobee.cli.dispatch.dispatch_command(args, api_instance_mock)
+        _repobee.cli.dispatch.dispatch_command(
+            args, api_instance_mock, EMPTY_PATH
+        )
 
         command_mock.clone_repos.assert_called_once_with(
             args.repos, api_instance_mock
@@ -392,7 +414,7 @@ class TestDispatchCommand:
             master_org_name=None,
         )
 
-        _repobee.cli.dispatch.dispatch_command(args, None)
+        _repobee.cli.dispatch.dispatch_command(args, None, EMPTY_PATH)
 
         api_class_mock.verify_settings.assert_called_once_with(
             args.user, args.org_name, args.base_url, TOKEN, None
@@ -408,7 +430,7 @@ class TestDispatchCommand:
             master_org_name=MASTER_ORG_NAME,
         )
 
-        _repobee.cli.dispatch.dispatch_command(args, None)
+        _repobee.cli.dispatch.dispatch_command(args, None, EMPTY_PATH)
 
         api_class_mock.verify_settings.assert_called_once_with(
             args.user, args.org_name, args.base_url, TOKEN, MASTER_ORG_NAME
@@ -779,7 +801,7 @@ class TestExtensionCommands:
         )
 
         _repobee.cli.dispatch.dispatch_command(
-            parsed_args, None, [ext_command]
+            parsed_args, None, EMPTY_PATH, [ext_command]
         )
 
         # for some reason, this completely sane assertion fails ALWAYS
@@ -815,7 +837,7 @@ class TestExtensionCommands:
         )
 
         _repobee.cli.dispatch.dispatch_command(
-            parsed_args, api_instance_mock, [ext_command]
+            parsed_args, api_instance_mock, EMPTY_PATH, [ext_command]
         )
 
         # for some reason, this completely sane assertion fails ALWAYS
diff --git a/tests/unit_tests/test_command.py b/tests/unit_tests/test_command.py
index 5245b77..7a405e9 100644
--- a/tests/unit_tests/test_command.py
+++ b/tests/unit_tests/test_command.py
@@ -495,15 +495,15 @@ class TestCloneRepos:
     """Tests for clone_repos."""
 
     @pytest.fixture
-    def register_default_plugins(self):
+    def register_default_plugins(self, unused_path):
         plugin_names = plugin.resolve_plugin_names(
-            plugin_names=constants.PLUGINS
+            plugin_names=constants.PLUGINS, config_file=unused_path
         )
         modules = plugin.load_plugin_modules(plugin_names)
         plugin.register_plugins(modules)
 
     @pytest.fixture
-    def act_mocks(self, monkeypatch, config_mock):
+    def act_mocks(self, monkeypatch, config_mock, unused_path):
         """Mocks for the act functions and method. This is a bit
         messy as the functions must be marked with the
         repobee_plug.repobee_hook decorator to be picked up by pluggy.
@@ -536,7 +536,7 @@ class TestCloneRepos:
         _repobee.ext.pylint.act(None, None)
 
         plugin_names = plugin.resolve_plugin_names(
-            plugin_names=constants.PLUGINS
+            plugin_names=constants.PLUGINS, config_file=unused_path
         )
         modules = plugin.load_plugin_modules(plugin_names)
         plugin.register_plugins(modules)
diff --git a/tests/unit_tests/test_config.py b/tests/unit_tests/test_config.py
index e7e8757..7466b82 100644
--- a/tests/unit_tests/test_config.py
+++ b/tests/unit_tests/test_config.py
@@ -1,6 +1,8 @@
 import os
 from unittest import mock
+
 import pytest
+
 import _repobee.constants
 from _repobee import config
 from _repobee import exception
@@ -19,8 +21,10 @@ CONFIG_TOKEN = constants.CONFIG_TOKEN
 class TestGetConfiguredDefaults:
     """Tests for get_configured_defaults"""
 
-    def test_get_configured_defaults_no_config_file(self, isfile_mock):
-        defaults = config.get_configured_defaults()
+    def test_get_configured_defaults_no_config_file(
+        self, isfile_mock, unused_path
+    ):
+        defaults = config.get_configured_defaults(unused_path)
         assert defaults == dict(token=constants.TOKEN)
 
     def test_get_configured_defaults_empty_file(self, empty_config_mock):
@@ -153,8 +157,8 @@ class TestGetPluginNames:
 class TestExecuteConfigHooks:
     """Tests for execute_config_hooks."""
 
-    def test_with_no_config_file(self, no_config_mock, plugin_manager_mock):
-        config.execute_config_hooks()
+    def test_with_no_config_file(self, unused_path, plugin_manager_mock):
+        config.execute_config_hooks(config_file=unused_path)
         assert not plugin_manager_mock.hook.config_hook.called
 
     def test_with_config_file(self, config_mock, plugin_manager_mock):
@@ -180,13 +184,11 @@ class TestCheckConfigIntegrity:
             + os.linesep.join(["[some_config]", "option = value", "bla = blu"])
         )
 
-    def test_with_no_config_file_raises(self, no_config_mock):
+    def test_with_no_config_file_raises(self, unused_path):
         with pytest.raises(exception.FileError) as exc_info:
-            config.check_config_integrity()
+            config.check_config_integrity(config_file=unused_path)
 
-        assert str(_repobee.constants.DEFAULT_CONFIG_FILE) in str(
-            exc_info.value
-        )
+        assert str(unused_path) in str(exc_info.value)
 
     def test_with_invalid_defaults_key_raises(self, empty_config_mock):
         empty_config_mock.write(
diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py
index cec05bb..4b818ce 100644
--- a/tests/unit_tests/test_main.py
+++ b/tests/unit_tests/test_main.py
@@ -1,5 +1,7 @@
 import argparse
-from unittest.mock import MagicMock
+import tempfile
+import pathlib
+from unittest.mock import MagicMock, patch
 from collections import namedtuple
 
 import pytest
@@ -93,10 +95,16 @@ def test_happy_path(
     main.main(sys_args)
 
     handle_args_mock.assert_called_once_with(
-        sys_args[1:], show_all_opts=False, ext_commands=DEFAULT_EXT_COMMANDS
+        sys_args[1:],
+        show_all_opts=False,
+        ext_commands=DEFAULT_EXT_COMMANDS,
+        config_file=_repobee.constants.DEFAULT_CONFIG_FILE,
     )
     dispatch_command_mock.assert_called_once_with(
-        PARSED_ARGS, api_instance_mock, DEFAULT_EXT_COMMANDS
+        PARSED_ARGS,
+        api_instance_mock,
+        _repobee.constants.DEFAULT_CONFIG_FILE,
+        DEFAULT_EXT_COMMANDS,
     )
 
 
@@ -112,7 +120,10 @@ def test_exit_status_1_on_exception_in_parsing(
 
     assert exc_info.value.code == 1
     handle_args_mock.assert_called_once_with(
-        sys_args[1:], show_all_opts=False, ext_commands=DEFAULT_EXT_COMMANDS
+        sys_args[1:],
+        show_all_opts=False,
+        ext_commands=DEFAULT_EXT_COMMANDS,
+        config_file=_repobee.constants.DEFAULT_CONFIG_FILE,
     )
     assert not dispatch_command_mock.called
 
@@ -130,7 +141,10 @@ def test_exit_status_1_on_exception_in_handling_parsed_args(
 
     assert exc_info.value.code == 1
     handle_args_mock.assert_called_once_with(
-        sys_args[1:], show_all_opts=False, ext_commands=DEFAULT_EXT_COMMANDS
+        sys_args[1:],
+        show_all_opts=False,
+        ext_commands=DEFAULT_EXT_COMMANDS,
+        config_file=_repobee.constants.DEFAULT_CONFIG_FILE,
     )
 
 
@@ -146,7 +160,10 @@ def test_plugins_args(
         ["javac", "pylint", _repobee.constants.DEFAULT_PLUGIN]
     )
     handle_args_mock.assert_called_once_with(
-        CLONE_ARGS, show_all_opts=False, ext_commands=[]
+        CLONE_ARGS,
+        show_all_opts=False,
+        ext_commands=[],
+        config_file=_repobee.constants.DEFAULT_CONFIG_FILE,
     )
 
 
@@ -164,7 +181,10 @@ def test_no_plugins_arg(
         [_repobee.constants.DEFAULT_PLUGIN]
     )
     handle_args_mock.assert_called_once_with(
-        CLONE_ARGS, show_all_opts=False, ext_commands=[]
+        CLONE_ARGS,
+        show_all_opts=False,
+        ext_commands=[],
+        config_file=_repobee.constants.DEFAULT_CONFIG_FILE,
     )
 
 
@@ -182,7 +202,10 @@ def test_no_plugins_with_configured_plugins(
         [_repobee.constants.DEFAULT_PLUGIN]
     )
     handle_args_mock.assert_called_once_with(
-        CLONE_ARGS, show_all_opts=False, ext_commands=[]
+        CLONE_ARGS,
+        show_all_opts=False,
+        ext_commands=[],
+        config_file=_repobee.constants.DEFAULT_CONFIG_FILE,
     )
 
 
@@ -197,7 +220,10 @@ def test_configured_plugins_are_loaded(
         [*constants.PLUGINS, _repobee.constants.DEFAULT_PLUGIN]
     )
     handle_args_mock.assert_called_once_with(
-        CLONE_ARGS, show_all_opts=False, ext_commands=[]
+        CLONE_ARGS,
+        show_all_opts=False,
+        ext_commands=[],
+        config_file=_repobee.constants.DEFAULT_CONFIG_FILE,
     )
 
 
@@ -212,7 +238,10 @@ def test_plugin_with_subparser_name(
         ["javac", "clone", _repobee.constants.DEFAULT_PLUGIN]
     )
     handle_args_mock.assert_called_once_with(
-        CLONE_ARGS, show_all_opts=False, ext_commands=[]
+        CLONE_ARGS,
+        show_all_opts=False,
+        ext_commands=[],
+        config_file=_repobee.constants.DEFAULT_CONFIG_FILE,
     )
 
 
@@ -290,6 +319,7 @@ def test_logs_traceback_on_exception_in_dispatch_if_traceback(
         [*CLONE_ARGS, "--traceback"],
         show_all_opts=False,
         ext_commands=DEFAULT_EXT_COMMANDS,
+        config_file=_repobee.constants.DEFAULT_CONFIG_FILE,
     )
 
 
@@ -302,7 +332,10 @@ def test_show_all_opts_correctly_separated(
         raise SystemExit(msg)
 
     parse_preparser_options_mock.return_value = argparse.Namespace(
-        show_all_opts=True, no_plugins=False, plug=None
+        show_all_opts=True,
+        no_plugins=False,
+        plug=None,
+        config_file=_repobee.constants.DEFAULT_CONFIG_FILE,
     )
     handle_args_mock.side_effect = _raise_sysexit
     sys_args = [
@@ -320,6 +353,7 @@ def test_show_all_opts_correctly_separated(
         [mainparser.SETUP_PARSER, "-h"],
         show_all_opts=True,
         ext_commands=DEFAULT_EXT_COMMANDS,
+        config_file=_repobee.constants.DEFAULT_CONFIG_FILE,
     )
     parse_preparser_options_mock.assert_called_once_with(
         [_repobee.cli.preparser.PRE_PARSER_SHOW_ALL_OPTS]
@@ -340,3 +374,19 @@ def test_non_zero_exit_status_on_exception(
         main.main(sys_args)
 
     assert exc_info.value.code == 1
+
+
+def test_show_config_custom_config():
+    config_text = """
+[DEFAULTS]
+user = some-unlikely-user
+""".strip()
+    with tempfile.NamedTemporaryFile() as tmpfile, patch(
+        "_repobee.command.show_config", autospec=True
+    ) as show_config_mock:
+        config_file = pathlib.Path(tmpfile.name)
+        config_file.write_text(config_text)
+        sys_args = ["repobee", "--config-file", tmpfile.name, "show-config"]
+        main.main(sys_args)
+
+    show_config_mock.assert_called_once_with(config_file)

pip install -e .[TEST]
parse_log_pytest
requirements.txt
pytest>=4.0.0 pytest-cov>=2.6.1 pytest-mock codecov pytest
apt-get update apt-get install -y gcc
3.8
requirements/app.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
codecov==2.1.13
colored==1.4.4
coverage==7.6.1
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.9.0
PyNaCl==1.5.0
pytest==8.3.5
pytest-cov==5.0.0
pytest-mock==3.14.0
python-gitlab==1.15.0
python-json-logger==3.3.0
-e git+https://github.com/repobee/repobee.git@241bcf066d987e3598a631ff7c8772a484006782#egg=repobee
repobee-plug==0.12.0
requests==2.32.3
six==1.17.0
tomli==2.2.1
typing_extensions==4.13.0
urllib3==2.2.3
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=24.2=py38h06a4308_0
  - python=3.8.20=he870216_0
  - readline=8.2=h5eee18b_0
  - setuptools=75.1.0=py38h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.44.0=py38h06a4308_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
      - codecov==2.1.13
      - colored==1.4.4
      - coverage==7.6.1
      - 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.9.0
      - pynacl==1.5.0
      - pytest==8.3.5
      - pytest-cov==5.0.0
      - pytest-mock==3.14.0
      - python-gitlab==1.15.0
      - python-json-logger==3.3.0
      - repobee==2.4.0
      - repobee-plug==0.12.0
      - requests==2.32.3
      - six==1.17.0
      - tomli==2.2.1
      - typing-extensions==4.13.0
      - urllib3==2.2.3
      - wrapt==1.17.2
prefix: /opt/conda/envs/repobee

tests/unit_tests/test_cli.py::TestDispatchCommand::test_writes_hook_results_correctly[clone-_repobee.command.clone_repos] tests/unit_tests/test_cli.py::TestDispatchCommand::test_writes_hook_results_correctly[list-issues-_repobee.command.list_issues] tests/unit_tests/test_cli.py::TestDispatchCommand::test_does_not_write_hook_results_if_there_are_none 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[create-teams] 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[end-reviews] 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_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_create_teams_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::TestExtensionCommands::test_dispatch_ext_command_that_does_not_require_api tests/unit_tests/test_cli.py::TestExtensionCommands::test_dispatch_ext_command_that_requires_api tests/unit_tests/test_main.py::test_happy_path tests/unit_tests/test_main.py::test_exit_status_1_on_exception_in_parsing tests/unit_tests/test_main.py::test_exit_status_1_on_exception_in_handling_parsed_args 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_no_plugins_with_configured_plugins tests/unit_tests/test_main.py::test_configured_plugins_are_loaded 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_main.py::test_show_config_custom_config
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[create-teams] 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[end-reviews] 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::test_create_parser_for_docs 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_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::TestBaseParsing::test_correctly_parses_create_teams tests/unit_tests/test_cli.py::TestExtensionCommands::test_parse_ext_command_that_does_not_require_api tests/unit_tests/test_cli.py::TestExtensionCommands::test_parse_ext_command_that_requires_api tests/unit_tests/test_cli.py::TestExtensionCommands::test_parse_ext_command_that_requires_base_parsers 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-end-reviews-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_command.py::TestSetupStudentRepos::test_raises_on_clone_failure tests/unit_tests/test_command.py::TestSetupStudentRepos::test_raises_on_duplicate_master_urls tests/unit_tests/test_command.py::TestSetupStudentRepos::test_happy_path tests/unit_tests/test_command.py::TestUpdateStudentRepos::test_raises_on_duplicate_master_urls tests/unit_tests/test_command.py::TestUpdateStudentRepos::test_happy_path tests/unit_tests/test_command.py::TestUpdateStudentRepos::test_issues_on_exceptions[issue0] tests/unit_tests/test_command.py::TestUpdateStudentRepos::test_issues_on_exceptions[None] tests/unit_tests/test_command.py::TestUpdateStudentRepos::test_issues_arent_opened_on_exceptions_if_unspeficied tests/unit_tests/test_command.py::TestOpenIssue::test_happy_path tests/unit_tests/test_command.py::TestCloseIssue::test_happy_path tests/unit_tests/test_command.py::TestCloneRepos::test_happy_path tests/unit_tests/test_command.py::TestCloneRepos::test_executes_act_hooks tests/unit_tests/test_command.py::TestCloneRepos::test_executes_clone_tasks tests/unit_tests/test_command.py::TestMigrateRepo::test_happy_path tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-True--IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-True--IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-True--IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-True-^.*$-IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-True-^.*$-IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-True-^.*$-IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-False--IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-False--IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-False--IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-False-^.*$-IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-False-^.*$-IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-False-^.*$-IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-True--IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-True--IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-True--IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-True-^.*$-IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-True-^.*$-IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-True-^.*$-IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-False--IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-False--IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-False--IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-False-^.*$-IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-False-^.*$-IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-False-^.*$-IssueState.ALL] tests/unit_tests/test_command.py::test_purge_review_teams tests/unit_tests/test_command.py::TestAssignPeerReviewers::test_too_few_students_raises[3-3] tests/unit_tests/test_command.py::TestAssignPeerReviewers::test_too_few_students_raises[3-4] tests/unit_tests/test_command.py::TestAssignPeerReviewers::test_zero_reviews_raises tests/unit_tests/test_command.py::TestAssignPeerReviewers::test_happy_path tests/unit_tests/test_command.py::TestCheckPeerReviewProgress::test_happy_path tests/unit_tests/test_command.py::TestSetupTask::test_executes_setup_hooks[setup_student_repos] tests/unit_tests/test_command.py::TestSetupTask::test_executes_setup_hooks[update_student_repos] tests/unit_tests/test_config.py::TestGetConfiguredDefaults::test_get_configured_defaults_no_config_file tests/unit_tests/test_config.py::TestGetConfiguredDefaults::test_get_configured_defaults_empty_file tests/unit_tests/test_config.py::TestGetConfiguredDefaults::test_get_configured_defaults_reads_full_config tests/unit_tests/test_config.py::TestGetConfiguredDefaults::test_token_in_env_variable_overrides_configuration_file tests/unit_tests/test_config.py::TestGetConfiguredDefaults::test_deprecated_token_env tests/unit_tests/test_config.py::TestGetConfiguredDefaults::test_get_configured_defaults_raises_on_invalid_keys tests/unit_tests/test_config.py::TestGetConfiguredDefaults::test_get_configured_defaults_raises_on_missing_header tests/unit_tests/test_config.py::TestGetPluginNames::test_with_full_config tests/unit_tests/test_config.py::TestGetPluginNames::test_with_only_plugins[javac,pylint-expected_plugins0] tests/unit_tests/test_config.py::TestGetPluginNames::test_with_only_plugins[javac, tests/unit_tests/test_config.py::TestGetPluginNames::test_with_only_plugins[javac-expected_plugins2] tests/unit_tests/test_config.py::TestGetPluginNames::test_with_only_plugins[-expected_plugins3] tests/unit_tests/test_config.py::TestExecuteConfigHooks::test_with_no_config_file tests/unit_tests/test_config.py::TestExecuteConfigHooks::test_with_config_file tests/unit_tests/test_config.py::TestCheckConfigIntegrity::test_with_well_formed_config tests/unit_tests/test_config.py::TestCheckConfigIntegrity::test_with_well_formed_plugin_options tests/unit_tests/test_config.py::TestCheckConfigIntegrity::test_with_no_config_file_raises tests/unit_tests/test_config.py::TestCheckConfigIntegrity::test_with_invalid_defaults_key_raises tests/unit_tests/test_config.py::TestCheckConfigIntegrity::test_with_valid_but_malformed_default_args_raises 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 tests/unit_tests/test_main.py::test_non_zero_exit_status_on_exception
src/_repobee/config.py src/_repobee/main.py src/_repobee/command/repos.py src/_repobee/cli/preparser.py src/_repobee/cli/parsing.py src/_repobee/plugin.py src/_repobee/cli/dispatch.py src/_repobee/cli/mainparser.py
src/_repobee/config.py src/_repobee/main.py src/_repobee/command/repos.py src/_repobee/cli/preparser.py src/_repobee/cli/parsing.py src/_repobee/plugin.py src/_repobee/cli/dispatch.py src/_repobee/cli/mainparser.py