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

Skip to content

Commit bcb0072

Browse files
committed
Clean up directories on failure. Closes pre-commit#58.
1 parent 443b62d commit bcb0072

8 files changed

Lines changed: 105 additions & 40 deletions

File tree

pre_commit/languages/node.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from pre_commit.languages import helpers
44
from pre_commit.languages import python
55
from pre_commit.prefixed_command_runner import CalledProcessError
6+
from pre_commit.util import clean_path_on_failure
67

78

89
NODE_ENV = 'node_env'
@@ -30,22 +31,30 @@ def install_environment(repo_cmd_runner):
3031
if repo_cmd_runner.exists(NODE_ENV):
3132
return
3233

33-
repo_cmd_runner.run(['virtualenv', '{{prefix}}{0}'.format(python.PY_ENV)])
34-
35-
with python.in_env(repo_cmd_runner) as python_env:
36-
python_env.run('pip install nodeenv')
37-
38-
# Try and use the system level node executable first
39-
try:
40-
python_env.run('nodeenv -n system {{prefix}}{0}'.format(NODE_ENV))
41-
except CalledProcessError:
42-
# TODO: log failure here
43-
# cleanup
44-
# TODO: local.path(NODE_ENV).delete()
45-
python_env.run('nodeenv --jobs 4 {{prefix}}{0}'.format(NODE_ENV))
34+
with clean_path_on_failure(repo_cmd_runner.path(python.PY_ENV)):
35+
repo_cmd_runner.run(
36+
['virtualenv', '{{prefix}}{0}'.format(python.PY_ENV)],
37+
)
4638

47-
with in_env(repo_cmd_runner) as node_env:
48-
node_env.run('cd {prefix} && npm install -g')
39+
with python.in_env(repo_cmd_runner) as python_env:
40+
python_env.run('pip install nodeenv')
41+
42+
with clean_path_on_failure(repo_cmd_runner.path(NODE_ENV)):
43+
# Try and use the system level node executable first
44+
try:
45+
python_env.run(
46+
'nodeenv -n system {{prefix}}{0}'.format(NODE_ENV),
47+
)
48+
except CalledProcessError:
49+
# TODO: log failure here
50+
# cleanup
51+
# TODO: local.path(NODE_ENV).delete()
52+
python_env.run(
53+
'nodeenv --jobs 4 {{prefix}}{0}'.format(NODE_ENV),
54+
)
55+
56+
with in_env(repo_cmd_runner) as node_env:
57+
node_env.run('cd {prefix} && npm install -g')
4958

5059

5160
def run_hook(repo_cmd_runner, hook, file_args):

pre_commit/languages/python.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import contextlib
33

44
from pre_commit.languages import helpers
5+
from pre_commit.util import clean_path_on_failure
56

67

78
PY_ENV = 'py_env'
@@ -25,9 +26,10 @@ def install_environment(repo_cmd_runner):
2526
return
2627

2728
# Install a virtualenv
28-
repo_cmd_runner.run(['virtualenv', '{{prefix}}{0}'.format(PY_ENV)])
29-
with in_env(repo_cmd_runner) as env:
30-
env.run('cd {prefix} && pip install .')
29+
with clean_path_on_failure(repo_cmd_runner.path(PY_ENV)):
30+
repo_cmd_runner.run(['virtualenv', '{{prefix}}{0}'.format(PY_ENV)])
31+
with in_env(repo_cmd_runner) as env:
32+
env.run('cd {prefix} && pip install .')
3133

3234

3335
def run_hook(repo_cmd_runner, hook, file_args):

pre_commit/languages/ruby.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import contextlib
33

44
from pre_commit.languages import helpers
5+
from pre_commit.util import clean_path_on_failure
56

67

78
RVM_ENV = 'rvm_env'
@@ -23,9 +24,10 @@ def install_environment(repo_cmd_runner):
2324
if repo_cmd_runner.exists(RVM_ENV):
2425
return
2526

26-
repo_cmd_runner.run(['__rvm-env.sh', '{{prefix}}{0}'.format(RVM_ENV)])
27-
with in_env(repo_cmd_runner) as env:
28-
env.run('cd {prefix} && bundle install')
27+
with clean_path_on_failure(repo_cmd_runner.path(RVM_ENV)):
28+
repo_cmd_runner.run(['__rvm-env.sh', '{{prefix}}{0}'.format(RVM_ENV)])
29+
with in_env(repo_cmd_runner) as env:
30+
env.run('cd {prefix} && bundle install')
2931

3032

3133
def run_hook(repo_cmd_runner, hook, file_args):

pre_commit/repository.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from pre_commit.ordereddict import OrderedDict
1010
from pre_commit.prefixed_command_runner import PrefixedCommandRunner
1111
from pre_commit.util import cached_property
12+
from pre_commit.util import clean_path_on_failure
1213

1314

1415
class Repository(object):
@@ -62,9 +63,10 @@ def create(self):
6263
# Project already exists, no reason to re-create it
6364
return
6465

65-
local['git']['clone', '--no-checkout', self.repo_url, self.sha]()
66-
with self.in_checkout():
67-
local['git']['checkout', self.sha]()
66+
with clean_path_on_failure(unicode(local.path(self.sha))):
67+
local['git']['clone', '--no-checkout', self.repo_url, self.sha]()
68+
with self.in_checkout():
69+
local['git']['checkout', self.sha]()
6870

6971
def require_installed(self, cmd_runner):
7072
if self.__installed:

pre_commit/util.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11

2+
import contextlib
23
import functools
34
import os
5+
import os.path
6+
import shutil
47
import sys
58

69

@@ -49,3 +52,14 @@ def wrapper(argv=None):
4952
argv = sys.argv[1:]
5053
return func(argv)
5154
return wrapper
55+
56+
57+
@contextlib.contextmanager
58+
def clean_path_on_failure(path):
59+
"""Cleans up the directory on an exceptional failure."""
60+
try:
61+
yield
62+
except BaseException:
63+
if os.path.exists(path):
64+
shutil.rmtree(path)
65+
raise

tests/conftest.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@
1313

1414

1515
@pytest.yield_fixture
16-
def empty_git_dir(tmpdir):
16+
def in_tmpdir(tmpdir):
1717
with local.cwd(tmpdir.strpath):
18-
local['git']['init']()
1918
yield tmpdir.strpath
2019

2120

21+
@pytest.yield_fixture
22+
def empty_git_dir(in_tmpdir):
23+
local['git']['init']()
24+
yield in_tmpdir
25+
26+
2227
def add_and_commit():
2328
local['git']['add', '.']()
2429
local['git']['commit', '-m', 'random commit {0}'.format(time.time())]()

tests/prefixed_command_runner_test.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import mock
44
import pytest
55
import subprocess
6-
from plumbum import local
76

87
from pre_commit.prefixed_command_runner import _replace_cmd
98
from pre_commit.prefixed_command_runner import CalledProcessError
@@ -110,23 +109,20 @@ def test_from_command_runner_preserves_popen(popen_mock, makedirs_mock):
110109
makedirs_mock.assert_called_once_with('foo/bar/')
111110

112111

113-
def test_create_path_if_not_exists(tmpdir):
114-
with local.cwd(tmpdir.strpath):
115-
instance = PrefixedCommandRunner('foo')
116-
assert not os.path.exists('foo')
117-
instance._create_path_if_not_exists()
118-
assert os.path.exists('foo')
112+
def test_create_path_if_not_exists(in_tmpdir):
113+
instance = PrefixedCommandRunner('foo')
114+
assert not os.path.exists('foo')
115+
instance._create_path_if_not_exists()
116+
assert os.path.exists('foo')
119117

120118

121-
def test_exists_does_not_exist(tmpdir):
122-
with local.cwd(tmpdir.strpath):
123-
assert not PrefixedCommandRunner('.').exists('foo')
119+
def test_exists_does_not_exist(in_tmpdir):
120+
assert not PrefixedCommandRunner('.').exists('foo')
124121

125122

126-
def test_exists_does_exist(tmpdir):
127-
with local.cwd(tmpdir.strpath):
128-
os.mkdir('foo')
129-
assert PrefixedCommandRunner('.').exists('foo')
123+
def test_exists_does_exist(in_tmpdir):
124+
os.mkdir('foo')
125+
assert PrefixedCommandRunner('.').exists('foo')
130126

131127

132128
def test_raises_on_error(popen_mock, makedirs_mock):

tests/util_test.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11

22
import mock
33
import pytest
4+
import os
5+
import os.path
46
import random
57
import sys
68
from plumbum import local
79

810
from pre_commit.util import cached_property
11+
from pre_commit.util import clean_path_on_failure
912
from pre_commit.util import entry
1013
from pre_commit.util import memoize_by_cwd
1114

@@ -84,3 +87,35 @@ def test_no_arguments_passed_uses_argv(entry_func):
8487
with mock.patch.object(sys, 'argv', argv):
8588
ret = entry_func()
8689
assert ret == argv[1:]
90+
91+
92+
def test_clean_on_failure_noop(in_tmpdir):
93+
with clean_path_on_failure('foo'): pass
94+
95+
96+
def test_clean_path_on_failure_does_nothing_when_not_raising(in_tmpdir):
97+
with clean_path_on_failure('foo'):
98+
os.mkdir('foo')
99+
assert os.path.exists('foo')
100+
101+
102+
def test_clean_path_on_failure_cleans_for_normal_exception(in_tmpdir):
103+
class MyException(Exception): pass
104+
105+
with pytest.raises(MyException):
106+
with clean_path_on_failure('foo'):
107+
os.mkdir('foo')
108+
raise MyException
109+
110+
assert not os.path.exists('foo')
111+
112+
113+
def test_clean_path_on_failure_cleans_for_system_exit(in_tmpdir):
114+
class MySystemExit(SystemExit): pass
115+
116+
with pytest.raises(MySystemExit):
117+
with clean_path_on_failure('foo'):
118+
os.mkdir('foo')
119+
raise MySystemExit
120+
121+
assert not os.path.exists('foo')

0 commit comments

Comments
 (0)