Show More
@@ -153,7 +153,7 b' class MergeResponse(object):' | |||||
153 | u'This pull request cannot be merged because of an unhandled exception. ' |
|
153 | u'This pull request cannot be merged because of an unhandled exception. ' | |
154 | u'{exception}'), |
|
154 | u'{exception}'), | |
155 | MergeFailureReason.MERGE_FAILED: lazy_ugettext( |
|
155 | MergeFailureReason.MERGE_FAILED: lazy_ugettext( | |
156 | u'This pull request cannot be merged because of merge conflicts.'), |
|
156 | u'This pull request cannot be merged because of merge conflicts. {unresolved_files}'), | |
157 | MergeFailureReason.PUSH_FAILED: lazy_ugettext( |
|
157 | MergeFailureReason.PUSH_FAILED: lazy_ugettext( | |
158 | u'This pull request could not be merged because push to ' |
|
158 | u'This pull request could not be merged because push to ' | |
159 | u'target:`{target}@{merge_commit}` failed.'), |
|
159 | u'target:`{target}@{merge_commit}` failed.'), |
@@ -42,7 +42,7 b' from rhodecode.lib.vcs.backends.git.diff' | |||||
42 | from rhodecode.lib.vcs.backends.git.inmemory import GitInMemoryCommit |
|
42 | from rhodecode.lib.vcs.backends.git.inmemory import GitInMemoryCommit | |
43 | from rhodecode.lib.vcs.exceptions import ( |
|
43 | from rhodecode.lib.vcs.exceptions import ( | |
44 | CommitDoesNotExistError, EmptyRepositoryError, |
|
44 | CommitDoesNotExistError, EmptyRepositoryError, | |
45 | RepositoryError, TagAlreadyExistError, TagDoesNotExistError, VCSError) |
|
45 | RepositoryError, TagAlreadyExistError, TagDoesNotExistError, VCSError, UnresolvedFilesInRepo) | |
46 |
|
46 | |||
47 |
|
47 | |||
48 | SHA_PATTERN = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$') |
|
48 | SHA_PATTERN = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$') | |
@@ -826,9 +826,10 b' class GitRepository(BaseRepository):' | |||||
826 | return |
|
826 | return | |
827 |
|
827 | |||
828 | if self.is_empty(): |
|
828 | if self.is_empty(): | |
829 |
# TODO(skreft): do some |
|
829 | # TODO(skreft): do something more robust in this case. | |
830 | raise RepositoryError( |
|
830 | raise RepositoryError( | |
831 | 'Do not know how to merge into empty repositories yet') |
|
831 | 'Do not know how to merge into empty repositories yet') | |
|
832 | unresolved = None | |||
832 |
|
833 | |||
833 | # N.B.(skreft): the --no-ff option is used to enforce the creation of a |
|
834 | # N.B.(skreft): the --no-ff option is used to enforce the creation of a | |
834 | # commit message. We also specify the user who is doing the merge. |
|
835 | # commit message. We also specify the user who is doing the merge. | |
@@ -839,9 +840,18 b' class GitRepository(BaseRepository):' | |||||
839 | try: |
|
840 | try: | |
840 | output = self.run_git_command(cmd, fail_on_stderr=False) |
|
841 | output = self.run_git_command(cmd, fail_on_stderr=False) | |
841 | except RepositoryError: |
|
842 | except RepositoryError: | |
|
843 | files = self.run_git_command(['diff', '--name-only', '--diff-filter', 'U'], | |||
|
844 | fail_on_stderr=False)[0].splitlines() | |||
|
845 | # NOTE(marcink): we add U notation for consistent with HG backend output | |||
|
846 | unresolved = ['U {}'.format(f) for f in files] | |||
|
847 | ||||
842 | # Cleanup any merge leftovers |
|
848 | # Cleanup any merge leftovers | |
843 | self.run_git_command(['merge', '--abort'], fail_on_stderr=False) |
|
849 | self.run_git_command(['merge', '--abort'], fail_on_stderr=False) | |
844 | raise |
|
850 | ||
|
851 | if unresolved: | |||
|
852 | raise UnresolvedFilesInRepo(unresolved) | |||
|
853 | else: | |||
|
854 | raise | |||
845 |
|
855 | |||
846 | def _local_push( |
|
856 | def _local_push( | |
847 | self, source_branch, repository_path, target_branch, |
|
857 | self, source_branch, repository_path, target_branch, | |
@@ -977,8 +987,11 b' class GitRepository(BaseRepository):' | |||||
977 | # the shadow repository. |
|
987 | # the shadow repository. | |
978 | shadow_repo.set_refs('refs/heads/pr-merge', merge_commit_id) |
|
988 | shadow_repo.set_refs('refs/heads/pr-merge', merge_commit_id) | |
979 | merge_ref = Reference('branch', 'pr-merge', merge_commit_id) |
|
989 | merge_ref = Reference('branch', 'pr-merge', merge_commit_id) | |
980 | except RepositoryError: |
|
990 | except RepositoryError as e: | |
981 | log.exception('Failure when doing local merge on git shadow repo') |
|
991 | log.exception('Failure when doing local merge on git shadow repo') | |
|
992 | if isinstance(e, UnresolvedFilesInRepo): | |||
|
993 | metadata['unresolved_files'] = 'file: ' + (', file: '.join(e.args[0])) | |||
|
994 | ||||
982 | merge_possible = False |
|
995 | merge_possible = False | |
983 | merge_failure_reason = MergeFailureReason.MERGE_FAILED |
|
996 | merge_failure_reason = MergeFailureReason.MERGE_FAILED | |
984 |
|
997 |
@@ -42,7 +42,7 b' from rhodecode.lib.vcs.backends.hg.diff ' | |||||
42 | from rhodecode.lib.vcs.backends.hg.inmemory import MercurialInMemoryCommit |
|
42 | from rhodecode.lib.vcs.backends.hg.inmemory import MercurialInMemoryCommit | |
43 | from rhodecode.lib.vcs.exceptions import ( |
|
43 | from rhodecode.lib.vcs.exceptions import ( | |
44 | EmptyRepositoryError, RepositoryError, TagAlreadyExistError, |
|
44 | EmptyRepositoryError, RepositoryError, TagAlreadyExistError, | |
45 | TagDoesNotExistError, CommitDoesNotExistError, SubrepoMergeError) |
|
45 | TagDoesNotExistError, CommitDoesNotExistError, SubrepoMergeError, UnresolvedFilesInRepo) | |
46 | from rhodecode.lib.vcs.compat import configparser |
|
46 | from rhodecode.lib.vcs.compat import configparser | |
47 |
|
47 | |||
48 | hexlify = binascii.hexlify |
|
48 | hexlify = binascii.hexlify | |
@@ -634,6 +634,7 b' class MercurialRepository(BaseRepository' | |||||
634 | # In this case we should force a commit message |
|
634 | # In this case we should force a commit message | |
635 | return source_ref.commit_id, True |
|
635 | return source_ref.commit_id, True | |
636 |
|
636 | |||
|
637 | unresolved = None | |||
637 | if use_rebase: |
|
638 | if use_rebase: | |
638 | try: |
|
639 | try: | |
639 | bookmark_name = 'rcbook%s%s' % (source_ref.commit_id, |
|
640 | bookmark_name = 'rcbook%s%s' % (source_ref.commit_id, | |
@@ -644,17 +645,23 b' class MercurialRepository(BaseRepository' | |||||
644 | self._remote.invalidate_vcs_cache() |
|
645 | self._remote.invalidate_vcs_cache() | |
645 | self._update(bookmark_name, clean=True) |
|
646 | self._update(bookmark_name, clean=True) | |
646 | return self._identify(), True |
|
647 | return self._identify(), True | |
647 | except RepositoryError: |
|
648 | except RepositoryError as e: | |
648 | # The rebase-abort may raise another exception which 'hides' |
|
649 | # The rebase-abort may raise another exception which 'hides' | |
649 | # the original one, therefore we log it here. |
|
650 | # the original one, therefore we log it here. | |
650 | log.exception('Error while rebasing shadow repo during merge.') |
|
651 | log.exception('Error while rebasing shadow repo during merge.') | |
|
652 | if 'unresolved conflicts' in e.message: | |||
|
653 | unresolved = self._remote.get_unresolved_files() | |||
|
654 | log.debug('unresolved files: %s', unresolved) | |||
651 |
|
655 | |||
652 | # Cleanup any rebase leftovers |
|
656 | # Cleanup any rebase leftovers | |
653 | self._remote.invalidate_vcs_cache() |
|
657 | self._remote.invalidate_vcs_cache() | |
654 | self._remote.rebase(abort=True) |
|
658 | self._remote.rebase(abort=True) | |
655 | self._remote.invalidate_vcs_cache() |
|
659 | self._remote.invalidate_vcs_cache() | |
656 | self._remote.update(clean=True) |
|
660 | self._remote.update(clean=True) | |
657 |
|
|
661 | if unresolved: | |
|
662 | raise UnresolvedFilesInRepo(unresolved) | |||
|
663 | else: | |||
|
664 | raise | |||
658 | else: |
|
665 | else: | |
659 | try: |
|
666 | try: | |
660 | self._remote.merge(source_ref.commit_id) |
|
667 | self._remote.merge(source_ref.commit_id) | |
@@ -664,10 +671,20 b' class MercurialRepository(BaseRepository' | |||||
664 | username=safe_str('%s <%s>' % (user_name, user_email))) |
|
671 | username=safe_str('%s <%s>' % (user_name, user_email))) | |
665 | self._remote.invalidate_vcs_cache() |
|
672 | self._remote.invalidate_vcs_cache() | |
666 | return self._identify(), True |
|
673 | return self._identify(), True | |
667 | except RepositoryError: |
|
674 | except RepositoryError as e: | |
|
675 | # The merge-abort may raise another exception which 'hides' | |||
|
676 | # the original one, therefore we log it here. | |||
|
677 | log.exception('Error while merging shadow repo during merge.') | |||
|
678 | if 'unresolved merge conflicts' in e.message: | |||
|
679 | unresolved = self._remote.get_unresolved_files() | |||
|
680 | log.debug('unresolved files: %s', unresolved) | |||
|
681 | ||||
668 | # Cleanup any merge leftovers |
|
682 | # Cleanup any merge leftovers | |
669 | self._remote.update(clean=True) |
|
683 | self._remote.update(clean=True) | |
670 |
|
|
684 | if unresolved: | |
|
685 | raise UnresolvedFilesInRepo(unresolved) | |||
|
686 | else: | |||
|
687 | raise | |||
671 |
|
688 | |||
672 | def _local_close(self, target_ref, user_name, user_email, |
|
689 | def _local_close(self, target_ref, user_name, user_email, | |
673 | source_ref, close_message=''): |
|
690 | source_ref, close_message=''): | |
@@ -810,8 +827,11 b' class MercurialRepository(BaseRepository' | |||||
810 | merge_possible = False |
|
827 | merge_possible = False | |
811 | merge_failure_reason = MergeFailureReason.SUBREPO_MERGE_FAILED |
|
828 | merge_failure_reason = MergeFailureReason.SUBREPO_MERGE_FAILED | |
812 | needs_push = False |
|
829 | needs_push = False | |
813 | except RepositoryError: |
|
830 | except RepositoryError as e: | |
814 | log.exception('Failure when doing local merge on hg shadow repo') |
|
831 | log.exception('Failure when doing local merge on hg shadow repo') | |
|
832 | if isinstance(e, UnresolvedFilesInRepo): | |||
|
833 | metadata['unresolved_files'] = 'file: ' + (', file: '.join(e.args[0])) | |||
|
834 | ||||
815 | merge_possible = False |
|
835 | merge_possible = False | |
816 | merge_failure_reason = MergeFailureReason.MERGE_FAILED |
|
836 | merge_failure_reason = MergeFailureReason.MERGE_FAILED | |
817 | needs_push = False |
|
837 | needs_push = False |
@@ -50,6 +50,10 b' class RepositoryRequirementError(Reposit' | |||||
50 | pass |
|
50 | pass | |
51 |
|
51 | |||
52 |
|
52 | |||
|
53 | class UnresolvedFilesInRepo(RepositoryError): | |||
|
54 | pass | |||
|
55 | ||||
|
56 | ||||
53 | class VCSBackendNotSupportedError(VCSError): |
|
57 | class VCSBackendNotSupportedError(VCSError): | |
54 | """ |
|
58 | """ | |
55 | Exception raised when VCSServer does not support requested backend |
|
59 | Exception raised when VCSServer does not support requested backend |
@@ -1335,6 +1335,7 b' class PullRequestModel(BaseModel):' | |||||
1335 | else: |
|
1335 | else: | |
1336 | possible = pull_request.last_merge_status == MergeFailureReason.NONE |
|
1336 | possible = pull_request.last_merge_status == MergeFailureReason.NONE | |
1337 | metadata = { |
|
1337 | metadata = { | |
|
1338 | 'unresolved_files': '', | |||
1338 | 'target_ref': pull_request.target_ref_parts, |
|
1339 | 'target_ref': pull_request.target_ref_parts, | |
1339 | 'source_ref': pull_request.source_ref_parts, |
|
1340 | 'source_ref': pull_request.source_ref_parts, | |
1340 | } |
|
1341 | } |
@@ -191,7 +191,8 b' class TestPullRequestModel(object):' | |||||
191 |
|
191 | |||
192 | def test_merge_status_known_failure(self, pull_request): |
|
192 | def test_merge_status_known_failure(self, pull_request): | |
193 | self.merge_mock.return_value = MergeResponse( |
|
193 | self.merge_mock.return_value = MergeResponse( | |
194 |
False, False, None, MergeFailureReason.MERGE_FAILED |
|
194 | False, False, None, MergeFailureReason.MERGE_FAILED, | |
|
195 | metadata={'unresolved_files': 'file1'}) | |||
195 |
|
196 | |||
196 | assert pull_request._last_merge_source_rev is None |
|
197 | assert pull_request._last_merge_source_rev is None | |
197 | assert pull_request._last_merge_target_rev is None |
|
198 | assert pull_request._last_merge_target_rev is None | |
@@ -199,7 +200,7 b' class TestPullRequestModel(object):' | |||||
199 |
|
200 | |||
200 | status, msg = PullRequestModel().merge_status(pull_request) |
|
201 | status, msg = PullRequestModel().merge_status(pull_request) | |
201 | assert status is False |
|
202 | assert status is False | |
202 | assert msg == 'This pull request cannot be merged because of merge conflicts.' |
|
203 | assert msg == 'This pull request cannot be merged because of merge conflicts. file1' | |
203 | self.merge_mock.assert_called_with( |
|
204 | self.merge_mock.assert_called_with( | |
204 | self.repo_id, self.workspace_id, |
|
205 | self.repo_id, self.workspace_id, | |
205 | pull_request.target_ref_parts, |
|
206 | pull_request.target_ref_parts, | |
@@ -209,13 +210,12 b' class TestPullRequestModel(object):' | |||||
209 |
|
210 | |||
210 | assert pull_request._last_merge_source_rev == self.source_commit |
|
211 | assert pull_request._last_merge_source_rev == self.source_commit | |
211 | assert pull_request._last_merge_target_rev == self.target_commit |
|
212 | assert pull_request._last_merge_target_rev == self.target_commit | |
212 | assert ( |
|
213 | assert pull_request.last_merge_status is MergeFailureReason.MERGE_FAILED | |
213 | pull_request.last_merge_status is MergeFailureReason.MERGE_FAILED) |
|
|||
214 |
|
214 | |||
215 | self.merge_mock.reset_mock() |
|
215 | self.merge_mock.reset_mock() | |
216 | status, msg = PullRequestModel().merge_status(pull_request) |
|
216 | status, msg = PullRequestModel().merge_status(pull_request) | |
217 | assert status is False |
|
217 | assert status is False | |
218 | assert msg == 'This pull request cannot be merged because of merge conflicts.' |
|
218 | assert msg == 'This pull request cannot be merged because of merge conflicts. ' | |
219 | assert self.merge_mock.called is False |
|
219 | assert self.merge_mock.called is False | |
220 |
|
220 | |||
221 | def test_merge_status_unknown_failure(self, pull_request): |
|
221 | def test_merge_status_unknown_failure(self, pull_request): | |
@@ -518,7 +518,7 b' def test_outdated_comments(' | |||||
518 | (MergeFailureReason.UNKNOWN, |
|
518 | (MergeFailureReason.UNKNOWN, | |
519 | 'This pull request cannot be merged because of an unhandled exception. CRASH'), |
|
519 | 'This pull request cannot be merged because of an unhandled exception. CRASH'), | |
520 | (MergeFailureReason.MERGE_FAILED, |
|
520 | (MergeFailureReason.MERGE_FAILED, | |
521 | 'This pull request cannot be merged because of merge conflicts.'), |
|
521 | 'This pull request cannot be merged because of merge conflicts. CONFLICT_FILE'), | |
522 | (MergeFailureReason.PUSH_FAILED, |
|
522 | (MergeFailureReason.PUSH_FAILED, | |
523 | 'This pull request could not be merged because push to target:`some-repo@merge_commit` failed.'), |
|
523 | 'This pull request could not be merged because push to target:`some-repo@merge_commit` failed.'), | |
524 | (MergeFailureReason.TARGET_IS_NOT_HEAD, |
|
524 | (MergeFailureReason.TARGET_IS_NOT_HEAD, | |
@@ -540,13 +540,15 b' def test_outdated_comments(' | |||||
540 | def test_merge_response_message(mr_type, expected_msg): |
|
540 | def test_merge_response_message(mr_type, expected_msg): | |
541 | merge_ref = Reference('type', 'ref_name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6') |
|
541 | merge_ref = Reference('type', 'ref_name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6') | |
542 | metadata = { |
|
542 | metadata = { | |
|
543 | 'unresolved_files': 'CONFLICT_FILE', | |||
543 | 'exception': "CRASH", |
|
544 | 'exception': "CRASH", | |
544 | 'target': 'some-repo', |
|
545 | 'target': 'some-repo', | |
545 | 'merge_commit': 'merge_commit', |
|
546 | 'merge_commit': 'merge_commit', | |
546 | 'target_ref': merge_ref, |
|
547 | 'target_ref': merge_ref, | |
547 | 'source_ref': merge_ref, |
|
548 | 'source_ref': merge_ref, | |
548 | 'heads': ','.join(['a', 'b', 'c']), |
|
549 | 'heads': ','.join(['a', 'b', 'c']), | |
549 |
'locked_by': 'user:123' |
|
550 | 'locked_by': 'user:123' | |
|
551 | } | |||
550 |
|
552 | |||
551 | merge_response = MergeResponse(True, True, merge_ref, mr_type, metadata=metadata) |
|
553 | merge_response = MergeResponse(True, True, merge_ref, mr_type, metadata=metadata) | |
552 | assert merge_response.merge_status_message == expected_msg |
|
554 | assert merge_response.merge_status_message == expected_msg |
General Comments 0
You need to be logged in to leave comments.
Login now