Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5a0d35d

Browse files
committed
Merge pull request #65 from pre-commit/stash_before_check_30
Pre-commit stashes unstaged changes on run. Closes #30.
2 parents 7496151 + 514a6d6 commit 5a0d35d

12 files changed

Lines changed: 363 additions & 27 deletions

pre_commit/color.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
RED = '\033[41m'
55
GREEN = '\033[42m'
6+
YELLOW = '\033[43;30m'
7+
TURQUOISE = '\033[46;30m'
68
NORMAL = '\033[0m'
79

810

pre_commit/logging_handler.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
from __future__ import print_function
3+
4+
import logging
5+
6+
from pre_commit import color
7+
8+
9+
LOG_LEVEL_COLORS = {
10+
'DEBUG': '',
11+
'INFO': '',
12+
'WARNING': color.YELLOW,
13+
'ERROR': color.RED,
14+
}
15+
16+
17+
class LoggingHandler(logging.Handler):
18+
def __init__(self, use_color):
19+
super(LoggingHandler, self).__init__()
20+
self.use_color = use_color
21+
22+
def emit(self, record):
23+
print(
24+
u'{0}{1}'.format(
25+
color.format_color(
26+
'[{0}]'.format(record.levelname),
27+
LOG_LEVEL_COLORS[record.levelname],
28+
self.use_color,
29+
) + ' ' if record.levelno >= logging.WARNING else '',
30+
record.getMessage(),
31+
)
32+
)

pre_commit/prefixed_command_runner.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
class CalledProcessError(RuntimeError):
88
def __init__(self, returncode, cmd, expected_returncode, output=None):
9+
super(CalledProcessError, self).__init__(
10+
returncode, cmd, expected_returncode, output,
11+
)
912
self.returncode = returncode
1013
self.cmd = cmd
1114
self.expected_returncode = expected_returncode
@@ -15,13 +18,13 @@ def __str__(self):
1518
return (
1619
'Command: {0!r}\n'
1720
'Return code: {1}\n'
18-
'Expected return code {2}\n',
21+
'Expected return code: {2}\n'
1922
'Output: {3!r}\n'.format(
2023
self.cmd,
2124
self.returncode,
2225
self.expected_returncode,
2326
self.output,
24-
),
27+
)
2528
)
2629

2730

@@ -48,15 +51,15 @@ def _create_path_if_not_exists(self):
4851
self.__makedirs(self.prefix_dir)
4952

5053
def run(self, cmd, retcode=0, stdin=None, **kwargs):
54+
popen_kwargs = {
55+
'stdin': subprocess.PIPE,
56+
'stdout': subprocess.PIPE,
57+
'stderr': subprocess.PIPE,
58+
}
59+
popen_kwargs.update(kwargs)
5160
self._create_path_if_not_exists()
5261
replaced_cmd = _replace_cmd(cmd, prefix=self.prefix_dir)
53-
proc = self.__popen(
54-
replaced_cmd,
55-
stdin=subprocess.PIPE,
56-
stdout=subprocess.PIPE,
57-
stderr=subprocess.PIPE,
58-
**kwargs
59-
)
62+
proc = self.__popen(replaced_cmd, **popen_kwargs)
6063
stdout, stderr = proc.communicate(stdin)
6164
returncode = proc.returncode
6265

pre_commit/repository.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11

2-
from __future__ import print_function
3-
42
import contextlib
3+
import logging
54
from plumbum import local
65

76
import pre_commit.constants as C
@@ -14,6 +13,9 @@
1413
from pre_commit.util import clean_path_on_failure
1514

1615

16+
logger = logging.getLogger('pre_commit')
17+
18+
1719
class Repository(object):
1820
def __init__(self, repo_config):
1921
self.repo_config = repo_config
@@ -66,9 +68,9 @@ def create(self):
6668
return
6769

6870
# Checking out environment for the first time
69-
print('Installing environment for {0}.'.format(self.repo_url))
70-
print('Once installed this environment will be reused.')
71-
print('This may take a few minutes...')
71+
logger.info('Installing environment for {0}.'.format(self.repo_url))
72+
logger.info('Once installed this environment will be reused.')
73+
logger.info('This may take a few minutes...')
7274
with clean_path_on_failure(unicode(local.path(self.sha))):
7375
local['git']['clone', '--no-checkout', self.repo_url, self.sha]()
7476
with self.in_checkout():

pre_commit/run.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22
from __future__ import print_function
33

44
import argparse
5+
import logging
56
import subprocess
67
import sys
78

89
from pre_commit import color
910
from pre_commit import commands
1011
from pre_commit import git
12+
from pre_commit.logging_handler import LoggingHandler
1113
from pre_commit.runner import Runner
14+
from pre_commit.staged_files_only import staged_files_only
1215
from pre_commit.util import entry
1316

1417

18+
logger = logging.getLogger('pre_commit')
19+
1520
COLS = int(subprocess.Popen(['tput', 'cols'], stdout=subprocess.PIPE).communicate()[0])
1621

1722
PASS_FAIL_LENGTH = 6
@@ -81,10 +86,15 @@ def run_single_hook(runner, hook_id, args):
8186

8287

8388
def _run(runner, args):
84-
if args.hook:
85-
return run_single_hook(runner, args.hook, args)
86-
else:
87-
return run_hooks(runner, args)
89+
# Set up our logging handler
90+
logger.addHandler(LoggingHandler(args.color))
91+
logger.setLevel(logging.INFO)
92+
93+
with staged_files_only(runner.cmd_runner):
94+
if args.hook:
95+
return run_single_hook(runner, args.hook, args)
96+
else:
97+
return run_hooks(runner, args)
8898

8999

90100
@entry

pre_commit/staged_files_only.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
2+
import contextlib
3+
import logging
4+
import time
5+
6+
from pre_commit.prefixed_command_runner import CalledProcessError
7+
8+
9+
logger = logging.getLogger('pre_commit')
10+
11+
12+
@contextlib.contextmanager
13+
def staged_files_only(cmd_runner):
14+
"""Clear any unstaged changes from the git working directory inside this
15+
context.
16+
17+
Args:
18+
cmd_runner - PrefixedCommandRunner
19+
"""
20+
# Determine if there are unstaged files
21+
retcode, _, _ = cmd_runner.run(
22+
['git', 'diff-files', '--quiet'],
23+
retcode=None,
24+
)
25+
if retcode:
26+
patch_filename = cmd_runner.path('patch{0}'.format(int(time.time())))
27+
logger.warning('Unstaged files detected.')
28+
logger.info(
29+
'Stashing unstaged files to {0}.'.format(patch_filename),
30+
)
31+
# Save the current unstaged changes as a patch
32+
with open(patch_filename, 'w') as patch_file:
33+
cmd_runner.run(['git', 'diff', '--binary'], stdout=patch_file)
34+
35+
# Clear the working directory of unstaged changes
36+
cmd_runner.run(['git', 'checkout', '--', '.'])
37+
try:
38+
yield
39+
finally:
40+
# Try to apply the patch we saved
41+
try:
42+
cmd_runner.run(['git', 'apply', patch_filename])
43+
except CalledProcessError:
44+
logger.warning(
45+
'Stashed changes conflicted with hook auto-fixes... '
46+
'Rolling back fixes...'
47+
)
48+
# We failed to apply the patch, presumably due to fixes made
49+
# by hooks.
50+
# Roll back the changes made by hooks.
51+
cmd_runner.run(['git', 'checkout', '--', '.'])
52+
cmd_runner.run(['git', 'apply', patch_filename])
53+
logger.info('Restored changes from {0}.'.format(patch_filename))
54+
else:
55+
# There weren't any staged files so we don't need to do anything
56+
# special
57+
yield

testing/resources/img1.jpg

843 Bytes
Loading

testing/resources/img2.jpg

891 Bytes
Loading

testing/resources/img3.jpg

859 Bytes
Loading

tests/prefixed_command_runner_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@
99
from pre_commit.prefixed_command_runner import PrefixedCommandRunner
1010

1111

12+
def test_CalledProcessError_str():
13+
error = CalledProcessError(1, ['git', 'status'], 0, ('stdout', 'stderr'))
14+
assert str(error) == (
15+
"Command: ['git', 'status']\n"
16+
"Return code: 1\n"
17+
"Expected return code: 0\n"
18+
"Output: ('stdout', 'stderr')\n"
19+
)
20+
21+
1222
@pytest.fixture
1323
def popen_mock():
1424
popen = mock.Mock(spec=subprocess.Popen)

0 commit comments

Comments
 (0)