nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: pre-commit/pre-commit
Pull request #1079 (2019-07-09 18:09:58)

Added hook-stage print to output for missing hook id
Adds the value of `hook-stage` to the error message for a missing hook id to suggest setting a different stage. 

Possible additions before merging:
- Add a prompt to set the hook-stage
- Add a search for the id in different stages to say `Did you mean to use --hook-stage bleh`
- Add tests for more than `commit` and `push`


Patch:
diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index 33c0f10..4087a65 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -285,7 +285,11 @@ def run(config_file, store, args, environ=os.environ):
         ]
 
         if args.hook and not hooks:
-            output.write_line('No hook with id `{}`'.format(args.hook))
+            output.write_line(
+                'No hook with id `{}` in stage `{}`'.format(
+                    args.hook, args.hook_stage,
+                ),
+            )
             return 1
 
         install_hook_envs(hooks, store)


Test patch:
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index fc2a973..94d44e1 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -231,7 +231,18 @@ def test_show_diff_on_failure(
         ({}, (b'Bash hook', b'Passed'), 0, True),
         ({'verbose': True}, (b'foo.py\nHello World',), 0, True),
         ({'hook': 'bash_hook'}, (b'Bash hook', b'Passed'), 0, True),
-        ({'hook': 'nope'}, (b'No hook with id `nope`',), 1, True),
+        (
+            {'hook': 'nope'},
+            (b'No hook with id `nope` in stage `commit`',),
+            1,
+            True,
+        ),
+        (
+            {'hook': 'nope', 'hook_stage': 'push'},
+            (b'No hook with id `nope` in stage `push`',),
+            1,
+            True,
+        ),
         (
             {'all_files': True, 'verbose': True},
             (b'foo.py',),
Repository: pre-commit/pre-commit
Pull request #1089 (2019-07-20 21:53:59)

Make autoupdate work for non-master default branches


Patch:
diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py
index 9701e93..fdada18 100644
--- a/pre_commit/commands/autoupdate.py
+++ b/pre_commit/commands/autoupdate.py
@@ -38,9 +38,9 @@ def _update_repo(repo_config, store, tags_only):
     """
     with tmpdir() as repo_path:
         git.init_repo(repo_path, repo_config['repo'])
-        cmd_output('git', 'fetch', cwd=repo_path)
+        cmd_output('git', 'fetch', 'origin', 'HEAD', '--tags', cwd=repo_path)
 
-        tag_cmd = ('git', 'describe', 'origin/master', '--tags')
+        tag_cmd = ('git', 'describe', 'FETCH_HEAD', '--tags')
         if tags_only:
             tag_cmd += ('--abbrev=0',)
         else:
@@ -48,7 +48,7 @@ def _update_repo(repo_config, store, tags_only):
         try:
             rev = cmd_output(*tag_cmd, cwd=repo_path)[1].strip()
         except CalledProcessError:
-            tag_cmd = ('git', 'rev-parse', 'origin/master')
+            tag_cmd = ('git', 'rev-parse', 'FETCH_HEAD')
             rev = cmd_output(*tag_cmd, cwd=repo_path)[1].strip()
 
     # Don't bother trying to update if our rev is the same


Test patch:
diff --git a/tests/commands/autoupdate_test.py b/tests/commands/autoupdate_test.py
index c1fceb4..ead0efe 100644
--- a/tests/commands/autoupdate_test.py
+++ b/tests/commands/autoupdate_test.py
@@ -309,6 +309,12 @@ def test_autoupdate_hook_disappearing_repo(
     assert before == after
 
 
+def test_autoupdate_non_master_default_branch(up_to_date_repo, store):
+    # change the default branch to be not-master
+    cmd_output('git', '-C', up_to_date_repo, 'branch', '-m', 'dev')
+    test_up_to_date_repo(up_to_date_repo, store)
+
+
 def test_autoupdate_local_hooks(in_git_dir, store):
     config = sample_local_config()
     add_config_to_repo('.', config)
Repository: pre-commit/pre-commit
Pull request #1103 (2019-07-27 20:47:25)

Disable color if TERM=dumb is detected
Relates to #1073 


Patch:
diff --git a/pre_commit/color.py b/pre_commit/color.py
index c785e2c..1fb6acc 100644
--- a/pre_commit/color.py
+++ b/pre_commit/color.py
@@ -49,6 +49,10 @@ def use_color(setting):
         raise InvalidColorSetting(setting)
 
     return (
-        setting == 'always' or
-        (setting == 'auto' and sys.stdout.isatty() and terminal_supports_color)
+        setting == 'always' or (
+            setting == 'auto' and
+            sys.stdout.isatty() and
+            terminal_supports_color and
+            os.getenv('TERM') != 'dumb'
+        )
     )


Test patch:
diff --git a/tests/color_test.py b/tests/color_test.py
index 6e11765..6c9889d 100644
--- a/tests/color_test.py
+++ b/tests/color_test.py
@@ -5,6 +5,7 @@ import sys
 import mock
 import pytest
 
+from pre_commit import envcontext
 from pre_commit.color import format_color
 from pre_commit.color import GREEN
 from pre_commit.color import InvalidColorSetting
@@ -38,13 +39,22 @@ def test_use_color_no_tty():
 def test_use_color_tty_with_color_support():
     with mock.patch.object(sys.stdout, 'isatty', return_value=True):
         with mock.patch('pre_commit.color.terminal_supports_color', True):
-            assert use_color('auto') is True
+            with envcontext.envcontext([('TERM', envcontext.UNSET)]):
+                assert use_color('auto') is True
 
 
 def test_use_color_tty_without_color_support():
     with mock.patch.object(sys.stdout, 'isatty', return_value=True):
         with mock.patch('pre_commit.color.terminal_supports_color', False):
-            assert use_color('auto') is False
+            with envcontext.envcontext([('TERM', envcontext.UNSET)]):
+                assert use_color('auto') is False
+
+
+def test_use_color_dumb_term():
+    with mock.patch.object(sys.stdout, 'isatty', return_value=True):
+        with mock.patch('pre_commit.color.terminal_supports_color', True):
+            with envcontext.envcontext([('TERM', 'dumb')]):
+                assert use_color('auto') is False
 
 
 def test_use_color_raises_if_given_shenanigans():
Repository: pre-commit/pre-commit
Pull request #1153 (2019-09-30 17:30:59)

Fix hook_types when calling init-templatedir
accidentally regressed in #1145 


Patch:
diff --git a/pre_commit/main.py b/pre_commit/main.py
index 8d2d630..59de5f2 100644
--- a/pre_commit/main.py
+++ b/pre_commit/main.py
@@ -55,12 +55,25 @@ def _add_config_option(parser):
     )
 
 
+class AppendReplaceDefault(argparse.Action):
+    def __init__(self, *args, **kwargs):
+        super(AppendReplaceDefault, self).__init__(*args, **kwargs)
+        self.appended = False
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        if not self.appended:
+            setattr(namespace, self.dest, [])
+            self.appended = True
+        getattr(namespace, self.dest).append(values)
+
+
 def _add_hook_type_option(parser):
     parser.add_argument(
         '-t', '--hook-type', choices=(
             'pre-commit', 'pre-push', 'prepare-commit-msg', 'commit-msg',
         ),
-        action='append',
+        action=AppendReplaceDefault,
+        default=['pre-commit'],
         dest='hook_types',
     )
 
@@ -121,11 +134,6 @@ def _adjust_args_and_chdir(args):
         args.files = [os.path.relpath(filename) for filename in args.files]
     if args.command == 'try-repo' and os.path.exists(args.repo):
         args.repo = os.path.relpath(args.repo)
-    if (
-            args.command in {'install', 'uninstall', 'init-templatedir'} and
-            not args.hook_types
-    ):
-        args.hook_types = ['pre-commit']
 
 
 def main(argv=None):


Test patch:
diff --git a/tests/main_test.py b/tests/main_test.py
index aad9c4b..364e0d3 100644
--- a/tests/main_test.py
+++ b/tests/main_test.py
@@ -13,6 +13,20 @@ from pre_commit.error_handler import FatalError
 from testing.auto_namedtuple import auto_namedtuple
 
 
+@pytest.mark.parametrize(
+    ('argv', 'expected'),
+    (
+        ((), ['f']),
+        (('--f', 'x'), ['x']),
+        (('--f', 'x', '--f', 'y'), ['x', 'y']),
+    ),
+)
+def test_append_replace_default(argv, expected):
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--f', action=main.AppendReplaceDefault, default=['f'])
+    assert parser.parse_args(argv).f == expected
+
+
 class Args(object):
     def __init__(self, **kwargs):
         kwargs.setdefault('command', 'help')
Repository: pre-commit/pre-commit
Pull request #1161 (2019-10-07 13:30:19)

Handle case when executable is not executable
- Changed error message if executable is not executable

Closes:[1159](https://github.com/pre-commit/pre-commit/issues/1159)


Patch:
diff --git a/pre_commit/parse_shebang.py b/pre_commit/parse_shebang.py
index 5a2ba72..ab2c9ee 100644
--- a/pre_commit/parse_shebang.py
+++ b/pre_commit/parse_shebang.py
@@ -51,10 +51,12 @@ def normexe(orig):
         if exe is None:
             _error('not found')
         return exe
-    elif not os.access(orig, os.X_OK):
-        _error('not found')
     elif os.path.isdir(orig):
         _error('is a directory')
+    elif not os.path.isfile(orig):
+        _error('not found')
+    elif not os.access(orig, os.X_OK):  # pragma: windows no cover
+        _error('is not executable')
     else:
         return orig
 


Test patch:
diff --git a/tests/parse_shebang_test.py b/tests/parse_shebang_test.py
index 400a287..5895332 100644
--- a/tests/parse_shebang_test.py
+++ b/tests/parse_shebang_test.py
@@ -91,6 +91,14 @@ def test_normexe_does_not_exist_sep():
     assert excinfo.value.args == ('Executable `./i-dont-exist-lol` not found',)
 
 
+@pytest.mark.xfail(os.name == 'nt', reason='posix only',)
+def test_normexe_not_executable(tmpdir):  # pragma: windows no cover
+    tmpdir.join('exe').ensure()
+    with tmpdir.as_cwd(), pytest.raises(OSError) as excinfo:
+        parse_shebang.normexe('./exe')
+    assert excinfo.value.args == ('Executable `./exe` is not executable',)
+
+
 def test_normexe_is_a_directory(tmpdir):
     with tmpdir.as_cwd():
         tmpdir.join('exe').ensure_dir()
Repository: pre-commit/pre-commit
Pull request #1185 (2019-10-22 20:29:01)

Fix python.healthy() check with stdlib module clashes
locally someone had `types/__init__.py` which caused the `healthy()` check to continually fail and recreate. this at least pushes the problem further down into hooks where an error message will give something googleable


Patch:
diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py
index c9bedb6..6eecc0c 100644
--- a/pre_commit/languages/python.py
+++ b/pre_commit/languages/python.py
@@ -147,6 +147,7 @@ def py_interface(_dir, _make_venv):
             retcode, _, _ = cmd_output_b(
                 'python', '-c',
                 'import ctypes, datetime, io, os, ssl, weakref',
+                cwd='/',
                 retcode=None,
             )
         return retcode == 0


Test patch:
diff --git a/tests/languages/python_test.py b/tests/languages/python_test.py
index d9d8ecd..7daff1d 100644
--- a/tests/languages/python_test.py
+++ b/tests/languages/python_test.py
@@ -7,7 +7,9 @@ import sys
 import mock
 import pytest
 
+import pre_commit.constants as C
 from pre_commit.languages import python
+from pre_commit.prefix import Prefix
 
 
 def test_norm_version_expanduser():
@@ -48,3 +50,11 @@ def test_find_by_sys_executable(exe, realpath, expected):
         with mock.patch.object(os.path, 'realpath', return_value=realpath):
             with mock.patch.object(python, 'find_executable', lambda x: x):
                 assert python._find_by_sys_executable() == expected
+
+
+def test_healthy_types_py_in_cwd(tmpdir):
+    with tmpdir.as_cwd():
+        # even if a `types.py` file exists, should still be healthy
+        tmpdir.join('types.py').ensure()
+        # this env doesn't actually exist (for test speed purposes)
+        assert python.healthy(Prefix(str(tmpdir)), C.DEFAULT) is True
Repository: pre-commit/pre-commit
Pull request #1197 (2019-10-28 22:24:39)

Bump the version of pre-commit-hooks in sample-config


Patch:
diff --git a/pre_commit/commands/sample_config.py b/pre_commit/commands/sample_config.py
index 38320f6..a35ef8e 100644
--- a/pre_commit/commands/sample_config.py
+++ b/pre_commit/commands/sample_config.py
@@ -12,7 +12,7 @@ SAMPLE_CONFIG = '''\
 # See https://pre-commit.com/hooks.html for more hooks
 repos:
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v2.0.0
+    rev: v2.4.0
     hooks:
     -   id: trailing-whitespace
     -   id: end-of-file-fixer


Test patch:
diff --git a/tests/commands/sample_config_test.py b/tests/commands/sample_config_test.py
index 83942a4..57ef3a4 100644
--- a/tests/commands/sample_config_test.py
+++ b/tests/commands/sample_config_test.py
@@ -13,7 +13,7 @@ def test_sample_config(capsys):
 # See https://pre-commit.com/hooks.html for more hooks
 repos:
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v2.0.0
+    rev: v2.4.0
     hooks:
     -   id: trailing-whitespace
     -   id: end-of-file-fixer
Repository: pre-commit/pre-commit
Pull request #1263 (2020-01-02 04:22:21)

clear 'frozen: ...' comment if autoupdate unfreezes


Patch:
diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py
index 5e804c1..05187b8 100644
--- a/pre_commit/commands/autoupdate.py
+++ b/pre_commit/commands/autoupdate.py
@@ -108,7 +108,9 @@ def _write_new_config(path, rev_infos):
         new_rev_s = ordered_dump({'rev': rev_info.rev}, **C.YAML_DUMP_KWARGS)
         new_rev = new_rev_s.split(':', 1)[1].strip()
         if rev_info.frozen is not None:
-            comment = '  # {}'.format(rev_info.frozen)
+            comment = '  # frozen: {}'.format(rev_info.frozen)
+        elif match.group(4).strip().startswith('# frozen:'):
+            comment = ''
         else:
             comment = match.group(4)
         lines[idx] = REV_LINE_FMT.format(


Test patch:
diff --git a/tests/commands/autoupdate_test.py b/tests/commands/autoupdate_test.py
index 9a72558..f8ea084 100644
--- a/tests/commands/autoupdate_test.py
+++ b/tests/commands/autoupdate_test.py
@@ -312,9 +312,14 @@ def test_autoupdate_freeze(tagged, in_tmpdir, store):
 
     assert autoupdate(C.CONFIG_FILE, store, freeze=True, tags_only=False) == 0
     with open(C.CONFIG_FILE) as f:
-        expected = 'rev: {}  # v1.2.3'.format(tagged.head_rev)
+        expected = 'rev: {}  # frozen: v1.2.3'.format(tagged.head_rev)
         assert expected in f.read()
 
+    # if we un-freeze it should remove the frozen comment
+    assert autoupdate(C.CONFIG_FILE, store, freeze=False, tags_only=False) == 0
+    with open(C.CONFIG_FILE) as f:
+        assert 'rev: v1.2.3\n' in f.read()
+
 
 def test_autoupdate_tags_only(tagged, in_tmpdir, store):
     # add some commits after the tag
Repository: pre-commit/pre-commit
Pull request #1378 (2020-03-27 09:40:34)

fix CJK characters width in output

sophgn: ![5a03bc20f247c58c](https://user-images.githubusercontent.com/13099836/77745778-df0eaa00-7056-11ea-97b4-55392c5a03e9.jpg)
before and after

Patch:
diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index 2f74578..8c8401c 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -6,6 +6,7 @@ import os
 import re
 import subprocess
 import time
+import unicodedata
 from typing import Any
 from typing import Collection
 from typing import Dict
@@ -33,8 +34,13 @@ from pre_commit.util import EnvironT
 logger = logging.getLogger('pre_commit')
 
 
+def _len_cjk(msg: str) -> int:
+    widths = {'A': 1, 'F': 2, 'H': 1, 'N': 1, 'Na': 1, 'W': 2}
+    return sum(widths[unicodedata.east_asian_width(c)] for c in msg)
+
+
 def _start_msg(*, start: str, cols: int, end_len: int) -> str:
-    dots = '.' * (cols - len(start) - end_len - 1)
+    dots = '.' * (cols - _len_cjk(start) - end_len - 1)
     return f'{start}{dots}'
 
 
@@ -47,7 +53,7 @@ def _full_msg(
         use_color: bool,
         postfix: str = '',
 ) -> str:
-    dots = '.' * (cols - len(start) - len(postfix) - len(end_msg) - 1)
+    dots = '.' * (cols - _len_cjk(start) - len(postfix) - len(end_msg) - 1)
     end = color.format_color(end_msg, end_color, use_color)
     return f'{start}{dots}{postfix}{end}\n'
 
@@ -206,7 +212,7 @@ def _compute_cols(hooks: Sequence[Hook]) -> int:
         Hook name...(no files to check) Skipped
     """
     if hooks:
-        name_len = max(len(hook.name) for hook in hooks)
+        name_len = max(_len_cjk(hook.name) for hook in hooks)
     else:
         name_len = 0
 


Test patch:
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index f8e8823..c51bcff 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -52,6 +52,18 @@ def test_full_msg():
     assert ret == 'start......end\n'
 
 
+def test_full_msg_with_cjk():
+    ret = _full_msg(
+        start='啊あ아',
+        end_msg='end',
+        end_color='',
+        use_color=False,
+        cols=15,
+    )
+    # 5 dots: 15 - 6 - 3 - 1
+    assert ret == '啊あ아.....end\n'
+
+
 def test_full_msg_with_color():
     ret = _full_msg(
         start='start',
Repository: pre-commit/pre-commit
Pull request #1402 (2020-04-18 16:36:21)

Preserve line ending when running autoupdate
This PR aims to make pre-commit's `autoupdate` option preserve the line endings.

This is slight inconvenience when using windows but having all code use Unix type line endings. 


Patch:
diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py
index 5a9a988..8c9fdd7 100644
--- a/pre_commit/commands/autoupdate.py
+++ b/pre_commit/commands/autoupdate.py
@@ -93,7 +93,7 @@ def _original_lines(
         retry: bool = False,
 ) -> Tuple[List[str], List[int]]:
     """detect `rev:` lines or reformat the file"""
-    with open(path) as f:
+    with open(path, newline='') as f:
         original = f.read()
 
     lines = original.splitlines(True)
@@ -126,7 +126,7 @@ def _write_new_config(path: str, rev_infos: List[Optional[RevInfo]]) -> None:
             comment = match[4]
         lines[idx] = f'{match[1]}rev:{match[2]}{new_rev}{comment}{match[5]}'
 
-    with open(path, 'w') as f:
+    with open(path, 'w', newline='') as f:
         f.write(''.join(lines))
 
 


Test patch:
diff --git a/tests/commands/autoupdate_test.py b/tests/commands/autoupdate_test.py
index 2c7b2f1..25161d1 100644
--- a/tests/commands/autoupdate_test.py
+++ b/tests/commands/autoupdate_test.py
@@ -263,6 +263,45 @@ def test_does_not_reformat(tmpdir, out_of_date, store):
     assert cfg.read() == expected
 
 
+def test_does_not_change_mixed_endlines_read(up_to_date, tmpdir, store):
+    fmt = (
+        'repos:\n'
+        '-   repo: {}\n'
+        '    rev: {}  # definitely the version I want!\r\n'
+        '    hooks:\r\n'
+        '    -   id: foo\n'
+        '        # These args are because reasons!\r\n'
+        '        args: [foo, bar, baz]\r\n'
+    )
+    cfg = tmpdir.join(C.CONFIG_FILE)
+
+    expected = fmt.format(up_to_date, git.head_rev(up_to_date)).encode()
+    cfg.write_binary(expected)
+
+    assert autoupdate(str(cfg), store, freeze=False, tags_only=False) == 0
+    assert cfg.read_binary() == expected
+
+
+def test_does_not_change_mixed_endlines_write(tmpdir, out_of_date, store):
+    fmt = (
+        'repos:\n'
+        '-   repo: {}\n'
+        '    rev: {}  # definitely the version I want!\r\n'
+        '    hooks:\r\n'
+        '    -   id: foo\n'
+        '        # These args are because reasons!\r\n'
+        '        args: [foo, bar, baz]\r\n'
+    )
+    cfg = tmpdir.join(C.CONFIG_FILE)
+    cfg.write_binary(
+        fmt.format(out_of_date.path, out_of_date.original_rev).encode(),
+    )
+
+    assert autoupdate(str(cfg), store, freeze=False, tags_only=False) == 0
+    expected = fmt.format(out_of_date.path, out_of_date.head_rev).encode()
+    assert cfg.read_binary() == expected
+
+
 def test_loses_formatting_when_not_detectable(out_of_date, store, tmpdir):
     """A best-effort attempt is made at updating rev without rewriting
     formatting.  When the original formatting cannot be detected, this
Repository: pre-commit/pre-commit
Pull request #1467 (2020-05-24 01:14:21)

Expose an env variable that indicates if pre-commit is running
Resolves  #1426


Patch:
diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index c2dab6f..f6916c2 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -347,6 +347,9 @@ def run(
     if args.checkout_type:
         environ['PRE_COMMIT_CHECKOUT_TYPE'] = args.checkout_type
 
+    # Set pre_commit flag
+    environ['PRE_COMMIT'] = '1'
+
     with contextlib.ExitStack() as exit_stack:
         if stash:
             exit_stack.enter_context(staged_files_only(store.directory))


Test patch:
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index 2fffdb9..cf9794e 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -1028,3 +1028,12 @@ def test_skipped_without_any_setup_for_post_checkout(in_git_dir, store):
     environ = {'_PRE_COMMIT_SKIP_POST_CHECKOUT': '1'}
     opts = run_opts(hook_stage='post-checkout')
     assert run(C.CONFIG_FILE, store, opts, environ=environ) == 0
+
+
+def test_pre_commit_env_variable_set(cap_out, store, repo_with_passing_hook):
+    args = run_opts()
+    environ: EnvironT = {}
+    ret, printed = _do_run(
+        cap_out, store, repo_with_passing_hook, args, environ,
+    )
+    assert environ['PRE_COMMIT'] == '1'
Repository: pre-commit/pre-commit
Pull request #1595 (2020-09-09 07:46:49)

Drop python.exe extension on windows in shebang line to fix pyenv-win setup
See issue #1583 for discussion. This PR right now is just to trigger automatic testing on the pipeline and check results.


Patch:
diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py
index 85fa53c..684b598 100644
--- a/pre_commit/commands/install_uninstall.py
+++ b/pre_commit/commands/install_uninstall.py
@@ -55,7 +55,7 @@ def is_our_script(filename: str) -> bool:
 
 def shebang() -> str:
     if sys.platform == 'win32':
-        py = SYS_EXE
+        py, _ = os.path.splitext(SYS_EXE)
     else:
         exe_choices = [
             f'python{sys.version_info[0]}.{sys.version_info[1]}',


Test patch:
diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py
index 481a727..7a4b906 100644
--- a/tests/commands/install_uninstall_test.py
+++ b/tests/commands/install_uninstall_test.py
@@ -56,8 +56,13 @@ def patch_sys_exe(exe):
 
 
 def test_shebang_windows():
+    with patch_platform('win32'), patch_sys_exe('python'):
+        assert shebang() == '#!/usr/bin/env python'
+
+
+def test_shebang_windows_drop_ext():
     with patch_platform('win32'), patch_sys_exe('python.exe'):
-        assert shebang() == '#!/usr/bin/env python.exe'
+        assert shebang() == '#!/usr/bin/env python'
 
 
 def test_shebang_posix_not_on_path():