| 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 master | |
| with repo1.as_cwd(): | |
| repo1_f1.write('parent\n') | |
| git_commit('-am', 'master 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 master | |
| with repo1.as_cwd(): | |
| repo1_f1.write('parent\n') | |
| git_commit('-am', 'master 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:1: Merge conflict string '<<<<<<<' found\n" | |
| "f1:3: Merge conflict string '=======' found\n" | |
| "f1:5: Merge conflict string '>>>>>>>' found\n" | |
| ) | |
| @pytest.mark.parametrize( | |
| 'contents', (b'<<<<<<< HEAD\n', b'=======\n', b'>>>>>>> master\n'), | |
| ) | |
| def test_merge_conflicts_failing(contents, repository_pending_merge): | |
| repository_pending_merge.join('f2').write_binary(contents) | |
| assert main(['f2']) == 1 | |
| @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 | |
| def test_care_when_assumed_merge(tmpdir): | |
| f = tmpdir.join('README.md') | |
| f.write_binary(b'problem\n=======\n') | |
| assert main([str(f.realpath()), '--assume-in-merge']) == 1 | |
| 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', 'master', retcode=None, | |
| ) | |
| msg = f1_is_a_conflict_file.join('.git/worktrees/worktree/MERGE_MSG') | |
| assert msg.exists() | |
| test_merge_conflicts_git(capsys) |