Repository: pre-commit/pre-commit

Issue:
TypeError: expected string or bytes-like object
OS: Ubuntu 19.10
App Version: pre-commit 2.1.0

Trying to create a commit-msg hook with the following hook yaml:

```yaml
---
- id: conform
  name: Conform
  description: Run 'conform enforce' for policy enforcement
  entry: conform enforce --commit-msg-file
  language: golang
  stages: [commit-msg]
```

However, when I run the `try-repo` command...

```bash
➜ pre-commit try-repo --hook-stage commit-msg .
[WARNING] Creating temporary repo with uncommitted changes...
[INFO] Initializing environment for /tmp/tmpinmhjsj2/shadow-repo.
===============================================================================
Using config:
===============================================================================
repos:
-   repo: /tmp/tmpinmhjsj2/shadow-repo
    rev: a4536d3f16eeddbe6faa39a19b3857b77967bd03
    hooks:
    -   id: conform
===============================================================================
[INFO] Installing environment for /tmp/tmpinmhjsj2/shadow-repo.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
An unexpected error has occurred: TypeError: expected string or bytes-like object
Check the log at /home/joelinux/.cache/pre-commit/pre-commit.log
```

The `pre-commit.log` file shows me this traceback:

```bash
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/error_handler.py", line 54, in error_handler
    yield
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/main.py", line 375, in main
    return try_repo(args)
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/commands/try_repo.py", line 77, in try_repo
    return run(config_filename, store, args)
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/commands/run.py", line 339, in run
    return _run_hooks(config, hooks, args, environ)
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/commands/run.py", line 242, in _run_hooks
    _all_filenames(args), config['files'], config['exclude'],
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/commands/run.py", line 62, in filter_by_include_exclude
    filename for filename in names
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/commands/run.py", line 63, in <listcomp>
    if include_re.search(filename)
TypeError: expected string or bytes-like object
```

Looking through the code it looks like it's expecting to run the `filter_by_include_exclude()` method and it attempts to iterate over `names` which has a value of `(None,)`. Not sure if this is expected behavior but I wouldn't expect a commit-msg hook to filter any files since the only thing it should get is the filename of the commit message?
TypeError: expected string or bytes-like object
OS: Ubuntu 19.10
App Version: pre-commit 2.1.0

Trying to create a commit-msg hook with the following hook yaml:

```yaml
---
- id: conform
  name: Conform
  description: Run 'conform enforce' for policy enforcement
  entry: conform enforce --commit-msg-file
  language: golang
  stages: [commit-msg]
```

However, when I run the `try-repo` command...

```bash
➜ pre-commit try-repo --hook-stage commit-msg .
[WARNING] Creating temporary repo with uncommitted changes...
[INFO] Initializing environment for /tmp/tmpinmhjsj2/shadow-repo.
===============================================================================
Using config:
===============================================================================
repos:
-   repo: /tmp/tmpinmhjsj2/shadow-repo
    rev: a4536d3f16eeddbe6faa39a19b3857b77967bd03
    hooks:
    -   id: conform
===============================================================================
[INFO] Installing environment for /tmp/tmpinmhjsj2/shadow-repo.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
An unexpected error has occurred: TypeError: expected string or bytes-like object
Check the log at /home/joelinux/.cache/pre-commit/pre-commit.log
```

The `pre-commit.log` file shows me this traceback:

```bash
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/error_handler.py", line 54, in error_handler
    yield
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/main.py", line 375, in main
    return try_repo(args)
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/commands/try_repo.py", line 77, in try_repo
    return run(config_filename, store, args)
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/commands/run.py", line 339, in run
    return _run_hooks(config, hooks, args, environ)
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/commands/run.py", line 242, in _run_hooks
    _all_filenames(args), config['files'], config['exclude'],
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/commands/run.py", line 62, in filter_by_include_exclude
    filename for filename in names
  File "/usr/local/lib/python3.7/dist-packages/pre_commit/commands/run.py", line 63, in <listcomp>
    if include_re.search(filename)
TypeError: expected string or bytes-like object
```

Looking through the code it looks like it's expecting to run the `filter_by_include_exclude()` method and it attempts to iterate over `names` which has a value of `(None,)`. Not sure if this is expected behavior but I wouldn't expect a commit-msg hook to filter any files since the only thing it should get is the filename of the commit message?


Discussion:
for `try-repo --hook-stage commit-msg` you need to pass `--commit-msg-filename ...`

the error message can probably be improved here

please in the future include the full contents of `pre-commit.log`, the entire output is very important here
would you like to work on improving the error message?
Is that required flag in a doc somewhere? If not, I can submit a PR for it.
@asottile also I'm not so sure it's an error message thing as much as it is an error. It's failing to find files, not failing intentionally, so while I'm not sure I'm familiar enough here to get the logic right for differentiating this specific failure scenario versus others (in different hook types, for instance), I can certainly take a crack at it.
it's a user error and probably not documented beyond `--help` -- the docs live at https://github.com/pre-commit/pre-commit.github.io if you'd like to improve them

as for the code change here, it probably needs a check similar to this one [here](https://github.com/pre-commit/pre-commit/blob/1c641b1c28ecc1005f46fdc76db4bbb0f67c82ac/pre_commit/commands/run.py#L297-L308) -- notably it needs to check that if `--hook-type` is `commit-msg` or `prepare-commit-msg` that `args.commit_msg_filename` is present and otherwise produce an error indicating that
for `try-repo --hook-stage commit-msg` you need to pass `--commit-msg-filename ...`

the error message can probably be improved here

please in the future include the full contents of `pre-commit.log`, the entire output is very important here
would you like to work on improving the error message?
Is that required flag in a doc somewhere? If not, I can submit a PR for it.
@asottile also I'm not so sure it's an error message thing as much as it is an error. It's failing to find files, not failing intentionally, so while I'm not sure I'm familiar enough here to get the logic right for differentiating this specific failure scenario versus others (in different hook types, for instance), I can certainly take a crack at it.
it's a user error and probably not documented beyond `--help` -- the docs live at https://github.com/pre-commit/pre-commit.github.io if you'd like to improve them

as for the code change here, it probably needs a check similar to this one [here](https://github.com/pre-commit/pre-commit/blob/1c641b1c28ecc1005f46fdc76db4bbb0f67c82ac/pre_commit/commands/run.py#L297-L308) -- notably it needs to check that if `--hook-type` is `commit-msg` or `prepare-commit-msg` that `args.commit_msg_filename` is present and otherwise produce an error indicating that

Resolving patch:
diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -306,6 +306,15 @@ def run(
             f'`git add {config_file}` to fix this.',
         )
         return 1
+    if (
+            args.hook_stage in {'prepare-commit-msg', 'commit-msg'} and
+            not args.commit_msg_filename
+    ):
+        logger.error(
+            f'`--commit-msg-filename` is required for '
+            f'`--hook-stage {args.hook_stage}`',
+        )
+        return 1
 
     # Expose origin / source as environment variables for hooks to consume
     if args.origin and args.source:


Test patch:
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -663,12 +663,7 @@ def test_stages(cap_out, store, repo_with_passing_hook):
                 'language': 'pygrep',
                 'stages': [stage],
             }
-            for i, stage in enumerate(
-                (
-                    'commit', 'push', 'manual', 'prepare-commit-msg',
-                    'commit-msg',
-                ), 1,
-            )
+            for i, stage in enumerate(('commit', 'push', 'manual'), 1)
         ],
     }
     add_config_to_repo(repo_with_passing_hook, config)
@@ -686,8 +681,6 @@ def _run_for_stage(stage):
     assert _run_for_stage('commit').startswith(b'hook 1...')
     assert _run_for_stage('push').startswith(b'hook 2...')
     assert _run_for_stage('manual').startswith(b'hook 3...')
-    assert _run_for_stage('prepare-commit-msg').startswith(b'hook 4...')
-    assert _run_for_stage('commit-msg').startswith(b'hook 5...')
 
 
 def test_commit_msg_hook(cap_out, store, commit_msg_repo):
@@ -819,6 +812,16 @@ def test_error_with_unstaged_config(cap_out, store, modified_config_repo):
     assert ret == 1
 
 
+def test_commit_msg_missing_filename(cap_out, store, repo_with_passing_hook):
+    args = run_opts(hook_stage='commit-msg')
+    ret, printed = _do_run(cap_out, store, repo_with_passing_hook, args)
+    assert ret == 1
+    assert printed == (
+        b'[ERROR] `--commit-msg-filename` is required for '
+        b'`--hook-stage commit-msg`\n'
+    )
+
+
 @pytest.mark.parametrize(
     'opts', (run_opts(all_files=True), run_opts(files=[C.CONFIG_FILE])),
 )