forked from pre-commit/pre-commit-hooks
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheck_merge_conflict_test.py
More file actions
178 lines (150 loc) · 5.28 KB
/
check_merge_conflict_test.py
File metadata and controls
178 lines (150 loc) · 5.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
from __future__ import annotations
import os
import shutil
import pytest
from pre_commit_hooks.check_merge_conflict import main
from pre_commit_hooks.util import cmd_output
from testing.util import get_resource_path
from testing.util import git_commit
@pytest.fixture
def f1_is_a_conflict_file(tmpdir):
# Make a merge conflict
repo1 = tmpdir.join('repo1')
repo1_f1 = repo1.join('f1')
repo2 = tmpdir.join('repo2')
repo2_f1 = repo2.join('f1')
cmd_output('git', 'init', '--', str(repo1))
with repo1.as_cwd():
repo1_f1.ensure()
cmd_output('git', 'add', '.')
git_commit('-m', 'commit1')
cmd_output('git', 'clone', str(repo1), str(repo2))
# Commit in mainline
with repo1.as_cwd():
repo1_f1.write('parent\n')
git_commit('-am', 'mainline commit2')
# Commit in clone and pull
with repo2.as_cwd():
repo2_f1.write('child\n')
git_commit('-am', 'clone commit2')
cmd_output('git', 'pull', '--no-rebase', retcode=None)
# We should end up in a merge conflict!
f1 = repo2_f1.read()
assert f1.startswith(
'<<<<<<< HEAD\n'
'child\n'
'=======\n'
'parent\n'
'>>>>>>>',
) or f1.startswith(
'<<<<<<< HEAD\n'
'child\n'
# diff3 conflict style git merges add this line:
'||||||| merged common ancestors\n'
'=======\n'
'parent\n'
'>>>>>>>',
) or f1.startswith(
# .gitconfig with [pull] rebase = preserve causes a rebase which
# flips parent / child
'<<<<<<< HEAD\n'
'parent\n'
'=======\n'
'child\n'
'>>>>>>>',
)
assert os.path.exists(os.path.join('.git', 'MERGE_MSG'))
yield repo2
@pytest.fixture
def repository_pending_merge(tmpdir):
# Make a (non-conflicting) merge
repo1 = tmpdir.join('repo1')
repo1_f1 = repo1.join('f1')
repo2 = tmpdir.join('repo2')
repo2_f1 = repo2.join('f1')
repo2_f2 = repo2.join('f2')
cmd_output('git', 'init', str(repo1))
with repo1.as_cwd():
repo1_f1.ensure()
cmd_output('git', 'add', '.')
git_commit('-m', 'commit1')
cmd_output('git', 'clone', str(repo1), str(repo2))
# Commit in mainline
with repo1.as_cwd():
repo1_f1.write('parent\n')
git_commit('-am', 'mainline commit2')
# Commit in clone and pull without committing
with repo2.as_cwd():
repo2_f2.write('child\n')
cmd_output('git', 'add', '.')
git_commit('-m', 'clone commit2')
cmd_output('git', 'pull', '--no-commit', '--no-rebase')
# We should end up in a pending merge
assert repo2_f1.read() == 'parent\n'
assert repo2_f2.read() == 'child\n'
assert os.path.exists(os.path.join('.git', 'MERGE_HEAD'))
yield repo2
@pytest.mark.usefixtures('f1_is_a_conflict_file')
def test_merge_conflicts_git(capsys):
assert main(['f1']) == 1
out, _ = capsys.readouterr()
assert out == (
"f1:5: Merge conflict string '>>>>>>>' found\n"
)
@pytest.mark.parametrize(
# Individual markers are not actually merge conflicts, need 3 markers
# to mark a real conflict
'contents, expected_retcode',
(
(b'<<<<<<< ', 0),
(b'=======', 0),
(b'>>>>>>> ', 0),
# Real conflict marker
(b'<<<<<<< HEAD\n=======\n>>>>>>> branch\n', 1),
# Allow for the possibility of an .rst file with a =======
# inside a conflict marker
(b'<<<<<<< HEAD\n=======\n=======\n>>>>>>> branch\n', 1),
),
)
def test_merge_conflicts_with_rst(
contents, expected_retcode, repository_pending_merge,
):
repository_pending_merge.join('f2.rst').write_binary(contents)
assert main(['f2.rst']) == expected_retcode
@pytest.mark.parametrize(
'contents', (b'# <<<<<<< HEAD\n', b'# =======\n', b'import mod', b''),
)
def test_merge_conflicts_ok(contents, f1_is_a_conflict_file):
f1_is_a_conflict_file.join('f1').write_binary(contents)
assert main(['f1']) == 0
@pytest.mark.usefixtures('f1_is_a_conflict_file')
def test_ignores_binary_files():
shutil.copy(get_resource_path('img1.jpg'), 'f1')
assert main(['f1']) == 0
def test_does_not_care_when_not_in_a_merge(tmpdir):
f = tmpdir.join('README.md')
f.write_binary(b'problem\n=======\n')
assert main([str(f.realpath())]) == 0
@pytest.mark.parametrize(
'contents, expected_retcode',
(
# Not a complete conflict marker
(b'=======', 0),
# Complete conflict marker
(b'<<<<<<< HEAD\nproblem\n=======\n>>>>>>> branch\n', 1),
),
)
def test_care_when_assumed_merge(contents, expected_retcode, tmpdir):
f = tmpdir.join('README.md')
f.write_binary(contents)
assert main([str(f.realpath()), '--assume-in-merge']) == expected_retcode
def test_worktree_merge_conflicts(f1_is_a_conflict_file, tmpdir, capsys):
worktree = tmpdir.join('worktree')
cmd_output('git', 'worktree', 'add', str(worktree))
with worktree.as_cwd():
cmd_output(
'git', 'pull', '--no-rebase', 'origin', 'HEAD', retcode=None,
)
msg = f1_is_a_conflict_file.join('.git/worktrees/worktree/MERGE_MSG')
assert msg.exists()
test_merge_conflicts_git(capsys)