##// END OF EJS Templates
pull-requests: introduce operation state for pull requests to prevent from...
marcink -
r3371:e7214a9f default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -0,0 +1,37 b''
1 import logging
2
3 from sqlalchemy import *
4
5 from rhodecode.model import meta
6 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
7
8 log = logging.getLogger(__name__)
9
10
11 def upgrade(migrate_engine):
12 """
13 Upgrade operations go here.
14 Don't create your own engine; bind migrate_engine to your metadata
15 """
16 _reset_base(migrate_engine)
17 from rhodecode.lib.dbmigrate.schema import db_4_13_0_0 as db
18
19 pull_request = db.PullRequest.__table__
20 pull_request_version = db.PullRequestVersion.__table__
21
22 repo_state_1 = Column("pull_request_state", String(255), nullable=True)
23 repo_state_1.create(table=pull_request)
24
25 repo_state_2 = Column("pull_request_state", String(255), nullable=True)
26 repo_state_2.create(table=pull_request_version)
27
28 fixups(db, meta.Session)
29
30
31 def downgrade(migrate_engine):
32 meta = MetaData()
33 meta.bind = migrate_engine
34
35
36 def fixups(models, _SESSION):
37 pass
@@ -0,0 +1,41 b''
1 import logging
2
3 from sqlalchemy import *
4
5 from rhodecode.model import meta
6 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
7
8 log = logging.getLogger(__name__)
9
10
11 def upgrade(migrate_engine):
12 """
13 Upgrade operations go here.
14 Don't create your own engine; bind migrate_engine to your metadata
15 """
16 _reset_base(migrate_engine)
17 from rhodecode.lib.dbmigrate.schema import db_4_16_0_0 as db
18
19 fixups(db, meta.Session)
20
21
22 def downgrade(migrate_engine):
23 meta = MetaData()
24 meta.bind = migrate_engine
25
26
27 def fixups(models, _SESSION):
28 # move the builtin token to external tokens
29
30 log.info('Updating pull request pull_request_state to %s',
31 models.PullRequest.STATE_CREATED)
32 qry = _SESSION().query(models.PullRequest)
33 qry.update({"pull_request_state": models.PullRequest.STATE_CREATED})
34 _SESSION().commit()
35
36 log.info('Updating pull_request_version pull_request_state to %s',
37 models.PullRequest.STATE_CREATED)
38 qry = _SESSION().query(models.PullRequestVersion)
39 qry.update({"pull_request_state": models.PullRequest.STATE_CREATED})
40 _SESSION().commit()
41
@@ -18,12 +18,6 b''
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
22
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
26
27 import os
21 import os
28 import sys
22 import sys
29 import platform
23 import platform
@@ -51,7 +45,7 b' PYRAMID_SETTINGS = {}'
51 EXTENSIONS = {}
45 EXTENSIONS = {}
52
46
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
47 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 91 # defines current db version for migrations
48 __dbversion__ = 93 # defines current db version for migrations
55 __platform__ = platform.system()
49 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
50 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
51 __author__ = 'RhodeCode GmbH'
@@ -65,6 +65,7 b' class TestGetPullRequest(object):'
65 'title': pull_request.title,
65 'title': pull_request.title,
66 'description': pull_request.description,
66 'description': pull_request.description,
67 'status': pull_request.status,
67 'status': pull_request.status,
68 'state': pull_request.pull_request_state,
68 'created_on': pull_request.created_on,
69 'created_on': pull_request.created_on,
69 'updated_on': pull_request.updated_on,
70 'updated_on': pull_request.updated_on,
70 'commit_ids': pull_request.revisions,
71 'commit_ids': pull_request.revisions,
@@ -29,11 +29,10 b' from rhodecode.api.tests.utils import ('
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestMergePullRequest(object):
31 class TestMergePullRequest(object):
32
32 @pytest.mark.backends("git", "hg")
33 @pytest.mark.backends("git", "hg")
33 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
34 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
34 pull_request = pr_util.create_pull_request(mergeable=True)
35 pull_request = pr_util.create_pull_request(mergeable=True)
35 author = pull_request.user_id
36 repo = pull_request.target_repo.repo_id
37 pull_request_id = pull_request.pull_request_id
36 pull_request_id = pull_request.pull_request_id
38 pull_request_repo = pull_request.target_repo.repo_name
37 pull_request_repo = pull_request.target_repo.repo_name
39
38
@@ -46,8 +45,7 b' class TestMergePullRequest(object):'
46
45
47 # The above api call detaches the pull request DB object from the
46 # The above api call detaches the pull request DB object from the
48 # session because of an unconditional transaction rollback in our
47 # session because of an unconditional transaction rollback in our
49 # middleware. Therefore we need to add it back here if we want to use
48 # middleware. Therefore we need to add it back here if we want to use it.
50 # it.
51 Session().add(pull_request)
49 Session().add(pull_request)
52
50
53 expected = 'merge not possible for following reasons: ' \
51 expected = 'merge not possible for following reasons: ' \
@@ -55,6 +53,29 b' class TestMergePullRequest(object):'
55 assert_error(id_, expected, given=response.body)
53 assert_error(id_, expected, given=response.body)
56
54
57 @pytest.mark.backends("git", "hg")
55 @pytest.mark.backends("git", "hg")
56 def test_api_merge_pull_request_merge_failed_disallowed_state(
57 self, pr_util, no_notifications):
58 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
59 pull_request_id = pull_request.pull_request_id
60 pull_request_repo = pull_request.target_repo.repo_name
61
62 pr = PullRequest.get(pull_request_id)
63 pr.pull_request_state = pull_request.STATE_UPDATING
64 Session().add(pr)
65 Session().commit()
66
67 id_, params = build_data(
68 self.apikey, 'merge_pull_request',
69 repoid=pull_request_repo,
70 pullrequestid=pull_request_id)
71
72 response = api_call(self.app, params)
73 expected = 'Operation forbidden because pull request is in state {}, '\
74 'only state {} is allowed.'.format(PullRequest.STATE_UPDATING,
75 PullRequest.STATE_CREATED)
76 assert_error(id_, expected, given=response.body)
77
78 @pytest.mark.backends("git", "hg")
58 def test_api_merge_pull_request(self, pr_util, no_notifications):
79 def test_api_merge_pull_request(self, pr_util, no_notifications):
59 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
80 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
60 author = pull_request.user_id
81 author = pull_request.user_id
@@ -123,8 +144,7 b' class TestMergePullRequest(object):'
123 assert_error(id_, expected, given=response.body)
144 assert_error(id_, expected, given=response.body)
124
145
125 @pytest.mark.backends("git", "hg")
146 @pytest.mark.backends("git", "hg")
126 def test_api_merge_pull_request_non_admin_with_userid_error(self,
147 def test_api_merge_pull_request_non_admin_with_userid_error(self, pr_util):
127 pr_util):
128 pull_request = pr_util.create_pull_request(mergeable=True)
148 pull_request = pr_util.create_pull_request(mergeable=True)
129 id_, params = build_data(
149 id_, params = build_data(
130 self.apikey_regular, 'merge_pull_request',
150 self.apikey_regular, 'merge_pull_request',
@@ -32,7 +32,7 b' from rhodecode.lib.base import vcs_opera'
32 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.lib.utils2 import str2bool
33 from rhodecode.model.changeset_status import ChangesetStatusModel
33 from rhodecode.model.changeset_status import ChangesetStatusModel
34 from rhodecode.model.comment import CommentsModel
34 from rhodecode.model.comment import CommentsModel
35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment, PullRequest
36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
37 from rhodecode.model.settings import SettingsModel
37 from rhodecode.model.settings import SettingsModel
38 from rhodecode.model.validation_schema import Invalid
38 from rhodecode.model.validation_schema import Invalid
@@ -128,11 +128,15 b' def get_pull_request(request, apiuser, p'
128 else:
128 else:
129 repo = pull_request.target_repo
129 repo = pull_request.target_repo
130
130
131 if not PullRequestModel().check_user_read(
131 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
132 pull_request, apiuser, api=True):
133 raise JSONRPCError('repository `%s` or pull request `%s` '
132 raise JSONRPCError('repository `%s` or pull request `%s` '
134 'does not exist' % (repoid, pullrequestid))
133 'does not exist' % (repoid, pullrequestid))
135 data = pull_request.get_api_data()
134
135 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
136 # otherwise we can lock the repo on calculation of merge state while update/merge
137 # is happening.
138 merge_state = pull_request.pull_request_state == pull_request.STATE_CREATED
139 data = pull_request.get_api_data(with_merge_state=merge_state)
136 return data
140 return data
137
141
138
142
@@ -283,8 +287,16 b' def merge_pull_request('
283 else:
287 else:
284 raise JSONRPCError('userid is not the same as your user')
288 raise JSONRPCError('userid is not the same as your user')
285
289
286 check = MergeCheck.validate(
290 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
287 pull_request, auth_user=apiuser, translator=request.translate)
291 raise JSONRPCError(
292 'Operation forbidden because pull request is in state {}, '
293 'only state {} is allowed.'.format(
294 pull_request.pull_request_state, PullRequest.STATE_CREATED))
295
296 with pull_request.set_state(PullRequest.STATE_UPDATING):
297 check = MergeCheck.validate(
298 pull_request, auth_user=apiuser,
299 translator=request.translate)
288 merge_possible = not check.failed
300 merge_possible = not check.failed
289
301
290 if not merge_possible:
302 if not merge_possible:
@@ -302,8 +314,9 b' def merge_pull_request('
302 request.environ, repo_name=target_repo.repo_name,
314 request.environ, repo_name=target_repo.repo_name,
303 username=apiuser.username, action='push',
315 username=apiuser.username, action='push',
304 scm=target_repo.repo_type)
316 scm=target_repo.repo_type)
305 merge_response = PullRequestModel().merge_repo(
317 with pull_request.set_state(PullRequest.STATE_UPDATING):
306 pull_request, apiuser, extras=extras)
318 merge_response = PullRequestModel().merge_repo(
319 pull_request, apiuser, extras=extras)
307 if merge_response.executed:
320 if merge_response.executed:
308 PullRequestModel().close_pull_request(
321 PullRequestModel().close_pull_request(
309 pull_request.pull_request_id, apiuser)
322 pull_request.pull_request_id, apiuser)
@@ -829,11 +842,18 b' def update_pull_request('
829
842
830 commit_changes = {"added": [], "common": [], "removed": []}
843 commit_changes = {"added": [], "common": [], "removed": []}
831 if str2bool(Optional.extract(update_commits)):
844 if str2bool(Optional.extract(update_commits)):
832 if PullRequestModel().has_valid_update_type(pull_request):
845
833 update_response = PullRequestModel().update_commits(
846 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
834 pull_request)
847 raise JSONRPCError(
835 commit_changes = update_response.changes or commit_changes
848 'Operation forbidden because pull request is in state {}, '
836 Session().commit()
849 'only state {} is allowed.'.format(
850 pull_request.pull_request_state, PullRequest.STATE_CREATED))
851
852 with pull_request.set_state(PullRequest.STATE_UPDATING):
853 if PullRequestModel().has_valid_update_type(pull_request):
854 update_response = PullRequestModel().update_commits(pull_request)
855 commit_changes = update_response.changes or commit_changes
856 Session().commit()
837
857
838 reviewers_changes = {"added": [], "removed": []}
858 reviewers_changes = {"added": [], "removed": []}
839 if reviewers:
859 if reviewers:
@@ -655,20 +655,20 b' class TestPullrequestsView(object):'
655
655
656 # create pr from a in source to A in target
656 # create pr from a in source to A in target
657 pull_request = PullRequest()
657 pull_request = PullRequest()
658
658 pull_request.source_repo = source
659 pull_request.source_repo = source
659 # TODO: johbo: Make sure that we write the source ref this way!
660 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
660 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
661 branch=backend.default_branch_name, commit_id=commit_ids['change'])
661 branch=backend.default_branch_name, commit_id=commit_ids['change'])
662
662 pull_request.target_repo = target
663 pull_request.target_repo = target
663
664 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
664 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
665 branch=backend.default_branch_name,
665 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
666 commit_id=commit_ids['ancestor'])
666
667 pull_request.revisions = [commit_ids['change']]
667 pull_request.revisions = [commit_ids['change']]
668 pull_request.title = u"Test"
668 pull_request.title = u"Test"
669 pull_request.description = u"Description"
669 pull_request.description = u"Description"
670 pull_request.author = UserModel().get_by_username(
670 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
671 TEST_USER_ADMIN_LOGIN)
671 pull_request.pull_request_state = PullRequest.STATE_CREATED
672 Session().add(pull_request)
672 Session().add(pull_request)
673 Session().commit()
673 Session().commit()
674 pull_request_id = pull_request.pull_request_id
674 pull_request_id = pull_request.pull_request_id
@@ -679,23 +679,21 b' class TestPullrequestsView(object):'
679 # update PR
679 # update PR
680 self.app.post(
680 self.app.post(
681 route_path('pullrequest_update',
681 route_path('pullrequest_update',
682 repo_name=target.repo_name,
682 repo_name=target.repo_name, pull_request_id=pull_request_id),
683 pull_request_id=pull_request_id),
683 params={'update_commits': 'true', 'csrf_token': csrf_token})
684 params={'update_commits': 'true',
684
685 'csrf_token': csrf_token})
685 response = self.app.get(
686 route_path('pullrequest_show',
687 repo_name=target.repo_name,
688 pull_request_id=pull_request.pull_request_id))
689
690 assert response.status_int == 200
691 assert 'Pull request updated to' in response.body
692 assert 'with 1 added, 0 removed commits.' in response.body
686
693
687 # check that we have now both revisions
694 # check that we have now both revisions
688 pull_request = PullRequest.get(pull_request_id)
695 pull_request = PullRequest.get(pull_request_id)
689 assert pull_request.revisions == [
696 assert pull_request.revisions == [commit_ids['change-2'], commit_ids['change']]
690 commit_ids['change-2'], commit_ids['change']]
691
692 # TODO: johbo: this should be a test on its own
693 response = self.app.get(route_path(
694 'pullrequest_new',
695 repo_name=target.repo_name))
696 assert response.status_int == 200
697 assert 'Pull request updated to' in response.body
698 assert 'with 1 added, 0 removed commits.' in response.body
699
697
700 def test_update_target_revision(self, backend, csrf_token):
698 def test_update_target_revision(self, backend, csrf_token):
701 commits = [
699 commits = [
@@ -710,21 +708,21 b' class TestPullrequestsView(object):'
710
708
711 # create pr from a in source to A in target
709 # create pr from a in source to A in target
712 pull_request = PullRequest()
710 pull_request = PullRequest()
711
713 pull_request.source_repo = source
712 pull_request.source_repo = source
714 # TODO: johbo: Make sure that we write the source ref this way!
715 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
713 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
716 branch=backend.default_branch_name, commit_id=commit_ids['change'])
714 branch=backend.default_branch_name, commit_id=commit_ids['change'])
715
717 pull_request.target_repo = target
716 pull_request.target_repo = target
718 # TODO: johbo: Target ref should be branch based, since tip can jump
719 # from branch to branch
720 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
717 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
721 branch=backend.default_branch_name,
718 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
722 commit_id=commit_ids['ancestor'])
719
723 pull_request.revisions = [commit_ids['change']]
720 pull_request.revisions = [commit_ids['change']]
724 pull_request.title = u"Test"
721 pull_request.title = u"Test"
725 pull_request.description = u"Description"
722 pull_request.description = u"Description"
726 pull_request.author = UserModel().get_by_username(
723 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
727 TEST_USER_ADMIN_LOGIN)
724 pull_request.pull_request_state = PullRequest.STATE_CREATED
725
728 Session().add(pull_request)
726 Session().add(pull_request)
729 Session().commit()
727 Session().commit()
730 pull_request_id = pull_request.pull_request_id
728 pull_request_id = pull_request.pull_request_id
@@ -737,23 +735,21 b' class TestPullrequestsView(object):'
737 # update PR
735 # update PR
738 self.app.post(
736 self.app.post(
739 route_path('pullrequest_update',
737 route_path('pullrequest_update',
740 repo_name=target.repo_name,
738 repo_name=target.repo_name,
741 pull_request_id=pull_request_id),
739 pull_request_id=pull_request_id),
742 params={'update_commits': 'true',
740 params={'update_commits': 'true', 'csrf_token': csrf_token},
743 'csrf_token': csrf_token},
744 status=200)
741 status=200)
745
742
746 # check that we have now both revisions
743 # check that we have now both revisions
747 pull_request = PullRequest.get(pull_request_id)
744 pull_request = PullRequest.get(pull_request_id)
748 assert pull_request.revisions == [commit_ids['change-rebased']]
745 assert pull_request.revisions == [commit_ids['change-rebased']]
749 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
746 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
750 branch=backend.default_branch_name,
747 branch=backend.default_branch_name, commit_id=commit_ids['ancestor-new'])
751 commit_id=commit_ids['ancestor-new'])
752
748
753 # TODO: johbo: This should be a test on its own
749 response = self.app.get(
754 response = self.app.get(route_path(
750 route_path('pullrequest_show',
755 'pullrequest_new',
751 repo_name=target.repo_name,
756 repo_name=target.repo_name))
752 pull_request_id=pull_request.pull_request_id))
757 assert response.status_int == 200
753 assert response.status_int == 200
758 assert 'Pull request updated to' in response.body
754 assert 'Pull request updated to' in response.body
759 assert 'with 1 added, 1 removed commits.' in response.body
755 assert 'with 1 added, 1 removed commits.' in response.body
@@ -775,17 +771,14 b' class TestPullrequestsView(object):'
775 # create pr from a in source to A in target
771 # create pr from a in source to A in target
776 pull_request = PullRequest()
772 pull_request = PullRequest()
777 pull_request.source_repo = source
773 pull_request.source_repo = source
778 # TODO: johbo: Make sure that we write the source ref this way!
774
779 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
775 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
780 branch=backend.default_branch_name,
776 branch=backend.default_branch_name,
781 commit_id=commit_ids['master-commit-3-change-2'])
777 commit_id=commit_ids['master-commit-3-change-2'])
782
778
783 pull_request.target_repo = target
779 pull_request.target_repo = target
784 # TODO: johbo: Target ref should be branch based, since tip can jump
785 # from branch to branch
786 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
780 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
787 branch=backend.default_branch_name,
781 branch=backend.default_branch_name, commit_id=commit_ids['feat-commit-2'])
788 commit_id=commit_ids['feat-commit-2'])
789
782
790 pull_request.revisions = [
783 pull_request.revisions = [
791 commit_ids['feat-commit-1'],
784 commit_ids['feat-commit-1'],
@@ -793,8 +786,8 b' class TestPullrequestsView(object):'
793 ]
786 ]
794 pull_request.title = u"Test"
787 pull_request.title = u"Test"
795 pull_request.description = u"Description"
788 pull_request.description = u"Description"
796 pull_request.author = UserModel().get_by_username(
789 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
797 TEST_USER_ADMIN_LOGIN)
790 pull_request.pull_request_state = PullRequest.STATE_CREATED
798 Session().add(pull_request)
791 Session().add(pull_request)
799 Session().commit()
792 Session().commit()
800 pull_request_id = pull_request.pull_request_id
793 pull_request_id = pull_request.pull_request_id
@@ -810,13 +803,10 b' class TestPullrequestsView(object):'
810 route_path('pullrequest_update',
803 route_path('pullrequest_update',
811 repo_name=target.repo_name,
804 repo_name=target.repo_name,
812 pull_request_id=pull_request_id),
805 pull_request_id=pull_request_id),
813 params={'update_commits': 'true',
806 params={'update_commits': 'true', 'csrf_token': csrf_token},
814 'csrf_token': csrf_token},
815 status=200)
807 status=200)
816
808
817 response = self.app.get(route_path(
809 response = self.app.get(route_path('pullrequest_new', repo_name=target.repo_name))
818 'pullrequest_new',
819 repo_name=target.repo_name))
820 assert response.status_int == 200
810 assert response.status_int == 200
821 response.mustcontain('Pull request updated to')
811 response.mustcontain('Pull request updated to')
822 response.mustcontain('with 0 added, 0 removed commits.')
812 response.mustcontain('with 0 added, 0 removed commits.')
@@ -836,21 +826,17 b' class TestPullrequestsView(object):'
836 # create pr from a in source to A in target
826 # create pr from a in source to A in target
837 pull_request = PullRequest()
827 pull_request = PullRequest()
838 pull_request.source_repo = source
828 pull_request.source_repo = source
839 # TODO: johbo: Make sure that we write the source ref this way!
829
840 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
830 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
841 branch=backend.default_branch_name,
831 branch=backend.default_branch_name, commit_id=commit_ids['change'])
842 commit_id=commit_ids['change'])
843 pull_request.target_repo = target
832 pull_request.target_repo = target
844 # TODO: johbo: Target ref should be branch based, since tip can jump
845 # from branch to branch
846 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
833 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
847 branch=backend.default_branch_name,
834 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
848 commit_id=commit_ids['ancestor'])
849 pull_request.revisions = [commit_ids['change']]
835 pull_request.revisions = [commit_ids['change']]
850 pull_request.title = u"Test"
836 pull_request.title = u"Test"
851 pull_request.description = u"Description"
837 pull_request.description = u"Description"
852 pull_request.author = UserModel().get_by_username(
838 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
853 TEST_USER_ADMIN_LOGIN)
839 pull_request.pull_request_state = PullRequest.STATE_CREATED
854 Session().add(pull_request)
840 Session().add(pull_request)
855 Session().commit()
841 Session().commit()
856 pull_request_id = pull_request.pull_request_id
842 pull_request_id = pull_request.pull_request_id
@@ -863,10 +849,8 b' class TestPullrequestsView(object):'
863 # update PR
849 # update PR
864 self.app.post(
850 self.app.post(
865 route_path('pullrequest_update',
851 route_path('pullrequest_update',
866 repo_name=target.repo_name,
852 repo_name=target.repo_name, pull_request_id=pull_request_id),
867 pull_request_id=pull_request_id),
853 params={'update_commits': 'true', 'csrf_token': csrf_token},
868 params={'update_commits': 'true',
869 'csrf_token': csrf_token},
870 status=200)
854 status=200)
871
855
872 # Expect the target reference to be updated correctly
856 # Expect the target reference to be updated correctly
@@ -893,13 +877,12 b' class TestPullrequestsView(object):'
893 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
877 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
894 branch=branch_name, commit_id=commit_ids['new-feature'])
878 branch=branch_name, commit_id=commit_ids['new-feature'])
895 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
879 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
896 branch=backend_git.default_branch_name,
880 branch=backend_git.default_branch_name, commit_id=commit_ids['old-feature'])
897 commit_id=commit_ids['old-feature'])
898 pull_request.revisions = [commit_ids['new-feature']]
881 pull_request.revisions = [commit_ids['new-feature']]
899 pull_request.title = u"Test"
882 pull_request.title = u"Test"
900 pull_request.description = u"Description"
883 pull_request.description = u"Description"
901 pull_request.author = UserModel().get_by_username(
884 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
902 TEST_USER_ADMIN_LOGIN)
885 pull_request.pull_request_state = PullRequest.STATE_CREATED
903 Session().add(pull_request)
886 Session().add(pull_request)
904 Session().commit()
887 Session().commit()
905
888
@@ -273,9 +273,22 b' class RepoPullRequestsView(RepoAppView, '
273 route_name='pullrequest_show', request_method='GET',
273 route_name='pullrequest_show', request_method='GET',
274 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
274 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
275 def pull_request_show(self):
275 def pull_request_show(self):
276 pull_request_id = self.request.matchdict['pull_request_id']
276 _ = self.request.translate
277 c = self.load_default_context()
278
279 pull_request = PullRequest.get_or_404(
280 self.request.matchdict['pull_request_id'])
281 pull_request_id = pull_request.pull_request_id
277
282
278 c = self.load_default_context()
283 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
284 log.debug('show: forbidden because pull request is in state %s',
285 pull_request.pull_request_state)
286 msg = _(u'Cannot show pull requests in state other than `{}`. '
287 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
288 pull_request.pull_request_state)
289 h.flash(msg, category='error')
290 raise HTTPFound(h.route_path('pullrequest_show_all',
291 repo_name=self.db_repo_name))
279
292
280 version = self.request.GET.get('version')
293 version = self.request.GET.get('version')
281 from_version = self.request.GET.get('from_version') or version
294 from_version = self.request.GET.get('from_version') or version
@@ -919,12 +932,17 b' class RepoPullRequestsView(RepoAppView, '
919 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
932 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
920 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
933 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
921
934
935 if not (source_db_repo or target_db_repo):
936 h.flash(_('source_repo or target repo not found'), category='error')
937 raise HTTPFound(
938 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
939
922 # re-check permissions again here
940 # re-check permissions again here
923 # source_repo we must have read permissions
941 # source_repo we must have read permissions
924
942
925 source_perm = HasRepoPermissionAny(
943 source_perm = HasRepoPermissionAny(
926 'repository.read',
944 'repository.read', 'repository.write', 'repository.admin')(
927 'repository.write', 'repository.admin')(source_db_repo.repo_name)
945 source_db_repo.repo_name)
928 if not source_perm:
946 if not source_perm:
929 msg = _('Not Enough permissions to source repo `{}`.'.format(
947 msg = _('Not Enough permissions to source repo `{}`.'.format(
930 source_db_repo.repo_name))
948 source_db_repo.repo_name))
@@ -938,8 +956,8 b' class RepoPullRequestsView(RepoAppView, '
938 # target repo we must have read permissions, and also later on
956 # target repo we must have read permissions, and also later on
939 # we want to check branch permissions here
957 # we want to check branch permissions here
940 target_perm = HasRepoPermissionAny(
958 target_perm = HasRepoPermissionAny(
941 'repository.read',
959 'repository.read', 'repository.write', 'repository.admin')(
942 'repository.write', 'repository.admin')(target_db_repo.repo_name)
960 target_db_repo.repo_name)
943 if not target_perm:
961 if not target_perm:
944 msg = _('Not Enough permissions to target repo `{}`.'.format(
962 msg = _('Not Enough permissions to target repo `{}`.'.format(
945 target_db_repo.repo_name))
963 target_db_repo.repo_name))
@@ -1042,6 +1060,15 b' class RepoPullRequestsView(RepoAppView, '
1042 h.flash(msg, category='error')
1060 h.flash(msg, category='error')
1043 return True
1061 return True
1044
1062
1063 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
1064 log.debug('update: forbidden because pull request is in state %s',
1065 pull_request.pull_request_state)
1066 msg = _(u'Cannot update pull requests in state other than `{}`. '
1067 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1068 pull_request.pull_request_state)
1069 h.flash(msg, category='error')
1070 return True
1071
1045 # only owner or admin can update it
1072 # only owner or admin can update it
1046 allowed_to_update = PullRequestModel().check_user_update(
1073 allowed_to_update = PullRequestModel().check_user_update(
1047 pull_request, self._rhodecode_user)
1074 pull_request, self._rhodecode_user)
@@ -1084,7 +1111,9 b' class RepoPullRequestsView(RepoAppView, '
1084
1111
1085 def _update_commits(self, pull_request):
1112 def _update_commits(self, pull_request):
1086 _ = self.request.translate
1113 _ = self.request.translate
1087 resp = PullRequestModel().update_commits(pull_request)
1114
1115 with pull_request.set_state(PullRequest.STATE_UPDATING):
1116 resp = PullRequestModel().update_commits(pull_request)
1088
1117
1089 if resp.executed:
1118 if resp.executed:
1090
1119
@@ -1097,10 +1126,9 b' class RepoPullRequestsView(RepoAppView, '
1097 else:
1126 else:
1098 changed = 'nothing'
1127 changed = 'nothing'
1099
1128
1100 msg = _(
1129 msg = _(u'Pull request updated to "{source_commit_id}" with '
1101 u'Pull request updated to "{source_commit_id}" with '
1130 u'{count_added} added, {count_removed} removed commits. '
1102 u'{count_added} added, {count_removed} removed commits. '
1131 u'Source of changes: {change_source}')
1103 u'Source of changes: {change_source}')
1104 msg = msg.format(
1132 msg = msg.format(
1105 source_commit_id=pull_request.source_ref_parts.commit_id,
1133 source_commit_id=pull_request.source_ref_parts.commit_id,
1106 count_added=len(resp.changes.added),
1134 count_added=len(resp.changes.added),
@@ -1109,8 +1137,7 b' class RepoPullRequestsView(RepoAppView, '
1109 h.flash(msg, category='success')
1137 h.flash(msg, category='success')
1110
1138
1111 channel = '/repo${}$/pr/{}'.format(
1139 channel = '/repo${}$/pr/{}'.format(
1112 pull_request.target_repo.repo_name,
1140 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1113 pull_request.pull_request_id)
1114 message = msg + (
1141 message = msg + (
1115 ' - <a onclick="window.location.reload()">'
1142 ' - <a onclick="window.location.reload()">'
1116 '<strong>{}</strong></a>'.format(_('Reload page')))
1143 '<strong>{}</strong></a>'.format(_('Reload page')))
@@ -1143,11 +1170,26 b' class RepoPullRequestsView(RepoAppView, '
1143 """
1170 """
1144 pull_request = PullRequest.get_or_404(
1171 pull_request = PullRequest.get_or_404(
1145 self.request.matchdict['pull_request_id'])
1172 self.request.matchdict['pull_request_id'])
1173 _ = self.request.translate
1174
1175 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
1176 log.debug('show: forbidden because pull request is in state %s',
1177 pull_request.pull_request_state)
1178 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1179 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1180 pull_request.pull_request_state)
1181 h.flash(msg, category='error')
1182 raise HTTPFound(
1183 h.route_path('pullrequest_show',
1184 repo_name=pull_request.target_repo.repo_name,
1185 pull_request_id=pull_request.pull_request_id))
1146
1186
1147 self.load_default_context()
1187 self.load_default_context()
1148 check = MergeCheck.validate(
1188
1149 pull_request, auth_user=self._rhodecode_user,
1189 with pull_request.set_state(PullRequest.STATE_UPDATING):
1150 translator=self.request.translate)
1190 check = MergeCheck.validate(
1191 pull_request, auth_user=self._rhodecode_user,
1192 translator=self.request.translate)
1151 merge_possible = not check.failed
1193 merge_possible = not check.failed
1152
1194
1153 for err_type, error_msg in check.errors:
1195 for err_type, error_msg in check.errors:
@@ -1159,8 +1201,9 b' class RepoPullRequestsView(RepoAppView, '
1159 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1201 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1160 username=self._rhodecode_db_user.username, action='push',
1202 username=self._rhodecode_db_user.username, action='push',
1161 scm=pull_request.target_repo.repo_type)
1203 scm=pull_request.target_repo.repo_type)
1162 self._merge_pull_request(
1204 with pull_request.set_state(PullRequest.STATE_UPDATING):
1163 pull_request, self._rhodecode_db_user, extras)
1205 self._merge_pull_request(
1206 pull_request, self._rhodecode_db_user, extras)
1164 else:
1207 else:
1165 log.debug("Pre-conditions failed, NOT merging.")
1208 log.debug("Pre-conditions failed, NOT merging.")
1166
1209
@@ -3538,6 +3538,32 b' class ChangesetStatus(Base, BaseModel):'
3538 return data
3538 return data
3539
3539
3540
3540
3541 class _SetState(object):
3542 """
3543 Context processor allowing changing state for sensitive operation such as
3544 pull request update or merge
3545 """
3546
3547 def __init__(self, pull_request, pr_state, back_state=None):
3548 self._pr = pull_request
3549 self._org_state = back_state or pull_request.pull_request_state
3550 self._pr_state = pr_state
3551
3552 def __enter__(self):
3553 log.debug('StateLock: entering set state context, setting state to: `%s`',
3554 self._pr_state)
3555 self._pr.pull_request_state = self._pr_state
3556 Session().add(self._pr)
3557 Session().commit()
3558
3559 def __exit__(self, exc_type, exc_val, exc_tb):
3560 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3561 self._org_state)
3562 self._pr.pull_request_state = self._org_state
3563 Session().add(self._pr)
3564 Session().commit()
3565
3566
3541 class _PullRequestBase(BaseModel):
3567 class _PullRequestBase(BaseModel):
3542 """
3568 """
3543 Common attributes of pull request and version entries.
3569 Common attributes of pull request and version entries.
@@ -3548,6 +3574,12 b' class _PullRequestBase(BaseModel):'
3548 STATUS_OPEN = u'open'
3574 STATUS_OPEN = u'open'
3549 STATUS_CLOSED = u'closed'
3575 STATUS_CLOSED = u'closed'
3550
3576
3577 # available states
3578 STATE_CREATING = u'creating'
3579 STATE_UPDATING = u'updating'
3580 STATE_MERGING = u'merging'
3581 STATE_CREATED = u'created'
3582
3551 title = Column('title', Unicode(255), nullable=True)
3583 title = Column('title', Unicode(255), nullable=True)
3552 description = Column(
3584 description = Column(
3553 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3585 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
@@ -3563,6 +3595,8 b' class _PullRequestBase(BaseModel):'
3563 'updated_on', DateTime(timezone=False), nullable=False,
3595 'updated_on', DateTime(timezone=False), nullable=False,
3564 default=datetime.datetime.now)
3596 default=datetime.datetime.now)
3565
3597
3598 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3599
3566 @declared_attr
3600 @declared_attr
3567 def user_id(cls):
3601 def user_id(cls):
3568 return Column(
3602 return Column(
@@ -3737,6 +3771,7 b' class _PullRequestBase(BaseModel):'
3737 'title': pull_request.title,
3771 'title': pull_request.title,
3738 'description': pull_request.description,
3772 'description': pull_request.description,
3739 'status': pull_request.status,
3773 'status': pull_request.status,
3774 'state': pull_request.pull_request_state,
3740 'created_on': pull_request.created_on,
3775 'created_on': pull_request.created_on,
3741 'updated_on': pull_request.updated_on,
3776 'updated_on': pull_request.updated_on,
3742 'commit_ids': pull_request.revisions,
3777 'commit_ids': pull_request.revisions,
@@ -3777,6 +3812,20 b' class _PullRequestBase(BaseModel):'
3777
3812
3778 return data
3813 return data
3779
3814
3815 def set_state(self, pull_request_state, final_state=None):
3816 """
3817 # goes from initial state to updating to initial state.
3818 # initial state can be changed by specifying back_state=
3819 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
3820 pull_request.merge()
3821
3822 :param pull_request_state:
3823 :param final_state:
3824
3825 """
3826
3827 return _SetState(self, pull_request_state, back_state=final_state)
3828
3780
3829
3781 class PullRequest(Base, _PullRequestBase):
3830 class PullRequest(Base, _PullRequestBase):
3782 __tablename__ = 'pull_requests'
3831 __tablename__ = 'pull_requests'
@@ -138,7 +138,7 b' class PullRequestModel(BaseModel):'
138
138
139 def _prepare_get_all_query(self, repo_name, source=False, statuses=None,
139 def _prepare_get_all_query(self, repo_name, source=False, statuses=None,
140 opened_by=None, order_by=None,
140 opened_by=None, order_by=None,
141 order_dir='desc'):
141 order_dir='desc', only_created=True):
142 repo = None
142 repo = None
143 if repo_name:
143 if repo_name:
144 repo = self._get_repo(repo_name)
144 repo = self._get_repo(repo_name)
@@ -159,6 +159,10 b' class PullRequestModel(BaseModel):'
159 if opened_by:
159 if opened_by:
160 q = q.filter(PullRequest.user_id.in_(opened_by))
160 q = q.filter(PullRequest.user_id.in_(opened_by))
161
161
162 # only get those that are in "created" state
163 if only_created:
164 q = q.filter(PullRequest.pull_request_state == PullRequest.STATE_CREATED)
165
162 if order_by:
166 if order_by:
163 order_map = {
167 order_map = {
164 'name_raw': PullRequest.pull_request_id,
168 'name_raw': PullRequest.pull_request_id,
@@ -429,7 +433,7 b' class PullRequestModel(BaseModel):'
429 pull_request.description_renderer = description_renderer
433 pull_request.description_renderer = description_renderer
430 pull_request.author = created_by_user
434 pull_request.author = created_by_user
431 pull_request.reviewer_data = reviewer_data
435 pull_request.reviewer_data = reviewer_data
432
436 pull_request.pull_request_state = pull_request.STATE_CREATING
433 Session().add(pull_request)
437 Session().add(pull_request)
434 Session().flush()
438 Session().flush()
435
439
@@ -497,9 +501,16 b' class PullRequestModel(BaseModel):'
497 # that for large repos could be long resulting in long row locks
501 # that for large repos could be long resulting in long row locks
498 Session().commit()
502 Session().commit()
499
503
500 # prepare workspace, and run initial merge simulation
504 # prepare workspace, and run initial merge simulation. Set state during that
501 MergeCheck.validate(
505 # operation
502 pull_request, auth_user=auth_user, translator=translator)
506 pull_request = PullRequest.get(pull_request.pull_request_id)
507
508 # set as merging, for simulation, and if finished to created so we mark
509 # simulation is working fine
510 with pull_request.set_state(PullRequest.STATE_MERGING,
511 final_state=PullRequest.STATE_CREATED):
512 MergeCheck.validate(
513 pull_request, auth_user=auth_user, translator=translator)
503
514
504 self.notify_reviewers(pull_request, reviewer_ids)
515 self.notify_reviewers(pull_request, reviewer_ids)
505 self._trigger_pull_request_hook(
516 self._trigger_pull_request_hook(
@@ -653,9 +664,8 b' class PullRequestModel(BaseModel):'
653 target_ref_id = pull_request.target_ref_parts.commit_id
664 target_ref_id = pull_request.target_ref_parts.commit_id
654
665
655 if not self.has_valid_update_type(pull_request):
666 if not self.has_valid_update_type(pull_request):
656 log.debug(
667 log.debug("Skipping update of pull request %s due to ref type: %s",
657 "Skipping update of pull request %s due to ref type: %s",
668 pull_request, source_ref_type)
658 pull_request, source_ref_type)
659 return UpdateResponse(
669 return UpdateResponse(
660 executed=False,
670 executed=False,
661 reason=UpdateFailureReason.WRONG_REF_TYPE,
671 reason=UpdateFailureReason.WRONG_REF_TYPE,
@@ -801,8 +811,7 b' class PullRequestModel(BaseModel):'
801 pull_request.source_ref_parts.commit_id,
811 pull_request.source_ref_parts.commit_id,
802 pull_request_version.pull_request_version_id)
812 pull_request_version.pull_request_version_id)
803 Session().commit()
813 Session().commit()
804 self._trigger_pull_request_hook(
814 self._trigger_pull_request_hook(pull_request, pull_request.author, 'update')
805 pull_request, pull_request.author, 'update')
806
815
807 return UpdateResponse(
816 return UpdateResponse(
808 executed=True, reason=UpdateFailureReason.NONE,
817 executed=True, reason=UpdateFailureReason.NONE,
@@ -814,6 +823,7 b' class PullRequestModel(BaseModel):'
814 version.title = pull_request.title
823 version.title = pull_request.title
815 version.description = pull_request.description
824 version.description = pull_request.description
816 version.status = pull_request.status
825 version.status = pull_request.status
826 version.pull_request_state = pull_request.pull_request_state
817 version.created_on = datetime.datetime.now()
827 version.created_on = datetime.datetime.now()
818 version.updated_on = pull_request.updated_on
828 version.updated_on = pull_request.updated_on
819 version.user_id = pull_request.user_id
829 version.user_id = pull_request.user_id
@@ -1614,8 +1624,7 b' class MergeCheck(object):'
1614
1624
1615 msg = _('Pull request reviewer approval is pending.')
1625 msg = _('Pull request reviewer approval is pending.')
1616
1626
1617 merge_check.push_error(
1627 merge_check.push_error('warning', msg, cls.REVIEW_CHECK, review_status)
1618 'warning', msg, cls.REVIEW_CHECK, review_status)
1619
1628
1620 if fail_early:
1629 if fail_early:
1621 return merge_check
1630 return merge_check
@@ -1624,7 +1633,7 b' class MergeCheck(object):'
1624 todos = CommentsModel().get_unresolved_todos(pull_request)
1633 todos = CommentsModel().get_unresolved_todos(pull_request)
1625 if todos:
1634 if todos:
1626 log.debug("MergeCheck: cannot merge, {} "
1635 log.debug("MergeCheck: cannot merge, {} "
1627 "unresolved todos left.".format(len(todos)))
1636 "unresolved TODOs left.".format(len(todos)))
1628
1637
1629 if len(todos) == 1:
1638 if len(todos) == 1:
1630 msg = _('Cannot merge, {} TODO still not resolved.').format(
1639 msg = _('Cannot merge, {} TODO still not resolved.').format(
@@ -1645,8 +1654,7 b' class MergeCheck(object):'
1645 merge_check.merge_possible = merge_status
1654 merge_check.merge_possible = merge_status
1646 merge_check.merge_msg = msg
1655 merge_check.merge_msg = msg
1647 if not merge_status:
1656 if not merge_status:
1648 log.debug(
1657 log.debug("MergeCheck: cannot merge, pull request merge not possible.")
1649 "MergeCheck: cannot merge, pull request merge not possible.")
1650 merge_check.push_error('warning', msg, cls.MERGE_CHECK, None)
1658 merge_check.push_error('warning', msg, cls.MERGE_CHECK, None)
1651
1659
1652 if fail_early:
1660 if fail_early:
@@ -1677,6 +1685,7 b' class MergeCheck(object):'
1677 close_branch = model._close_branch_before_merging(pull_request)
1685 close_branch = model._close_branch_before_merging(pull_request)
1678 if close_branch:
1686 if close_branch:
1679 repo_type = pull_request.target_repo.repo_type
1687 repo_type = pull_request.target_repo.repo_type
1688 close_msg = ''
1680 if repo_type == 'hg':
1689 if repo_type == 'hg':
1681 close_msg = _('Source branch will be closed after merge.')
1690 close_msg = _('Source branch will be closed after merge.')
1682 elif repo_type == 'git':
1691 elif repo_type == 'git':
@@ -1689,6 +1698,7 b' class MergeCheck(object):'
1689
1698
1690 return merge_details
1699 return merge_details
1691
1700
1701
1692 ChangeTuple = collections.namedtuple(
1702 ChangeTuple = collections.namedtuple(
1693 'ChangeTuple', ['added', 'common', 'removed', 'total'])
1703 'ChangeTuple', ['added', 'common', 'removed', 'total'])
1694
1704
@@ -257,8 +257,7 b' class TestPullRequestModel(object):'
257 def has_largefiles(self, repo):
257 def has_largefiles(self, repo):
258 return repo == pull_request.source_repo
258 return repo == pull_request.source_repo
259
259
260 patcher = mock.patch.object(
260 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
261 PullRequestModel, '_has_largefiles', has_largefiles)
262 with patcher:
261 with patcher:
263 status, msg = PullRequestModel().merge_status(pull_request)
262 status, msg = PullRequestModel().merge_status(pull_request)
264
263
@@ -270,8 +269,7 b' class TestPullRequestModel(object):'
270 def has_largefiles(self, repo):
269 def has_largefiles(self, repo):
271 return repo == pull_request.target_repo
270 return repo == pull_request.target_repo
272
271
273 patcher = mock.patch.object(
272 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
274 PullRequestModel, '_has_largefiles', has_largefiles)
275 with patcher:
273 with patcher:
276 status, msg = PullRequestModel().merge_status(pull_request)
274 status, msg = PullRequestModel().merge_status(pull_request)
277
275
@@ -314,9 +312,50 b' class TestPullRequestModel(object):'
314 self.pull_request, self.pull_request.author, 'merge')
312 self.pull_request, self.pull_request.author, 'merge')
315
313
316 pull_request = PullRequest.get(pull_request.pull_request_id)
314 pull_request = PullRequest.get(pull_request.pull_request_id)
317 assert (
315 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
318 pull_request.merge_rev ==
316
319 '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
317 def test_merge_with_status_lock(self, pull_request, merge_extras):
318 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
319 merge_ref = Reference(
320 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
321 self.merge_mock.return_value = MergeResponse(
322 True, True, merge_ref, MergeFailureReason.NONE)
323
324 merge_extras['repository'] = pull_request.target_repo.repo_name
325
326 with pull_request.set_state(PullRequest.STATE_UPDATING):
327 assert pull_request.pull_request_state == PullRequest.STATE_UPDATING
328 PullRequestModel().merge_repo(
329 pull_request, pull_request.author, extras=merge_extras)
330
331 assert pull_request.pull_request_state == PullRequest.STATE_CREATED
332
333 message = (
334 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
335 u'\n\n {pr_title}'.format(
336 pr_id=pull_request.pull_request_id,
337 source_repo=safe_unicode(
338 pull_request.source_repo.scm_instance().name),
339 source_ref_name=pull_request.source_ref_parts.name,
340 pr_title=safe_unicode(pull_request.title)
341 )
342 )
343 self.merge_mock.assert_called_with(
344 self.repo_id, self.workspace_id,
345 pull_request.target_ref_parts,
346 pull_request.source_repo.scm_instance(),
347 pull_request.source_ref_parts,
348 user_name=user.short_contact, user_email=user.email, message=message,
349 use_rebase=False, close_branch=False
350 )
351 self.invalidation_mock.assert_called_once_with(
352 pull_request.target_repo.repo_name)
353
354 self.hook_mock.assert_called_with(
355 self.pull_request, self.pull_request.author, 'merge')
356
357 pull_request = PullRequest.get(pull_request.pull_request_id)
358 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
320
359
321 def test_merge_failed(self, pull_request, merge_extras):
360 def test_merge_failed(self, pull_request, merge_extras):
322 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
361 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
General Comments 0
You need to be logged in to leave comments. Login now