prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Unicode error: python 2 + merge conflict + non-ascii commit message
  The important part of the stack:
  
  ```
    File "...python2.7/site-packages/pre_commit/commands/run.py", line 52, in get_filenames
      return getter(include_expr, exclude_expr)
    File "...python2.7/site-packages/pre_commit/util.py", line 46, in wrapper
      ret = wrapper._cache[key] = func(*args)
    File "...python2.7/site-packages/pre_commit/git.py", line 98, in wrapper
      for filename in all_file_list_strategy()
    File "...python2.7/site-packages/pre_commit/util.py", line 46, in wrapper
      ret = wrapper._cache[key] = func(*args)
    File "...python2.7/site-packages/pre_commit/git.py", line 64, in get_conflicted_files
      merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)
    File "...python2.7/site-packages/pre_commit/git.py", line 54, in parse_merge_msg_for_conflicts
      if line.startswith(('\t', '#\t'))
  UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 37: ordinal not in range(128)
  ```
  
  An easy fix: https://github.com/pre-commit/pre-commit/blob/e3b14c35f782ed464e3f96b44e8509048187689f/pre_commit/git.py#L63
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `pre_commit/git.py`
  Content:
  ```
  1 from __future__ import unicode_literals
  2 
  3 import functools
  4 import logging
  5 import os.path
  6 import re
  7 import sys
  8 
  9 from pre_commit.errors import FatalError
  10 from pre_commit.util import CalledProcessError
  11 from pre_commit.util import cmd_output
  12 from pre_commit.util import memoize_by_cwd
  13 
  14 
  15 logger = logging.getLogger('pre_commit')
  16 
  17 
  18 def get_root():
  19     try:
  20         return cmd_output('git', 'rev-parse', '--show-toplevel')[1].strip()
  21     except CalledProcessError:
  22         raise FatalError(
  23             'git failed. Is it installed, and are you in a Git repository '
  24             'directory?'
  25         )
  26 
  27 
  28 def get_git_dir(git_root):
  29     return os.path.normpath(os.path.join(
  30         git_root,
  31         cmd_output('git', 'rev-parse', '--git-dir', cwd=git_root)[1].strip(),
  32     ))
  33 
  34 
  35 def get_remote_url(git_root):
  36     ret = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root)[1]
  37     return ret.strip()
  38 
  39 
  40 def is_in_merge_conflict():
  41     git_dir = get_git_dir('.')
  42     return (
  43         os.path.exists(os.path.join(git_dir, 'MERGE_MSG')) and
  44         os.path.exists(os.path.join(git_dir, 'MERGE_HEAD'))
  45     )
  46 
  47 
  48 def parse_merge_msg_for_conflicts(merge_msg):
  49     # Conflicted files start with tabs
  50     return [
  51         line.lstrip('#').strip()
  52         for line in merge_msg.splitlines()
  53         # '#\t' for git 2.4.1
  54         if line.startswith(('\t', '#\t'))
  55     ]
  56 
  57 
  58 @memoize_by_cwd
  59 def get_conflicted_files():
  60     logger.info('Checking merge-conflict files only.')
  61     # Need to get the conflicted files from the MERGE_MSG because they could
  62     # have resolved the conflict by choosing one side or the other
  63     merge_msg = open(os.path.join(get_git_dir('.'), 'MERGE_MSG')).read()
  64     merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)
  65 
  66     # This will get the rest of the changes made after the merge.
  67     # If they resolved the merge conflict by choosing a mesh of both sides
  68     # this will also include the conflicted files
  69     tree_hash = cmd_output('git', 'write-tree')[1].strip()
  70     merge_diff_filenames = cmd_output(
  71         'git', 'diff', '-m', tree_hash, 'HEAD', 'MERGE_HEAD', '--name-only',
  72     )[1].splitlines()
  73     return set(merge_conflict_filenames) | set(merge_diff_filenames)
  74 
  75 
  76 @memoize_by_cwd
  77 def get_staged_files():
  78     return cmd_output(
  79         'git', 'diff', '--staged', '--name-only',
  80         # Everything except for D
  81         '--diff-filter=ACMRTUXB'
  82     )[1].splitlines()
  83 
  84 
  85 @memoize_by_cwd
  86 def get_all_files():
  87     return cmd_output('git', 'ls-files')[1].splitlines()
  88 
  89 
  90 def get_files_matching(all_file_list_strategy):
  91     @functools.wraps(all_file_list_strategy)
  92     @memoize_by_cwd
  93     def wrapper(include_expr, exclude_expr):
  94         include_regex = re.compile(include_expr)
  95         exclude_regex = re.compile(exclude_expr)
  96         return {
  97             filename
  98             for filename in all_file_list_strategy()
  99             if (
  100                 include_regex.search(filename) and
  101                 not exclude_regex.search(filename) and
  102                 os.path.lexists(filename)
  103             )
  104         }
  105     return wrapper
  106 
  107 
  108 get_staged_files_matching = get_files_matching(get_staged_files)
  109 get_all_files_matching = get_files_matching(get_all_files)
  110 get_conflicted_files_matching = get_files_matching(get_conflicted_files)
  111 
  112 
  113 def check_for_cygwin_mismatch():
  114     """See https://github.com/pre-commit/pre-commit/issues/354"""
  115     if sys.platform in ('cygwin', 'win32'):  # pragma: no cover (windows)
  116         is_cygwin_python = sys.platform == 'cygwin'
  117         toplevel = cmd_output('git', 'rev-parse', '--show-toplevel')[1]
  118         is_cygwin_git = toplevel.startswith('/')
  119 
  120         if is_cygwin_python ^ is_cygwin_git:
  121             exe_type = {True: '(cygwin)', False: '(windows)'}
  122             logger.warn(
  123                 'pre-commit has detected a mix of cygwin python / git\n'
  124                 'This combination is not supported, it is likely you will '
  125                 'receive an error later in the program.\n'
  126                 'Make sure to use cygwin git+python while using cygwin\n'
  127                 'These can be installed through the cygwin installer.\n'
  128                 ' - python {}\n'
  129                 ' - git {}\n'.format(
  130                     exe_type[is_cygwin_python],
  131                     exe_type[is_cygwin_git],
  132                 )
  133             )
  134
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/pre_commit/git.py b/pre_commit/git.py
  --- a/pre_commit/git.py
  +++ b/pre_commit/git.py
  @@ -48,10 +48,10 @@
   def parse_merge_msg_for_conflicts(merge_msg):
       # Conflicted files start with tabs
       return [
  -        line.lstrip('#').strip()
  +        line.lstrip(b'#').strip().decode('UTF-8')
           for line in merge_msg.splitlines()
           # '#\t' for git 2.4.1
  -        if line.startswith(('\t', '#\t'))
  +        if line.startswith((b'\t', b'#\t'))
       ]
   
   
  @@ -60,7 +60,7 @@
       logger.info('Checking merge-conflict files only.')
       # Need to get the conflicted files from the MERGE_MSG because they could
       # have resolved the conflict by choosing one side or the other
  -    merge_msg = open(os.path.join(get_git_dir('.'), 'MERGE_MSG')).read()
  +    merge_msg = open(os.path.join(get_git_dir('.'), 'MERGE_MSG'), 'rb').read()
       merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)
   
       # This will get the rest of the changes made after the merge.
  
verification_info: {"golden_diff": "diff --git a/pre_commit/git.py b/pre_commit/git.py\n--- a/pre_commit/git.py\n+++ b/pre_commit/git.py\n@@ -48,10 +48,10 @@\n def parse_merge_msg_for_conflicts(merge_msg):\n     # Conflicted files start with tabs\n     return [\n-        line.lstrip('#').strip()\n+        line.lstrip(b'#').strip().decode('UTF-8')\n         for line in merge_msg.splitlines()\n         # '#\\t' for git 2.4.1\n-        if line.startswith(('\\t', '#\\t'))\n+        if line.startswith((b'\\t', b'#\\t'))\n     ]\n \n \n@@ -60,7 +60,7 @@\n     logger.info('Checking merge-conflict files only.')\n     # Need to get the conflicted files from the MERGE_MSG because they could\n     # have resolved the conflict by choosing one side or the other\n-    merge_msg = open(os.path.join(get_git_dir('.'), 'MERGE_MSG')).read()\n+    merge_msg = open(os.path.join(get_git_dir('.'), 'MERGE_MSG'), 'rb').read()\n     merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)\n \n     # This will get the rest of the changes made after the merge.\n", "issue": "Unicode error: python 2 + merge conflict + non-ascii commit message\nThe important part of the stack:\r\n\r\n```\r\n  File \"...python2.7/site-packages/pre_commit/commands/run.py\", line 52, in get_filenames\r\n    return getter(include_expr, exclude_expr)\r\n  File \"...python2.7/site-packages/pre_commit/util.py\", line 46, in wrapper\r\n    ret = wrapper._cache[key] = func(*args)\r\n  File \"...python2.7/site-packages/pre_commit/git.py\", line 98, in wrapper\r\n    for filename in all_file_list_strategy()\r\n  File \"...python2.7/site-packages/pre_commit/util.py\", line 46, in wrapper\r\n    ret = wrapper._cache[key] = func(*args)\r\n  File \"...python2.7/site-packages/pre_commit/git.py\", line 64, in get_conflicted_files\r\n    merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)\r\n  File \"...python2.7/site-packages/pre_commit/git.py\", line 54, in parse_merge_msg_for_conflicts\r\n    if line.startswith(('\\t', '#\\t'))\r\nUnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 37: ordinal not in range(128)\r\n```\r\n\r\nAn easy fix: https://github.com/pre-commit/pre-commit/blob/e3b14c35f782ed464e3f96b44e8509048187689f/pre_commit/git.py#L63\n", "before_files": [{"content": "from __future__ import unicode_literals\n\nimport functools\nimport logging\nimport os.path\nimport re\nimport sys\n\nfrom pre_commit.errors import FatalError\nfrom pre_commit.util import CalledProcessError\nfrom pre_commit.util import cmd_output\nfrom pre_commit.util import memoize_by_cwd\n\n\nlogger = logging.getLogger('pre_commit')\n\n\ndef get_root():\n    try:\n        return cmd_output('git', 'rev-parse', '--show-toplevel')[1].strip()\n    except CalledProcessError:\n        raise FatalError(\n            'git failed. Is it installed, and are you in a Git repository '\n            'directory?'\n        )\n\n\ndef get_git_dir(git_root):\n    return os.path.normpath(os.path.join(\n        git_root,\n        cmd_output('git', 'rev-parse', '--git-dir', cwd=git_root)[1].strip(),\n    ))\n\n\ndef get_remote_url(git_root):\n    ret = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root)[1]\n    return ret.strip()\n\n\ndef is_in_merge_conflict():\n    git_dir = get_git_dir('.')\n    return (\n        os.path.exists(os.path.join(git_dir, 'MERGE_MSG')) and\n        os.path.exists(os.path.join(git_dir, 'MERGE_HEAD'))\n    )\n\n\ndef parse_merge_msg_for_conflicts(merge_msg):\n    # Conflicted files start with tabs\n    return [\n        line.lstrip('#').strip()\n        for line in merge_msg.splitlines()\n        # '#\\t' for git 2.4.1\n        if line.startswith(('\\t', '#\\t'))\n    ]\n\n\n@memoize_by_cwd\ndef get_conflicted_files():\n    logger.info('Checking merge-conflict files only.')\n    # Need to get the conflicted files from the MERGE_MSG because they could\n    # have resolved the conflict by choosing one side or the other\n    merge_msg = open(os.path.join(get_git_dir('.'), 'MERGE_MSG')).read()\n    merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)\n\n    # This will get the rest of the changes made after the merge.\n    # If they resolved the merge conflict by choosing a mesh of both sides\n    # this will also include the conflicted files\n    tree_hash = cmd_output('git', 'write-tree')[1].strip()\n    merge_diff_filenames = cmd_output(\n        'git', 'diff', '-m', tree_hash, 'HEAD', 'MERGE_HEAD', '--name-only',\n    )[1].splitlines()\n    return set(merge_conflict_filenames) | set(merge_diff_filenames)\n\n\n@memoize_by_cwd\ndef get_staged_files():\n    return cmd_output(\n        'git', 'diff', '--staged', '--name-only',\n        # Everything except for D\n        '--diff-filter=ACMRTUXB'\n    )[1].splitlines()\n\n\n@memoize_by_cwd\ndef get_all_files():\n    return cmd_output('git', 'ls-files')[1].splitlines()\n\n\ndef get_files_matching(all_file_list_strategy):\n    @functools.wraps(all_file_list_strategy)\n    @memoize_by_cwd\n    def wrapper(include_expr, exclude_expr):\n        include_regex = re.compile(include_expr)\n        exclude_regex = re.compile(exclude_expr)\n        return {\n            filename\n            for filename in all_file_list_strategy()\n            if (\n                include_regex.search(filename) and\n                not exclude_regex.search(filename) and\n                os.path.lexists(filename)\n            )\n        }\n    return wrapper\n\n\nget_staged_files_matching = get_files_matching(get_staged_files)\nget_all_files_matching = get_files_matching(get_all_files)\nget_conflicted_files_matching = get_files_matching(get_conflicted_files)\n\n\ndef check_for_cygwin_mismatch():\n    \"\"\"See https://github.com/pre-commit/pre-commit/issues/354\"\"\"\n    if sys.platform in ('cygwin', 'win32'):  # pragma: no cover (windows)\n        is_cygwin_python = sys.platform == 'cygwin'\n        toplevel = cmd_output('git', 'rev-parse', '--show-toplevel')[1]\n        is_cygwin_git = toplevel.startswith('/')\n\n        if is_cygwin_python ^ is_cygwin_git:\n            exe_type = {True: '(cygwin)', False: '(windows)'}\n            logger.warn(\n                'pre-commit has detected a mix of cygwin python / git\\n'\n                'This combination is not supported, it is likely you will '\n                'receive an error later in the program.\\n'\n                'Make sure to use cygwin git+python while using cygwin\\n'\n                'These can be installed through the cygwin installer.\\n'\n                ' - python {}\\n'\n                ' - git {}\\n'.format(\n                    exe_type[is_cygwin_python],\n                    exe_type[is_cygwin_git],\n                )\n            )\n", "path": "pre_commit/git.py"}], "after_files": [{"content": "from __future__ import unicode_literals\n\nimport functools\nimport logging\nimport os.path\nimport re\nimport sys\n\nfrom pre_commit.errors import FatalError\nfrom pre_commit.util import CalledProcessError\nfrom pre_commit.util import cmd_output\nfrom pre_commit.util import memoize_by_cwd\n\n\nlogger = logging.getLogger('pre_commit')\n\n\ndef get_root():\n    try:\n        return cmd_output('git', 'rev-parse', '--show-toplevel')[1].strip()\n    except CalledProcessError:\n        raise FatalError(\n            'git failed. Is it installed, and are you in a Git repository '\n            'directory?'\n        )\n\n\ndef get_git_dir(git_root):\n    return os.path.normpath(os.path.join(\n        git_root,\n        cmd_output('git', 'rev-parse', '--git-dir', cwd=git_root)[1].strip(),\n    ))\n\n\ndef get_remote_url(git_root):\n    ret = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root)[1]\n    return ret.strip()\n\n\ndef is_in_merge_conflict():\n    git_dir = get_git_dir('.')\n    return (\n        os.path.exists(os.path.join(git_dir, 'MERGE_MSG')) and\n        os.path.exists(os.path.join(git_dir, 'MERGE_HEAD'))\n    )\n\n\ndef parse_merge_msg_for_conflicts(merge_msg):\n    # Conflicted files start with tabs\n    return [\n        line.lstrip(b'#').strip().decode('UTF-8')\n        for line in merge_msg.splitlines()\n        # '#\\t' for git 2.4.1\n        if line.startswith((b'\\t', b'#\\t'))\n    ]\n\n\n@memoize_by_cwd\ndef get_conflicted_files():\n    logger.info('Checking merge-conflict files only.')\n    # Need to get the conflicted files from the MERGE_MSG because they could\n    # have resolved the conflict by choosing one side or the other\n    merge_msg = open(os.path.join(get_git_dir('.'), 'MERGE_MSG'), 'rb').read()\n    merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)\n\n    # This will get the rest of the changes made after the merge.\n    # If they resolved the merge conflict by choosing a mesh of both sides\n    # this will also include the conflicted files\n    tree_hash = cmd_output('git', 'write-tree')[1].strip()\n    merge_diff_filenames = cmd_output(\n        'git', 'diff', '-m', tree_hash, 'HEAD', 'MERGE_HEAD', '--name-only',\n    )[1].splitlines()\n    return set(merge_conflict_filenames) | set(merge_diff_filenames)\n\n\n@memoize_by_cwd\ndef get_staged_files():\n    return cmd_output(\n        'git', 'diff', '--staged', '--name-only',\n        # Everything except for D\n        '--diff-filter=ACMRTUXB'\n    )[1].splitlines()\n\n\n@memoize_by_cwd\ndef get_all_files():\n    return cmd_output('git', 'ls-files')[1].splitlines()\n\n\ndef get_files_matching(all_file_list_strategy):\n    @functools.wraps(all_file_list_strategy)\n    @memoize_by_cwd\n    def wrapper(include_expr, exclude_expr):\n        include_regex = re.compile(include_expr)\n        exclude_regex = re.compile(exclude_expr)\n        return {\n            filename\n            for filename in all_file_list_strategy()\n            if (\n                include_regex.search(filename) and\n                not exclude_regex.search(filename) and\n                os.path.lexists(filename)\n            )\n        }\n    return wrapper\n\n\nget_staged_files_matching = get_files_matching(get_staged_files)\nget_all_files_matching = get_files_matching(get_all_files)\nget_conflicted_files_matching = get_files_matching(get_conflicted_files)\n\n\ndef check_for_cygwin_mismatch():\n    \"\"\"See https://github.com/pre-commit/pre-commit/issues/354\"\"\"\n    if sys.platform in ('cygwin', 'win32'):  # pragma: no cover (windows)\n        is_cygwin_python = sys.platform == 'cygwin'\n        toplevel = cmd_output('git', 'rev-parse', '--show-toplevel')[1]\n        is_cygwin_git = toplevel.startswith('/')\n\n        if is_cygwin_python ^ is_cygwin_git:\n            exe_type = {True: '(cygwin)', False: '(windows)'}\n            logger.warn(\n                'pre-commit has detected a mix of cygwin python / git\\n'\n                'This combination is not supported, it is likely you will '\n                'receive an error later in the program.\\n'\n                'Make sure to use cygwin git+python while using cygwin\\n'\n                'These can be installed through the cygwin installer.\\n'\n                ' - python {}\\n'\n                ' - git {}\\n'.format(\n                    exe_type[is_cygwin_python],\n                    exe_type[is_cygwin_git],\n                )\n            )\n", "path": "pre_commit/git.py"}]}