Repository: pre-commit/pre-commit

Issue:
Remove / deprecate expected_return_code
Since we rely heavily on xargs, this feature doesn't really work as expected.



Discussion:
Damn, that was useful.
To revert the logic of a `grep` for  example.
I don't have a specific use case though, so I won't miss it personally.

Yeah it was actually in the version-negative-one of the project, but is horribly broken for a nontrivial number of files: https://github.com/pre-commit/pre-commit/commit/7bb7f4a4838dbd5d52873187cc9b1b51b83cf0ba

I also ran into this while implementing pcre hooks, and you can see the "correct" way here (pcre are basically just special system hooks): https://github.com/pre-commit/pre-commit/blob/2ec7a34035e3072b2a4d118131fbf3b64c12b2f6/pre_commit/languages/pcre.py#L19-L23

You can see the test specifically tailored to this "bug" for pcre hooks here: https://github.com/pre-commit/pre-commit/blob/2ec7a34035e3072b2a4d118131fbf3b64c12b2f6/tests/repository_test.py#L157-L173

The problem is with the way that xargs works (we'll use -n 1 to simplify the problem, but the same occurs with generalized larger n):

``` sh
# test.sh
# Trivial shell file which executes whatever command is given to it
$1
```

```
$ ./test.sh true; echo $?
0
$ ./test.sh false; echo $?
1
$ echo true false | xargs -n 1 ./test.sh; echo $?
123
$ echo false false | xargs -n 1 ./test.sh; echo $?
123
```

Consider `false` to be like "grep failed to match", it's impossible to distinguish between "grep failing to match" in not all the processes vs "grep failing to match" in all of the processes.

I could reimplement xargs inside of pre-commit, but I don't want to do that and I don't think I'd get it right in all of the edge cases.


Resolving patch:
diff --git a/pre_commit/clientlib/validate_manifest.py b/pre_commit/clientlib/validate_manifest.py
--- a/pre_commit/clientlib/validate_manifest.py
+++ b/pre_commit/clientlib/validate_manifest.py
@@ -24,7 +24,6 @@ class InvalidManifestError(ValueError):
             'language': {'type': 'string'},
             'language_version': {'type': 'string', 'default': 'default'},
             'files': {'type': 'string'},
-            'expected_return_value': {'type': 'number', 'default': 0},
             'stages': {
                 'type': 'array',
                 'default': [],
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
@@ -87,7 +87,7 @@ def _run_single_hook(hook, repo, args, write, skips=frozenset()):
 
     retcode, stdout, stderr = repo.run_hook(hook, filenames)
 
-    if retcode != hook['expected_return_value']:
+    if retcode:
         retcode = 1
         print_color = color.RED
         pass_fail = 'Failed'


Test patch:
diff --git a/tests/clientlib/validate_manifest_test.py b/tests/clientlib/validate_manifest_test.py
--- a/tests/clientlib/validate_manifest_test.py
+++ b/tests/clientlib/validate_manifest_test.py
@@ -77,7 +77,6 @@ def test_additional_manifest_failing(obj):
                 'language': 'python',
                 'language_version': 'python3.3',
                 'files': r'\.py$',
-                'expected_return_value': 0,
             }],
             True,
         ),
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
@@ -476,8 +476,7 @@ def test_local_hook_fails(
         ('hooks', [OrderedDict((
             ('id', 'no-todo'),
             ('name', 'No TODO'),
-            ('entry', 'grep -iI todo'),
-            ('expected_return_value', 1),
+            ('entry', 'sh -c "! grep -iI todo $@" --'),
             ('language', 'system'),
             ('files', ''),
         ))])
diff --git a/tests/manifest_test.py b/tests/manifest_test.py
--- a/tests/manifest_test.py
+++ b/tests/manifest_test.py
@@ -23,7 +23,6 @@ def test_manifest_contents(manifest):
         'description': '',
         'entry': 'bin/hook.sh',
         'exclude': '^$',
-        'expected_return_value': 0,
         'files': '',
         'id': 'bash_hook',
         'language': 'script',
@@ -39,7 +38,6 @@ def test_hooks(manifest):
         'description': '',
         'entry': 'bin/hook.sh',
         'exclude': '^$',
-        'expected_return_value': 0,
         'files': '',
         'id': 'bash_hook',
         'language': 'script',