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__ = 9 |
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
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