Skip to content

Commit c3c98af

Browse files
committed
Support pre-commit from inside submodules
1 parent d6cf625 commit c3c98af

File tree

7 files changed

+92
-26
lines changed

7 files changed

+92
-26
lines changed

pre_commit/git.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,18 @@ def get_root():
2424
)
2525

2626

27+
def get_git_dir(git_root):
28+
return os.path.normpath(os.path.join(
29+
git_root,
30+
cmd_output('git', 'rev-parse', '--git-dir', cwd=git_root)[1].strip(),
31+
))
32+
33+
2734
def is_in_merge_conflict():
35+
git_dir = get_git_dir('.')
2836
return (
29-
os.path.exists(os.path.join('.git', 'MERGE_MSG')) and
30-
os.path.exists(os.path.join('.git', 'MERGE_HEAD'))
37+
os.path.exists(os.path.join(git_dir, 'MERGE_MSG')) and
38+
os.path.exists(os.path.join(git_dir, 'MERGE_HEAD'))
3139
)
3240

3341

@@ -46,7 +54,7 @@ def get_conflicted_files():
4654
logger.info('Checking merge-conflict files only.')
4755
# Need to get the conflicted files from the MERGE_MSG because they could
4856
# have resolved the conflict by choosing one side or the other
49-
merge_msg = open(os.path.join('.git', 'MERGE_MSG')).read()
57+
merge_msg = open(os.path.join(get_git_dir('.'), 'MERGE_MSG')).read()
5058
merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)
5159

5260
# This will get the rest of the changes made after the merge.

pre_commit/main.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@
2525
# https://github.com/pre-commit/pre-commit/issues/300
2626
# In git 2.6.3 (maybe others), git exports this while running pre-commit hooks
2727
os.environ.pop('GIT_WORK_TREE', None)
28+
# In git 1.9.1 (maybe others), git exports these while running pre-commit hooks
29+
# in submodules. In the general case this causes problems.
30+
# These are covered by test_install_in_submodule_and_run
31+
# Causes git clone to clone wrong thing
32+
os.environ.pop('GIT_DIR', None)
33+
# Causes 'error invalid object ...' during commit
34+
os.environ.pop('GIT_INDEX_FILE', None)
2835

2936

3037
def main(argv=None):

pre_commit/runner.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ def create(cls):
3030
os.chdir(root)
3131
return cls(root)
3232

33+
@cached_property
34+
def git_dir(self):
35+
return git.get_git_dir(self.git_root)
36+
3337
@cached_property
3438
def config_file_path(self):
3539
return os.path.join(self.git_root, C.CONFIG_FILE)
@@ -44,7 +48,7 @@ def repositories(self):
4448
return repositories
4549

4650
def get_hook_path(self, hook_type):
47-
return os.path.join(self.git_root, '.git', 'hooks', hook_type)
51+
return os.path.join(self.git_dir, 'hooks', hook_type)
4852

4953
@cached_property
5054
def pre_commit_path(self):

tests/commands/install_uninstall_test.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,21 @@ def test_install_pre_commit_and_run(tempdir_factory):
170170
assert NORMAL_PRE_COMMIT_RUN.match(output)
171171

172172

173+
def test_install_in_submodule_and_run(tempdir_factory):
174+
src_path = make_consuming_repo(tempdir_factory, 'script_hooks_repo')
175+
parent_path = git_dir(tempdir_factory)
176+
with cwd(parent_path):
177+
cmd_output('git', 'submodule', 'add', src_path, 'sub')
178+
cmd_output('git', 'commit', '-am', 'foo')
179+
180+
sub_pth = os.path.join(parent_path, 'sub')
181+
with cwd(sub_pth):
182+
assert install(Runner(sub_pth)) == 0
183+
ret, output = _get_commit_output(tempdir_factory)
184+
assert ret == 0
185+
assert NORMAL_PRE_COMMIT_RUN.match(output)
186+
187+
173188
def test_install_idempotent(tempdir_factory):
174189
path = make_consuming_repo(tempdir_factory, 'script_hooks_repo')
175190
with cwd(path):

tests/conftest.py

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from pre_commit.store import Store
1616
from pre_commit.util import cmd_output
1717
from pre_commit.util import cwd
18+
from testing.fixtures import git_dir
1819
from testing.fixtures import make_consuming_repo
1920

2021

@@ -40,6 +41,26 @@ def in_tmpdir(tempdir_factory):
4041
yield path
4142

4243

44+
def _make_conflict():
45+
cmd_output('git', 'checkout', 'origin/master', '-b', 'foo')
46+
with io.open('conflict_file', 'w') as conflict_file:
47+
conflict_file.write('herp\nderp\n')
48+
cmd_output('git', 'add', 'conflict_file')
49+
with io.open('foo_only_file', 'w') as foo_only_file:
50+
foo_only_file.write('foo')
51+
cmd_output('git', 'add', 'foo_only_file')
52+
cmd_output('git', 'commit', '-m', 'conflict_file')
53+
cmd_output('git', 'checkout', 'origin/master', '-b', 'bar')
54+
with io.open('conflict_file', 'w') as conflict_file:
55+
conflict_file.write('harp\nddrp\n')
56+
cmd_output('git', 'add', 'conflict_file')
57+
with io.open('bar_only_file', 'w') as bar_only_file:
58+
bar_only_file.write('bar')
59+
cmd_output('git', 'add', 'bar_only_file')
60+
cmd_output('git', 'commit', '-m', 'conflict_file')
61+
cmd_output('git', 'merge', 'foo', retcode=None)
62+
63+
4364
@pytest.yield_fixture
4465
def in_merge_conflict(tempdir_factory):
4566
path = make_consuming_repo(tempdir_factory, 'script_hooks_repo')
@@ -51,26 +72,23 @@ def in_merge_conflict(tempdir_factory):
5172
conflict_path = tempdir_factory.get()
5273
cmd_output('git', 'clone', path, conflict_path)
5374
with cwd(conflict_path):
54-
cmd_output('git', 'checkout', 'origin/master', '-b', 'foo')
55-
with io.open('conflict_file', 'w') as conflict_file:
56-
conflict_file.write('herp\nderp\n')
57-
cmd_output('git', 'add', 'conflict_file')
58-
with io.open('foo_only_file', 'w') as foo_only_file:
59-
foo_only_file.write('foo')
60-
cmd_output('git', 'add', 'foo_only_file')
61-
cmd_output('git', 'commit', '-m', 'conflict_file')
62-
cmd_output('git', 'checkout', 'origin/master', '-b', 'bar')
63-
with io.open('conflict_file', 'w') as conflict_file:
64-
conflict_file.write('harp\nddrp\n')
65-
cmd_output('git', 'add', 'conflict_file')
66-
with io.open('bar_only_file', 'w') as bar_only_file:
67-
bar_only_file.write('bar')
68-
cmd_output('git', 'add', 'bar_only_file')
69-
cmd_output('git', 'commit', '-m', 'conflict_file')
70-
cmd_output('git', 'merge', 'foo', retcode=None)
75+
_make_conflict()
7176
yield os.path.join(conflict_path)
7277

7378

79+
@pytest.yield_fixture
80+
def in_conflicting_submodule(tempdir_factory):
81+
git_dir_1 = git_dir(tempdir_factory)
82+
git_dir_2 = git_dir(tempdir_factory)
83+
with cwd(git_dir_2):
84+
cmd_output('git', 'commit', '--allow-empty', '-m', 'init!')
85+
with cwd(git_dir_1):
86+
cmd_output('git', 'submodule', 'add', git_dir_2, 'sub')
87+
with cwd(os.path.join(git_dir_1, 'sub')):
88+
_make_conflict()
89+
yield
90+
91+
7492
@pytest.yield_fixture(scope='session', autouse=True)
7593
def dont_write_to_home_directory():
7694
"""pre_commit.store.Store will by default write to the home directory

tests/git_test.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ def test_is_in_merge_conflict(in_merge_conflict):
4343
assert git.is_in_merge_conflict() is True
4444

4545

46+
def test_is_in_merge_conflict_submodule(in_conflicting_submodule):
47+
assert git.is_in_merge_conflict() is True
48+
49+
4650
def test_cherry_pick_conflict(in_merge_conflict):
4751
cmd_output('git', 'merge', '--abort')
4852
foo_ref = cmd_output('git', 'rev-parse', 'foo')[1].strip()
@@ -111,6 +115,11 @@ def test_get_conflicted_files(in_merge_conflict):
111115
assert ret == set(('conflict_file', 'other_file'))
112116

113117

118+
def test_get_conflicted_files_in_submodule(in_conflicting_submodule):
119+
resolve_conflict()
120+
assert set(git.get_conflicted_files()) == set(('conflict_file',))
121+
122+
114123
def test_get_conflicted_files_unstaged_files(in_merge_conflict):
115124
# If they for whatever reason did pre-commit run --no-stash during a
116125
# conflict

tests/runner_test.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import pre_commit.constants as C
88
from pre_commit.ordereddict import OrderedDict
99
from pre_commit.runner import Runner
10+
from pre_commit.util import cmd_output
1011
from pre_commit.util import cwd
1112
from testing.fixtures import add_config_to_repo
1213
from testing.fixtures import git_dir
@@ -79,15 +80,19 @@ def test_local_hooks(tempdir_factory, mock_out_store_directory):
7980
assert len(runner.repositories[0].hooks) == 2
8081

8182

82-
def test_pre_commit_path():
83-
runner = Runner(os.path.join('foo', 'bar'))
84-
expected_path = os.path.join('foo', 'bar', '.git', 'hooks', 'pre-commit')
83+
def test_pre_commit_path(in_tmpdir):
84+
path = os.path.join('foo', 'bar')
85+
cmd_output('git', 'init', path)
86+
runner = Runner(path)
87+
expected_path = os.path.join(path, '.git', 'hooks', 'pre-commit')
8588
assert runner.pre_commit_path == expected_path
8689

8790

8891
def test_pre_push_path():
89-
runner = Runner(os.path.join('foo', 'bar'))
90-
expected_path = os.path.join('foo', 'bar', '.git', 'hooks', 'pre-push')
92+
path = os.path.join('foo', 'bar')
93+
cmd_output('git', 'init', path)
94+
runner = Runner(path)
95+
expected_path = os.path.join(path, '.git', 'hooks', 'pre-push')
9196
assert runner.pre_push_path == expected_path
9297

9398

0 commit comments

Comments
 (0)