From 7905594215647b6900b466626b782c93588829f3 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 21 May 2015 07:49:22 -0700 Subject: [PATCH] Don't UnicodeDecodeError on non-ascii not-found hooks. Resolves #207. --- pre_commit/five.py | 8 ++++++++ pre_commit/main.py | 2 ++ pre_commit/output.py | 12 ++++-------- tests/commands/install_uninstall_test.py | 4 ++-- tests/commands/run_test.py | 21 +++++++++++++++++++-- 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/pre_commit/five.py b/pre_commit/five.py index 647061cf..8b9a2b54 100644 --- a/pre_commit/five.py +++ b/pre_commit/five.py @@ -20,3 +20,11 @@ else: # pragma: no cover (PY3 only) return s else: return s.decode('UTF-8') + + +def to_text(s): + return s if isinstance(s, text) else s.decode('UTF-8') + + +def to_bytes(s): + return s if isinstance(s, bytes) else s.encode('UTF-8') diff --git a/pre_commit/main.py b/pre_commit/main.py index e0b86b30..aed66886 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -7,6 +7,7 @@ import sys import pkg_resources from pre_commit import color +from pre_commit import five from pre_commit.commands.autoupdate import autoupdate from pre_commit.commands.clean import clean from pre_commit.commands.install_uninstall import install @@ -25,6 +26,7 @@ os.environ.pop('__PYVENV_LAUNCHER__', None) def main(argv=None): argv = argv if argv is not None else sys.argv[1:] + argv = [five.to_text(arg) for arg in argv] parser = argparse.ArgumentParser() # http://stackoverflow.com/a/8521644/812183 diff --git a/pre_commit/output.py b/pre_commit/output.py index 60c95cab..b0cdd8c6 100644 --- a/pre_commit/output.py +++ b/pre_commit/output.py @@ -77,12 +77,8 @@ def get_hook_message( ) -def sys_stdout_write_wrapper(s, stream=sys.stdout): - """Python 2.6 chokes on unicode being passed to sys.stdout.write. +stdout_byte_stream = getattr(sys.stdout, 'buffer', sys.stdout) - This is an adapter because PY2 is ok with bytes and PY3 requires text. - """ - assert type(s) is five.text - if five.PY2: # pragma: no cover (PY2) - s = s.encode('UTF-8') - stream.write(s) + +def sys_stdout_write_wrapper(s, stream=stdout_byte_stream): + stream.write(five.to_bytes(s)) diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index a9f35342..9e1806e1 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -105,7 +105,7 @@ def _get_commit_output( cmd_output('git', 'add', touch_file) # Don't want to write to home directory home = home or tmpdir_factory.get() - env = dict(env_base, **{'PRE_COMMIT_HOME': home}) + env = dict(env_base, PRE_COMMIT_HOME=home) return cmd_output( 'git', 'commit', '-m', 'Commit!', '--allow-empty', # git commit puts pre-commit to stderr @@ -414,7 +414,7 @@ def test_installed_from_venv(tmpdir_factory): def _get_push_output(tmpdir_factory): # Don't want to write to home directory home = tmpdir_factory.get() - env = dict(os.environ, **{'PRE_COMMIT_HOME': home}) + env = dict(os.environ, PRE_COMMIT_HOME=home) return cmd_output( 'git', 'push', 'origin', 'HEAD:new_branch', # git commit puts pre-commit to stderr diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index f907eed9..c687e832 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -5,6 +5,7 @@ import io import os import os.path import subprocess +import sys import mock import pytest @@ -274,6 +275,22 @@ def test_multiple_hooks_same_id( assert output.count('Bash hook') == 2 +def test_non_ascii_hook_id( + repo_with_passing_hook, mock_out_store_directory, tmpdir_factory, +): + with cwd(repo_with_passing_hook): + install(Runner(repo_with_passing_hook)) + # Don't want to write to home directory + env = dict(os.environ, PRE_COMMIT_HOME=tmpdir_factory.get()) + _, stdout, _ = cmd_output( + sys.executable, '-m', 'pre_commit.main', 'run', '☃', + env=env, retcode=None, + ) + assert 'UnicodeDecodeError' not in stdout + # Doesn't actually happen, but a reasonable assertion + assert 'UnicodeEncodeError' not in stdout + + def test_stdout_write_bug_py26( repo_with_failing_hook, mock_out_store_directory, tmpdir_factory, ): @@ -289,7 +306,7 @@ def test_stdout_write_bug_py26( install(Runner(repo_with_failing_hook)) # Don't want to write to home directory - env = dict(os.environ, **{'PRE_COMMIT_HOME': tmpdir_factory.get()}) + env = dict(os.environ, PRE_COMMIT_HOME=tmpdir_factory.get()) # Have to use subprocess because pytest monkeypatches sys.stdout _, stdout, _ = cmd_output( 'git', 'commit', '-m', 'Commit!', @@ -329,7 +346,7 @@ def test_lots_of_files(mock_out_store_directory, tmpdir_factory): install(Runner(git_path)) # Don't want to write to home directory - env = dict(os.environ, **{'PRE_COMMIT_HOME': tmpdir_factory.get()}) + env = dict(os.environ, PRE_COMMIT_HOME=tmpdir_factory.get()) cmd_output( 'git', 'commit', '-m', 'Commit!', # git commit puts pre-commit to stderr