Skip to content

Commit 216b5c6

Browse files
committed
Resolves cwd problem
1 parent 6f0d566 commit 216b5c6

13 files changed

Lines changed: 215 additions & 110 deletions

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ python:
55
- 2.7
66

77
install: pip install virtualenv
8-
script: make
8+
script: make coverage
99

1010

1111
before_install:

pre_commit/languages/all.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,22 @@
55

66
# A language implements the following two functions in its module:
77
#
8-
# def install_environment():
8+
# def install_environment(repo_cmd_runner):
99
# """Installs a repository in the given repository. Note that the current
1010
# working directory will already be inside the repository.
11+
#
12+
# Args:
13+
# repo_cmd_runner - `PrefixedCommandRunner` bound to the repository.
1114
# """
1215
#
13-
# def run_hook(hook, file_args):
16+
# def run_hook(repo_cmd_runner, hook, file_args):
1417
# """Runs a hook and returns the returncode and output of running that hook.
1518
#
19+
# Args:
20+
# repo_cmd_runner - `PrefixedCommandRunner` bound to the repository.
21+
# hook - Hook dictionary
22+
# file_args - The files to be run
23+
#
1624
# Returns:
1725
# (returncode, stdout, stderr)
1826
# """

pre_commit/languages/helpers.py

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

2-
import subprocess
3-
42

53
def run_hook(env, hook, file_args):
64
return env.run(
75
' '.join(['xargs', hook['entry']] + hook.get('args', [])),
86
stdin='\n'.join(list(file_args) + ['']),
7+
retcode=None,
98
)
109

1110

1211
class Environment(object):
12+
def __init__(self, repo_cmd_runner):
13+
self.repo_cmd_runner = repo_cmd_runner
14+
1315
@property
1416
def env_prefix(self):
1517
"""env_prefix is a value that is prefixed to the command that is run.
@@ -24,14 +26,8 @@ def env_prefix(self):
2426
"""
2527
raise NotImplementedError
2628

27-
def run(self, cmd, stdin=None):
29+
def run(self, cmd, **kwargs):
2830
"""Returns (returncode, stdout, stderr)."""
29-
proc = subprocess.Popen(
30-
['bash', '-c', ' '.join([self.env_prefix, cmd])],
31-
stdin=subprocess.PIPE,
32-
stdout=subprocess.PIPE,
33-
stderr=subprocess.PIPE,
31+
return self.repo_cmd_runner.run(
32+
['bash', '-c', ' '.join([self.env_prefix, cmd])], **kwargs
3433
)
35-
stdout, stderr = proc.communicate(stdin)
36-
37-
return proc.returncode, stdout, stderr

pre_commit/languages/node.py

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import contextlib
2-
from plumbum import local
2+
import subprocess
33

44
from pre_commit.languages import helpers
55
from pre_commit.languages import python
@@ -12,37 +12,44 @@ class NodeEnv(python.PythonEnv):
1212
@property
1313
def env_prefix(self):
1414
base = super(NodeEnv, self).env_prefix
15-
return ' '.join([base, '. {0}/bin/activate &&'.format(NODE_ENV)])
15+
return ' '.join([
16+
base,
17+
'. {{prefix}}{0}/bin/activate &&'.format(NODE_ENV)]
18+
)
1619

1720

1821
@contextlib.contextmanager
19-
def in_env():
20-
yield NodeEnv()
22+
def in_env(repo_cmd_runner):
23+
yield NodeEnv(repo_cmd_runner)
2124

2225

23-
def install_environment():
24-
assert local.path('package.json').exists()
26+
def install_environment(repo_cmd_runner):
27+
assert repo_cmd_runner.exists('package.json')
2528

26-
if local.path(NODE_ENV).exists():
29+
# Return immediately if we already have a virtualenv
30+
if repo_cmd_runner.exists(NODE_ENV):
2731
return
2832

29-
local['virtualenv'][python.PY_ENV]()
33+
repo_cmd_runner.run(['virtualenv', '{{prefix}}{0}'.format(python.PY_ENV)])
3034

31-
with python.in_env() as python_env:
35+
with python.in_env(repo_cmd_runner) as python_env:
3236
python_env.run('pip install nodeenv')
3337

3438
# Try and use the system level node executable first
35-
retcode, _, _ = python_env.run('nodeenv -n system {0}'.format(NODE_ENV))
36-
# TODO: log failure here
37-
# cleanup
38-
if retcode:
39-
local.path(NODE_ENV).delete()
40-
python_env.run('nodeenv --jobs 4 {0}'.format(NODE_ENV))
41-
42-
with in_env() as node_env:
43-
node_env.run('npm install -g')
44-
45-
46-
def run_hook(hook, file_args):
47-
with in_env() as node_env:
48-
return helpers.run_hook(node_env, hook, file_args)
39+
try:
40+
python_env.run('nodeenv -n system {{prefix}}{0}'.format(NODE_ENV))
41+
except subprocess.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))
46+
47+
with in_env(repo_cmd_runner) as node_env:
48+
node_env.run(
49+
'cd {0} && npm install -g'.format(repo_cmd_runner.prefix_dir),
50+
)
51+
52+
53+
def run_hook(repo_cmd_runner, hook, file_args):
54+
with in_env(repo_cmd_runner) as env:
55+
return helpers.run_hook(env, hook, file_args)

pre_commit/languages/python.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,35 @@
11

22
import contextlib
3-
from plumbum import local
3+
44
from pre_commit.languages import helpers
55

6+
67
PY_ENV = 'py_env'
78

89

910
class PythonEnv(helpers.Environment):
1011
@property
1112
def env_prefix(self):
12-
return '. {0}/bin/activate &&'.format(PY_ENV)
13+
return '. {{prefix}}{0}/bin/activate &&'.format(PY_ENV)
1314

1415

1516
@contextlib.contextmanager
16-
def in_env():
17-
yield PythonEnv()
17+
def in_env(repo_cmd_runner):
18+
yield PythonEnv(repo_cmd_runner)
1819

1920

20-
def install_environment():
21-
assert local.path('setup.py').exists()
21+
def install_environment(repo_cmd_runner):
22+
assert repo_cmd_runner.exists('setup.py')
2223
# Return immediately if we already have a virtualenv
23-
if local.path(PY_ENV).exists():
24+
if repo_cmd_runner.exists(PY_ENV):
2425
return
2526

2627
# Install a virtualenv
27-
local['virtualenv'][PY_ENV]()
28-
with in_env() as env:
29-
env.run('pip install .')
28+
repo_cmd_runner.run(['virtualenv', '{{prefix}}{0}'.format(PY_ENV)])
29+
with in_env(repo_cmd_runner) as env:
30+
env.run('cd {0} && pip install .'.format(repo_cmd_runner.prefix_dir))
3031

3132

32-
def run_hook(hook, file_args):
33-
with in_env() as env:
33+
def run_hook(repo_cmd_runner, hook, file_args):
34+
with in_env(repo_cmd_runner) as env:
3435
return helpers.run_hook(env, hook, file_args)

pre_commit/languages/ruby.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,33 @@
11

22
import contextlib
3-
from plumbum import local
43

54
from pre_commit.languages import helpers
65

76

87
RVM_ENV = 'rvm_env'
98

109

11-
class RubyEnv(object):
12-
def __init__(self):
13-
self.env_prefix = '. {0}/.rvm/scripts/rvm'.format(RVM_ENV)
14-
15-
def run(self, cmd, **kwargs):
16-
return local['bash']['-c', ' '.join([self.env_prefix, cmd])].run(**kwargs)
10+
class RubyEnv(helpers.Environment):
11+
@property
12+
def env_prefix(self):
13+
return '. {{prefix}}{0}/bin/activate &&'.format(RVM_ENV)
1714

1815

1916
@contextlib.contextmanager
20-
def in_env():
21-
yield RubyEnv()
17+
def in_env(repo_cmd_runner):
18+
yield RubyEnv(repo_cmd_runner)
2219

2320

24-
def install_environment():
21+
def install_environment(repo_cmd_runner):
2522
# Return immediately if we already have a virtualenv
26-
if local.path(RVM_ENV).exists():
23+
if repo_cmd_runner.exists(RVM_ENV):
2724
return
2825

29-
local['__rvm-env.sh'][RVM_ENV]()
30-
with in_env() as env:
31-
env.run('bundle install')
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('bundle install', cwd=repo_cmd_runner.prefix_dir)
3229

3330

34-
def run_hook(hook, file_args):
35-
with in_env() as env:
31+
def run_hook(repo_cmd_runner, hook, file_args):
32+
with in_env(repo_cmd_runner) as env:
3633
return helpers.run_hook(env, hook, file_args)

pre_commit/prefixed_command_runner.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,42 @@ class PrefixedCommandRunner(object):
1717
1818
will run ['/tmpl/foo/foo.sh', 'bar', 'baz']
1919
"""
20-
def __init__(self, prefix_dir, popen=subprocess.Popen):
20+
def __init__(self, prefix_dir, popen=subprocess.Popen, makedirs=os.makedirs):
2121
self.prefix_dir = prefix_dir.rstrip(os.sep) + os.sep
2222
self.__popen = popen
23+
self.__makedirs = makedirs
2324

24-
def run(self, cmd, stdin=None):
25+
def _create_path_if_not_exists(self):
26+
if not os.path.exists(self.prefix_dir):
27+
self.__makedirs(self.prefix_dir)
28+
29+
def run(self, cmd, retcode=0, stdin=None, **kwargs):
30+
self._create_path_if_not_exists()
2531
replaced_cmd = _replace_cmd(cmd, prefix=self.prefix_dir)
2632
proc = self.__popen(
2733
replaced_cmd,
2834
stdin=subprocess.PIPE,
2935
stdout=subprocess.PIPE,
3036
stderr=subprocess.PIPE,
37+
**kwargs
3138
)
3239
stdout, stderr = proc.communicate(stdin)
40+
returncode = proc.returncode
41+
42+
if retcode is not None and retcode != returncode:
43+
raise subprocess.CalledProcessError(
44+
returncode, replaced_cmd, output=(stdout, stderr),
45+
)
3346

3447
return proc.returncode, stdout, stderr
3548

36-
def path(self, path_end):
37-
path = os.path.join(self.prefix_dir, path_end)
49+
def path(self, *parts):
50+
path = os.path.join(self.prefix_dir, *parts)
3851
return os.path.normpath(path)
3952

53+
def exists(self, *parts):
54+
return os.path.exists(self.path(*parts))
55+
4056
@classmethod
4157
def from_command_runner(cls, command_runner, path_end):
4258
"""Constructs a new command runner from an existing one by appending

pre_commit/repository.py

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
from pre_commit.hooks_workspace import in_hooks_workspace
88
from pre_commit.languages.all import languages
99
from pre_commit.ordereddict import OrderedDict
10+
from pre_commit.prefixed_command_runner import PrefixedCommandRunner
1011
from pre_commit.util import cached_property
1112

1213

1314
class Repository(object):
1415
def __init__(self, repo_config):
1516
self.repo_config = repo_config
17+
self.__created = False
18+
self.__installed = False
1619

1720
@cached_property
1821
def repo_url(self):
@@ -43,13 +46,17 @@ def manifest(self):
4346
for hook in load_manifest(C.MANIFEST_FILE)
4447
)
4548

46-
@contextlib.contextmanager
47-
def in_checkout(self):
48-
with in_hooks_workspace():
49-
# SMELL:
50-
self.create()
51-
with local.cwd(self.sha):
52-
yield
49+
def get_cmd_runner(self, hooks_cmd_runner):
50+
return PrefixedCommandRunner.from_command_runner(
51+
hooks_cmd_runner, self.sha,
52+
)
53+
54+
def require_created(self):
55+
if self.__created:
56+
return
57+
58+
self.create()
59+
self.__created = True
5360

5461
def create(self):
5562
with in_hooks_workspace():
@@ -61,13 +68,42 @@ def create(self):
6168
with self.in_checkout():
6269
local['git']['checkout', self.sha]()
6370

64-
def install(self):
65-
with self.in_checkout():
66-
for language in C.SUPPORTED_LANGUAGES:
67-
if language in self.languages:
68-
languages[language].install_environment()
71+
def require_installed(self, cmd_runner):
72+
if self.__installed:
73+
return
6974

70-
def run_hook(self, hook_id, file_args):
71-
with self.in_checkout():
72-
hook = self.hooks[hook_id]
73-
return languages[hook['language']].run_hook(hook, file_args)
75+
self.install(cmd_runner)
76+
77+
def install(self, cmd_runner):
78+
"""Install the hook repository.
79+
80+
Args:
81+
cmd_runner - A `PrefixedCommandRunner` bound to the hooks workspace
82+
"""
83+
self.require_created()
84+
repo_cmd_runner = self.get_cmd_runner(cmd_runner)
85+
for language in C.SUPPORTED_LANGUAGES:
86+
if language in self.languages:
87+
languages[language].install_environment(repo_cmd_runner)
88+
89+
@contextlib.contextmanager
90+
def in_checkout(self):
91+
self.require_created()
92+
with in_hooks_workspace():
93+
with local.cwd(self.sha):
94+
yield
95+
96+
def run_hook(self, cmd_runner, hook_id, file_args):
97+
"""Run a hook.
98+
99+
Args:
100+
cmd_runner - A `PrefixedCommandRunner` bound to the hooks workspace
101+
hook_id - Id of the hook
102+
file_args - List of files to run
103+
"""
104+
self.require_installed(cmd_runner)
105+
repo_cmd_runner = self.get_cmd_runner(cmd_runner)
106+
hook = self.hooks[hook_id]
107+
return languages[hook['language']].run_hook(
108+
repo_cmd_runner, hook, file_args,
109+
)

0 commit comments

Comments
 (0)