##// END OF EJS Templates
api: pull-requests, make the repoid parameter optional. Because pullrequestid is global...
marcink -
r2395:a90c6294 default
parent child Browse files
Show More
@@ -1,131 +1,131 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.api.utils import Optional, OAttr
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestApi(object):
30 30 maxDiff = None
31 31
32 32 def test_Optional_object(self):
33 33
34 34 option1 = Optional(None)
35 35 assert '<Optional:%s>' % (None,) == repr(option1)
36 36 assert option1() is None
37 37
38 38 assert 1 == Optional.extract(Optional(1))
39 39 assert 'example' == Optional.extract('example')
40 40
41 41 def test_Optional_OAttr(self):
42 42 option1 = Optional(OAttr('apiuser'))
43 43 assert 'apiuser' == Optional.extract(option1)
44 44
45 45 def test_OAttr_object(self):
46 46 oattr1 = OAttr('apiuser')
47 47 assert '<OptionalAttr:apiuser>' == repr(oattr1)
48 48 assert oattr1() == oattr1
49 49
50 50 def test_api_wrong_key(self):
51 51 id_, params = build_data('trololo', 'get_user')
52 52 response = api_call(self.app, params)
53 53
54 54 expected = 'Invalid API KEY'
55 55 assert_error(id_, expected, given=response.body)
56 56
57 57 def test_api_missing_non_optional_param(self):
58 58 id_, params = build_data(self.apikey, 'get_repo')
59 59 response = api_call(self.app, params)
60 60
61 61 expected = 'Missing non optional `repoid` arg in JSON DATA'
62 62 assert_error(id_, expected, given=response.body)
63 63
64 64 def test_api_missing_non_optional_param_args_null(self):
65 65 id_, params = build_data(self.apikey, 'get_repo')
66 66 params = params.replace('"args": {}', '"args": null')
67 67 response = api_call(self.app, params)
68 68
69 69 expected = 'Missing non optional `repoid` arg in JSON DATA'
70 70 assert_error(id_, expected, given=response.body)
71 71
72 72 def test_api_missing_non_optional_param_args_bad(self):
73 73 id_, params = build_data(self.apikey, 'get_repo')
74 74 params = params.replace('"args": {}', '"args": 1')
75 75 response = api_call(self.app, params)
76 76
77 77 expected = 'Missing non optional `repoid` arg in JSON DATA'
78 78 assert_error(id_, expected, given=response.body)
79 79
80 80 def test_api_non_existing_method(self, request):
81 81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
82 82 response = api_call(self.app, params)
83 83 expected = 'No such method: not_existing. Similar methods: none'
84 84 assert_error(id_, expected, given=response.body)
85 85
86 86 def test_api_non_existing_method_have_similar(self, request):
87 87 id_, params = build_data(self.apikey, 'comment', args='xx')
88 88 response = api_call(self.app, params)
89 expected = 'No such method: comment. Similar methods: changeset_comment, comment_pull_request, comment_commit'
89 expected = 'No such method: comment. Similar methods: changeset_comment, comment_pull_request, get_pull_request_comments, comment_commit'
90 90 assert_error(id_, expected, given=response.body)
91 91
92 92 def test_api_disabled_user(self, request):
93 93
94 94 def set_active(active):
95 95 from rhodecode.model.db import Session, User
96 96 user = User.get_by_auth_token(self.apikey)
97 97 user.active = active
98 98 Session().add(user)
99 99 Session().commit()
100 100
101 101 request.addfinalizer(lambda: set_active(True))
102 102
103 103 set_active(False)
104 104 id_, params = build_data(self.apikey, 'test', args='xx')
105 105 response = api_call(self.app, params)
106 106 expected = 'Request from this user not allowed'
107 107 assert_error(id_, expected, given=response.body)
108 108
109 109 def test_api_args_is_null(self):
110 110 __, params = build_data(self.apikey, 'get_users', )
111 111 params = params.replace('"args": {}', '"args": null')
112 112 response = api_call(self.app, params)
113 113 assert response.status == '200 OK'
114 114
115 115 def test_api_args_is_bad(self):
116 116 __, params = build_data(self.apikey, 'get_users', )
117 117 params = params.replace('"args": {}', '"args": 1')
118 118 response = api_call(self.app, params)
119 119 assert response.status == '200 OK'
120 120
121 121 def test_api_args_different_args(self):
122 122 import string
123 123 expected = {
124 124 'ascii_letters': string.ascii_letters,
125 125 'ws': string.whitespace,
126 126 'printables': string.printable
127 127 }
128 128 id_, params = build_data(self.apikey, 'test', args=expected)
129 129 response = api_call(self.app, params)
130 130 assert response.status == '200 OK'
131 131 assert_ok(id_, expected, response.body)
@@ -1,112 +1,113 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.db import UserLog
24 24 from rhodecode.model.pull_request import PullRequestModel
25 25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestClosePullRequest(object):
32 32
33 33 @pytest.mark.backends("git", "hg")
34 34 def test_api_close_pull_request(self, pr_util):
35 35 pull_request = pr_util.create_pull_request()
36 36 pull_request_id = pull_request.pull_request_id
37 37 author = pull_request.user_id
38 38 repo = pull_request.target_repo.repo_id
39 39 id_, params = build_data(
40 40 self.apikey, 'close_pull_request',
41 41 repoid=pull_request.target_repo.repo_name,
42 42 pullrequestid=pull_request.pull_request_id)
43 43 response = api_call(self.app, params)
44 44 expected = {
45 45 'pull_request_id': pull_request_id,
46 46 'close_status': 'Rejected',
47 47 'closed': True,
48 48 }
49 49 assert_ok(id_, expected, response.body)
50 50 journal = UserLog.query()\
51 51 .filter(UserLog.user_id == author) \
52 52 .order_by('user_log_id') \
53 53 .filter(UserLog.repository_id == repo)\
54 54 .all()
55 55 assert journal[-1].action == 'repo.pull_request.close'
56 56
57 57 @pytest.mark.backends("git", "hg")
58 58 def test_api_close_pull_request_already_closed_error(self, pr_util):
59 59 pull_request = pr_util.create_pull_request()
60 60 pull_request_id = pull_request.pull_request_id
61 61 pull_request_repo = pull_request.target_repo.repo_name
62 62 PullRequestModel().close_pull_request(
63 63 pull_request, pull_request.author)
64 64 id_, params = build_data(
65 65 self.apikey, 'close_pull_request',
66 66 repoid=pull_request_repo, pullrequestid=pull_request_id)
67 67 response = api_call(self.app, params)
68 68
69 69 expected = 'pull request `%s` is already closed' % pull_request_id
70 70 assert_error(id_, expected, given=response.body)
71 71
72 72 @pytest.mark.backends("git", "hg")
73 def test_api_close_pull_request_repo_error(self):
73 def test_api_close_pull_request_repo_error(self, pr_util):
74 pull_request = pr_util.create_pull_request()
74 75 id_, params = build_data(
75 76 self.apikey, 'close_pull_request',
76 repoid=666, pullrequestid=1)
77 repoid=666, pullrequestid=pull_request.pull_request_id)
77 78 response = api_call(self.app, params)
78 79
79 80 expected = 'repository `666` does not exist'
80 81 assert_error(id_, expected, given=response.body)
81 82
82 83 @pytest.mark.backends("git", "hg")
83 84 def test_api_close_pull_request_non_admin_with_userid_error(self,
84 85 pr_util):
85 86 pull_request = pr_util.create_pull_request()
86 87 id_, params = build_data(
87 88 self.apikey_regular, 'close_pull_request',
88 89 repoid=pull_request.target_repo.repo_name,
89 90 pullrequestid=pull_request.pull_request_id,
90 91 userid=TEST_USER_ADMIN_LOGIN)
91 92 response = api_call(self.app, params)
92 93
93 94 expected = 'userid is not the same as your user'
94 95 assert_error(id_, expected, given=response.body)
95 96
96 97 @pytest.mark.backends("git", "hg")
97 98 def test_api_close_pull_request_no_perms_to_close(
98 99 self, user_util, pr_util):
99 100 user = user_util.create_user()
100 101 pull_request = pr_util.create_pull_request()
101 102
102 103 id_, params = build_data(
103 104 user.api_key, 'close_pull_request',
104 105 repoid=pull_request.target_repo.repo_name,
105 106 pullrequestid=pull_request.pull_request_id,)
106 107 response = api_call(self.app, params)
107 108
108 109 expected = ('pull request `%s` close failed, '
109 110 'no permission to close.') % pull_request.pull_request_id
110 111
111 112 response_json = response.json['error']
112 113 assert response_json == expected
@@ -1,208 +1,209 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.comment import CommentsModel
24 24 from rhodecode.model.db import UserLog
25 25 from rhodecode.model.pull_request import PullRequestModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_error, assert_ok)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestCommentPullRequest(object):
33 33 finalizers = []
34 34
35 35 def teardown_method(self, method):
36 36 if self.finalizers:
37 37 for finalizer in self.finalizers:
38 38 finalizer()
39 39 self.finalizers = []
40 40
41 41 @pytest.mark.backends("git", "hg")
42 42 def test_api_comment_pull_request(self, pr_util, no_notifications):
43 43 pull_request = pr_util.create_pull_request()
44 44 pull_request_id = pull_request.pull_request_id
45 45 author = pull_request.user_id
46 46 repo = pull_request.target_repo.repo_id
47 47 id_, params = build_data(
48 48 self.apikey, 'comment_pull_request',
49 49 repoid=pull_request.target_repo.repo_name,
50 50 pullrequestid=pull_request.pull_request_id,
51 51 message='test message')
52 52 response = api_call(self.app, params)
53 53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
54 54
55 55 comments = CommentsModel().get_comments(
56 56 pull_request.target_repo.repo_id, pull_request=pull_request)
57 57
58 58 expected = {
59 59 'pull_request_id': pull_request.pull_request_id,
60 60 'comment_id': comments[-1].comment_id,
61 61 'status': {'given': None, 'was_changed': None}
62 62 }
63 63 assert_ok(id_, expected, response.body)
64 64
65 65 journal = UserLog.query()\
66 66 .filter(UserLog.user_id == author)\
67 67 .filter(UserLog.repository_id == repo) \
68 68 .order_by('user_log_id') \
69 69 .all()
70 70 assert journal[-1].action == 'repo.pull_request.comment.create'
71 71
72 72 @pytest.mark.backends("git", "hg")
73 73 def test_api_comment_pull_request_change_status(
74 74 self, pr_util, no_notifications):
75 75 pull_request = pr_util.create_pull_request()
76 76 pull_request_id = pull_request.pull_request_id
77 77 id_, params = build_data(
78 78 self.apikey, 'comment_pull_request',
79 79 repoid=pull_request.target_repo.repo_name,
80 80 pullrequestid=pull_request.pull_request_id,
81 81 status='rejected')
82 82 response = api_call(self.app, params)
83 83 pull_request = PullRequestModel().get(pull_request_id)
84 84
85 85 comments = CommentsModel().get_comments(
86 86 pull_request.target_repo.repo_id, pull_request=pull_request)
87 87 expected = {
88 88 'pull_request_id': pull_request.pull_request_id,
89 89 'comment_id': comments[-1].comment_id,
90 90 'status': {'given': 'rejected', 'was_changed': True}
91 91 }
92 92 assert_ok(id_, expected, response.body)
93 93
94 94 @pytest.mark.backends("git", "hg")
95 95 def test_api_comment_pull_request_change_status_with_specific_commit_id(
96 96 self, pr_util, no_notifications):
97 97 pull_request = pr_util.create_pull_request()
98 98 pull_request_id = pull_request.pull_request_id
99 99 latest_commit_id = 'test_commit'
100 100 # inject additional revision, to fail test the status change on
101 101 # non-latest commit
102 102 pull_request.revisions = pull_request.revisions + ['test_commit']
103 103
104 104 id_, params = build_data(
105 105 self.apikey, 'comment_pull_request',
106 106 repoid=pull_request.target_repo.repo_name,
107 107 pullrequestid=pull_request.pull_request_id,
108 108 status='approved', commit_id=latest_commit_id)
109 109 response = api_call(self.app, params)
110 110 pull_request = PullRequestModel().get(pull_request_id)
111 111
112 112 expected = {
113 113 'pull_request_id': pull_request.pull_request_id,
114 114 'comment_id': None,
115 115 'status': {'given': 'approved', 'was_changed': False}
116 116 }
117 117 assert_ok(id_, expected, response.body)
118 118
119 119 @pytest.mark.backends("git", "hg")
120 120 def test_api_comment_pull_request_change_status_with_specific_commit_id(
121 121 self, pr_util, no_notifications):
122 122 pull_request = pr_util.create_pull_request()
123 123 pull_request_id = pull_request.pull_request_id
124 124 latest_commit_id = pull_request.revisions[0]
125 125
126 126 id_, params = build_data(
127 127 self.apikey, 'comment_pull_request',
128 128 repoid=pull_request.target_repo.repo_name,
129 129 pullrequestid=pull_request.pull_request_id,
130 130 status='approved', commit_id=latest_commit_id)
131 131 response = api_call(self.app, params)
132 132 pull_request = PullRequestModel().get(pull_request_id)
133 133
134 134 comments = CommentsModel().get_comments(
135 135 pull_request.target_repo.repo_id, pull_request=pull_request)
136 136 expected = {
137 137 'pull_request_id': pull_request.pull_request_id,
138 138 'comment_id': comments[-1].comment_id,
139 139 'status': {'given': 'approved', 'was_changed': True}
140 140 }
141 141 assert_ok(id_, expected, response.body)
142 142
143 143 @pytest.mark.backends("git", "hg")
144 144 def test_api_comment_pull_request_missing_params_error(self, pr_util):
145 145 pull_request = pr_util.create_pull_request()
146 146 pull_request_id = pull_request.pull_request_id
147 147 pull_request_repo = pull_request.target_repo.repo_name
148 148 id_, params = build_data(
149 149 self.apikey, 'comment_pull_request',
150 150 repoid=pull_request_repo,
151 151 pullrequestid=pull_request_id)
152 152 response = api_call(self.app, params)
153 153
154 154 expected = 'Both message and status parameters are missing. At least one is required.'
155 155 assert_error(id_, expected, given=response.body)
156 156
157 157 @pytest.mark.backends("git", "hg")
158 158 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
159 159 pull_request = pr_util.create_pull_request()
160 160 pull_request_id = pull_request.pull_request_id
161 161 pull_request_repo = pull_request.target_repo.repo_name
162 162 id_, params = build_data(
163 163 self.apikey, 'comment_pull_request',
164 164 repoid=pull_request_repo,
165 165 pullrequestid=pull_request_id,
166 166 status='42')
167 167 response = api_call(self.app, params)
168 168
169 169 expected = 'Unknown comment status: `42`'
170 170 assert_error(id_, expected, given=response.body)
171 171
172 172 @pytest.mark.backends("git", "hg")
173 def test_api_comment_pull_request_repo_error(self):
173 def test_api_comment_pull_request_repo_error(self, pr_util):
174 pull_request = pr_util.create_pull_request()
174 175 id_, params = build_data(
175 176 self.apikey, 'comment_pull_request',
176 repoid=666, pullrequestid=1)
177 repoid=666, pullrequestid=pull_request.pull_request_id)
177 178 response = api_call(self.app, params)
178 179
179 180 expected = 'repository `666` does not exist'
180 181 assert_error(id_, expected, given=response.body)
181 182
182 183 @pytest.mark.backends("git", "hg")
183 184 def test_api_comment_pull_request_non_admin_with_userid_error(
184 185 self, pr_util):
185 186 pull_request = pr_util.create_pull_request()
186 187 id_, params = build_data(
187 188 self.apikey_regular, 'comment_pull_request',
188 189 repoid=pull_request.target_repo.repo_name,
189 190 pullrequestid=pull_request.pull_request_id,
190 191 userid=TEST_USER_ADMIN_LOGIN)
191 192 response = api_call(self.app, params)
192 193
193 194 expected = 'userid is not the same as your user'
194 195 assert_error(id_, expected, given=response.body)
195 196
196 197 @pytest.mark.backends("git", "hg")
197 198 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
198 199 pull_request = pr_util.create_pull_request()
199 200 id_, params = build_data(
200 201 self.apikey_regular, 'comment_pull_request',
201 202 repoid=pull_request.target_repo.repo_name,
202 203 status='approved',
203 204 pullrequestid=pull_request.pull_request_id,
204 205 commit_id='XXX')
205 206 response = api_call(self.app, params)
206 207
207 208 expected = 'Invalid commit_id `XXX` for this pull request.'
208 209 assert_error(id_, expected, given=response.body)
@@ -1,58 +1,59 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import pytest
23 23
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestGetMethod(object):
29 29 def test_get_methods_no_matches(self):
30 30 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
31 31 response = api_call(self.app, params)
32 32
33 33 expected = []
34 34 assert_ok(id_, expected, given=response.body)
35 35
36 36 def test_get_methods(self):
37 37 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
38 38 response = api_call(self.app, params)
39 39
40 40 expected = ['changeset_comment', 'comment_pull_request',
41 'comment_commit']
41 'get_pull_request_comments', 'comment_commit']
42 42 assert_ok(id_, expected, given=response.body)
43 43
44 44 def test_get_methods_on_single_match(self):
45 id_, params = build_data(self.apikey, 'get_method', pattern='*comment_commit*')
45 id_, params = build_data(self.apikey, 'get_method',
46 pattern='*comment_commit*')
46 47 response = api_call(self.app, params)
47 48
48 49 expected = ['comment_commit',
49 50 {'apiuser': '<RequiredType>',
50 51 'comment_type': "<Optional:u'note'>",
51 52 'commit_id': '<RequiredType>',
52 53 'message': '<RequiredType>',
53 54 'repoid': '<RequiredType>',
54 55 'request': '<RequiredType>',
55 56 'resolves_comment_id': '<Optional:None>',
56 57 'status': '<Optional:None>',
57 58 'userid': '<Optional:<OptionalAttr:apiuser>>'}]
58 59 assert_ok(id_, expected, given=response.body)
@@ -1,134 +1,142 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import pytest
23 23 import urlobject
24 24
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok)
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.utils2 import safe_unicode
29 29
30 30 pytestmark = pytest.mark.backends("git", "hg")
31 31
32 32
33 33 @pytest.mark.usefixtures("testuser_api", "app")
34 34 class TestGetPullRequest(object):
35 35
36 36 def test_api_get_pull_request(self, pr_util, http_host_only_stub):
37 37 from rhodecode.model.pull_request import PullRequestModel
38 38 pull_request = pr_util.create_pull_request(mergeable=True)
39 39 id_, params = build_data(
40 40 self.apikey, 'get_pull_request',
41 repoid=pull_request.target_repo.repo_name,
42 41 pullrequestid=pull_request.pull_request_id)
43 42
44 43 response = api_call(self.app, params)
45 44
46 45 assert response.status == '200 OK'
47 46
48 47 url_obj = urlobject.URLObject(
49 48 h.route_url(
50 49 'pullrequest_show',
51 50 repo_name=pull_request.target_repo.repo_name,
52 51 pull_request_id=pull_request.pull_request_id))
53 52
54 53 pr_url = safe_unicode(
55 54 url_obj.with_netloc(http_host_only_stub))
56 55 source_url = safe_unicode(
57 56 pull_request.source_repo.clone_url().with_netloc(http_host_only_stub))
58 57 target_url = safe_unicode(
59 58 pull_request.target_repo.clone_url().with_netloc(http_host_only_stub))
60 59 shadow_url = safe_unicode(
61 60 PullRequestModel().get_shadow_clone_url(pull_request))
62 61
63 62 expected = {
64 63 'pull_request_id': pull_request.pull_request_id,
65 64 'url': pr_url,
66 65 'title': pull_request.title,
67 66 'description': pull_request.description,
68 67 'status': pull_request.status,
69 68 'created_on': pull_request.created_on,
70 69 'updated_on': pull_request.updated_on,
71 70 'commit_ids': pull_request.revisions,
72 71 'review_status': pull_request.calculated_review_status(),
73 72 'mergeable': {
74 73 'status': True,
75 74 'message': 'This pull request can be automatically merged.',
76 75 },
77 76 'source': {
78 77 'clone_url': source_url,
79 78 'repository': pull_request.source_repo.repo_name,
80 79 'reference': {
81 80 'name': pull_request.source_ref_parts.name,
82 81 'type': pull_request.source_ref_parts.type,
83 82 'commit_id': pull_request.source_ref_parts.commit_id,
84 83 },
85 84 },
86 85 'target': {
87 86 'clone_url': target_url,
88 87 'repository': pull_request.target_repo.repo_name,
89 88 'reference': {
90 89 'name': pull_request.target_ref_parts.name,
91 90 'type': pull_request.target_ref_parts.type,
92 91 'commit_id': pull_request.target_ref_parts.commit_id,
93 92 },
94 93 },
95 94 'merge': {
96 95 'clone_url': shadow_url,
97 96 'reference': {
98 97 'name': pull_request.shadow_merge_ref.name,
99 98 'type': pull_request.shadow_merge_ref.type,
100 99 'commit_id': pull_request.shadow_merge_ref.commit_id,
101 100 },
102 101 },
103 102 'author': pull_request.author.get_api_data(include_secrets=False,
104 103 details='basic'),
105 104 'reviewers': [
106 105 {
107 106 'user': reviewer.get_api_data(include_secrets=False,
108 107 details='basic'),
109 108 'reasons': reasons,
110 109 'review_status': st[0][1].status if st else 'not_reviewed',
111 110 }
112 111 for reviewer, reasons, mandatory, st in
113 112 pull_request.reviewers_statuses()
114 113 ]
115 114 }
116 115 assert_ok(id_, expected, response.body)
117 116
118 def test_api_get_pull_request_repo_error(self):
117 def test_api_get_pull_request_repo_error(self, pr_util):
118 pull_request = pr_util.create_pull_request()
119 119 id_, params = build_data(
120 120 self.apikey, 'get_pull_request',
121 repoid=666, pullrequestid=1)
121 repoid=666, pullrequestid=pull_request.pull_request_id)
122 122 response = api_call(self.app, params)
123 123
124 124 expected = 'repository `666` does not exist'
125 125 assert_error(id_, expected, given=response.body)
126 126
127 127 def test_api_get_pull_request_pull_request_error(self):
128 128 id_, params = build_data(
129 self.apikey, 'get_pull_request',
130 repoid=1, pullrequestid=666)
129 self.apikey, 'get_pull_request', pullrequestid=666)
131 130 response = api_call(self.app, params)
132 131
133 132 expected = 'pull request `666` does not exist'
134 133 assert_error(id_, expected, given=response.body)
134
135 def test_api_get_pull_request_pull_request_error_just_pr_id(self):
136 id_, params = build_data(
137 self.apikey, 'get_pull_request',
138 pullrequestid=666)
139 response = api_call(self.app, params)
140
141 expected = 'pull request `666` does not exist'
142 assert_error(id_, expected, given=response.body)
@@ -1,136 +1,137 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.db import UserLog, PullRequest
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestMergePullRequest(object):
32 32 @pytest.mark.backends("git", "hg")
33 33 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
34 34 pull_request = pr_util.create_pull_request(mergeable=True)
35 35 author = pull_request.user_id
36 36 repo = pull_request.target_repo.repo_id
37 37 pull_request_id = pull_request.pull_request_id
38 38 pull_request_repo = pull_request.target_repo.repo_name
39 39
40 40 id_, params = build_data(
41 41 self.apikey, 'merge_pull_request',
42 42 repoid=pull_request_repo,
43 43 pullrequestid=pull_request_id)
44 44
45 45 response = api_call(self.app, params)
46 46
47 47 # The above api call detaches the pull request DB object from the
48 48 # session because of an unconditional transaction rollback in our
49 49 # middleware. Therefore we need to add it back here if we want to use
50 50 # it.
51 51 Session().add(pull_request)
52 52
53 53 expected = 'merge not possible for following reasons: ' \
54 54 'Pull request reviewer approval is pending.'
55 55 assert_error(id_, expected, given=response.body)
56 56
57 57 @pytest.mark.backends("git", "hg")
58 58 def test_api_merge_pull_request(self, pr_util, no_notifications):
59 59 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
60 60 author = pull_request.user_id
61 61 repo = pull_request.target_repo.repo_id
62 62 pull_request_id = pull_request.pull_request_id
63 63 pull_request_repo = pull_request.target_repo.repo_name
64 64
65 65 id_, params = build_data(
66 66 self.apikey, 'comment_pull_request',
67 67 repoid=pull_request_repo,
68 68 pullrequestid=pull_request_id,
69 69 status='approved')
70 70
71 71 response = api_call(self.app, params)
72 72 expected = {
73 73 'comment_id': response.json.get('result', {}).get('comment_id'),
74 74 'pull_request_id': pull_request_id,
75 75 'status': {'given': 'approved', 'was_changed': True}
76 76 }
77 77 assert_ok(id_, expected, given=response.body)
78 78
79 79 id_, params = build_data(
80 80 self.apikey, 'merge_pull_request',
81 81 repoid=pull_request_repo,
82 82 pullrequestid=pull_request_id)
83 83
84 84 response = api_call(self.app, params)
85 85
86 86 pull_request = PullRequest.get(pull_request_id)
87 87
88 88 expected = {
89 89 'executed': True,
90 90 'failure_reason': 0,
91 91 'possible': True,
92 92 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
93 93 'merge_ref': pull_request.shadow_merge_ref._asdict()
94 94 }
95 95
96 96 assert_ok(id_, expected, response.body)
97 97
98 98 journal = UserLog.query()\
99 99 .filter(UserLog.user_id == author)\
100 100 .filter(UserLog.repository_id == repo) \
101 101 .order_by('user_log_id') \
102 102 .all()
103 103 assert journal[-2].action == 'repo.pull_request.merge'
104 104 assert journal[-1].action == 'repo.pull_request.close'
105 105
106 106 id_, params = build_data(
107 107 self.apikey, 'merge_pull_request',
108 108 repoid=pull_request_repo, pullrequestid=pull_request_id)
109 109 response = api_call(self.app, params)
110 110
111 111 expected = 'merge not possible for following reasons: This pull request is closed.'
112 112 assert_error(id_, expected, given=response.body)
113 113
114 114 @pytest.mark.backends("git", "hg")
115 def test_api_merge_pull_request_repo_error(self):
115 def test_api_merge_pull_request_repo_error(self, pr_util):
116 pull_request = pr_util.create_pull_request()
116 117 id_, params = build_data(
117 118 self.apikey, 'merge_pull_request',
118 repoid=666, pullrequestid=1)
119 repoid=666, pullrequestid=pull_request.pull_request_id)
119 120 response = api_call(self.app, params)
120 121
121 122 expected = 'repository `666` does not exist'
122 123 assert_error(id_, expected, given=response.body)
123 124
124 125 @pytest.mark.backends("git", "hg")
125 126 def test_api_merge_pull_request_non_admin_with_userid_error(self,
126 127 pr_util):
127 128 pull_request = pr_util.create_pull_request(mergeable=True)
128 129 id_, params = build_data(
129 130 self.apikey_regular, 'merge_pull_request',
130 131 repoid=pull_request.target_repo.repo_name,
131 132 pullrequestid=pull_request.pull_request_id,
132 133 userid=TEST_USER_ADMIN_LOGIN)
133 134 response = api_call(self.app, params)
134 135
135 136 expected = 'userid is not the same as your user'
136 137 assert_error(id_, expected, given=response.body)
@@ -1,212 +1,213 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.lib.vcs.nodes import FileNode
24 24 from rhodecode.model.db import User
25 25 from rhodecode.model.pull_request import PullRequestModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_ok, assert_error)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestUpdatePullRequest(object):
33 33
34 34 @pytest.mark.backends("git", "hg")
35 35 def test_api_update_pull_request_title_or_description(
36 36 self, pr_util, no_notifications):
37 37 pull_request = pr_util.create_pull_request()
38 38
39 39 id_, params = build_data(
40 40 self.apikey, 'update_pull_request',
41 41 repoid=pull_request.target_repo.repo_name,
42 42 pullrequestid=pull_request.pull_request_id,
43 43 title='New TITLE OF A PR',
44 44 description='New DESC OF A PR',
45 45 )
46 46 response = api_call(self.app, params)
47 47
48 48 expected = {
49 49 "msg": "Updated pull request `{}`".format(
50 50 pull_request.pull_request_id),
51 51 "pull_request": response.json['result']['pull_request'],
52 52 "updated_commits": {"added": [], "common": [], "removed": []},
53 53 "updated_reviewers": {"added": [], "removed": []},
54 54 }
55 55
56 56 response_json = response.json['result']
57 57 assert response_json == expected
58 58 pr = response_json['pull_request']
59 59 assert pr['title'] == 'New TITLE OF A PR'
60 60 assert pr['description'] == 'New DESC OF A PR'
61 61
62 62 @pytest.mark.backends("git", "hg")
63 63 def test_api_try_update_closed_pull_request(
64 64 self, pr_util, no_notifications):
65 65 pull_request = pr_util.create_pull_request()
66 66 PullRequestModel().close_pull_request(
67 67 pull_request, TEST_USER_ADMIN_LOGIN)
68 68
69 69 id_, params = build_data(
70 70 self.apikey, 'update_pull_request',
71 71 repoid=pull_request.target_repo.repo_name,
72 72 pullrequestid=pull_request.pull_request_id)
73 73 response = api_call(self.app, params)
74 74
75 75 expected = 'pull request `{}` update failed, pull request ' \
76 76 'is closed'.format(pull_request.pull_request_id)
77 77
78 78 assert_error(id_, expected, response.body)
79 79
80 80 @pytest.mark.backends("git", "hg")
81 81 def test_api_update_update_commits(self, pr_util, no_notifications):
82 82 commits = [
83 83 {'message': 'a'},
84 84 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
85 85 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
86 86 ]
87 87 pull_request = pr_util.create_pull_request(
88 88 commits=commits, target_head='a', source_head='b', revisions=['b'])
89 89 pr_util.update_source_repository(head='c')
90 90 repo = pull_request.source_repo.scm_instance()
91 91 commits = [x for x in repo.get_commits()]
92 92 print commits
93 93
94 94 added_commit_id = commits[-1].raw_id # c commit
95 95 common_commit_id = commits[1].raw_id # b commit is common ancestor
96 96 total_commits = [added_commit_id, common_commit_id]
97 97
98 98 id_, params = build_data(
99 99 self.apikey, 'update_pull_request',
100 100 repoid=pull_request.target_repo.repo_name,
101 101 pullrequestid=pull_request.pull_request_id,
102 102 update_commits=True
103 103 )
104 104 response = api_call(self.app, params)
105 105
106 106 expected = {
107 107 "msg": "Updated pull request `{}`".format(
108 108 pull_request.pull_request_id),
109 109 "pull_request": response.json['result']['pull_request'],
110 110 "updated_commits": {"added": [added_commit_id],
111 111 "common": [common_commit_id],
112 112 "total": total_commits,
113 113 "removed": []},
114 114 "updated_reviewers": {"added": [], "removed": []},
115 115 }
116 116
117 117 assert_ok(id_, expected, response.body)
118 118
119 119 @pytest.mark.backends("git", "hg")
120 120 def test_api_update_change_reviewers(
121 121 self, user_util, pr_util, no_notifications):
122 122 a = user_util.create_user()
123 123 b = user_util.create_user()
124 124 c = user_util.create_user()
125 125 new_reviewers = [
126 126 {'username': b.username,'reasons': ['updated via API'],
127 127 'mandatory':False},
128 128 {'username': c.username, 'reasons': ['updated via API'],
129 129 'mandatory':False},
130 130 ]
131 131
132 132 added = [b.username, c.username]
133 133 removed = [a.username]
134 134
135 135 pull_request = pr_util.create_pull_request(
136 136 reviewers=[(a.username, ['added via API'], False)])
137 137
138 138 id_, params = build_data(
139 139 self.apikey, 'update_pull_request',
140 140 repoid=pull_request.target_repo.repo_name,
141 141 pullrequestid=pull_request.pull_request_id,
142 142 reviewers=new_reviewers)
143 143 response = api_call(self.app, params)
144 144 expected = {
145 145 "msg": "Updated pull request `{}`".format(
146 146 pull_request.pull_request_id),
147 147 "pull_request": response.json['result']['pull_request'],
148 148 "updated_commits": {"added": [], "common": [], "removed": []},
149 149 "updated_reviewers": {"added": added, "removed": removed},
150 150 }
151 151
152 152 assert_ok(id_, expected, response.body)
153 153
154 154 @pytest.mark.backends("git", "hg")
155 155 def test_api_update_bad_user_in_reviewers(self, pr_util):
156 156 pull_request = pr_util.create_pull_request()
157 157
158 158 id_, params = build_data(
159 159 self.apikey, 'update_pull_request',
160 160 repoid=pull_request.target_repo.repo_name,
161 161 pullrequestid=pull_request.pull_request_id,
162 162 reviewers=[{'username': 'bad_name'}])
163 163 response = api_call(self.app, params)
164 164
165 165 expected = 'user `bad_name` does not exist'
166 166
167 167 assert_error(id_, expected, response.body)
168 168
169 169 @pytest.mark.backends("git", "hg")
170 170 def test_api_update_repo_error(self, pr_util):
171 pull_request = pr_util.create_pull_request()
171 172 id_, params = build_data(
172 173 self.apikey, 'update_pull_request',
173 174 repoid='fake',
174 pullrequestid='fake',
175 pullrequestid=pull_request.pull_request_id,
175 176 reviewers=[{'username': 'bad_name'}])
176 177 response = api_call(self.app, params)
177 178
178 179 expected = 'repository `fake` does not exist'
179 180
180 181 response_json = response.json['error']
181 182 assert response_json == expected
182 183
183 184 @pytest.mark.backends("git", "hg")
184 185 def test_api_update_pull_request_error(self, pr_util):
185 186 pull_request = pr_util.create_pull_request()
186 187
187 188 id_, params = build_data(
188 189 self.apikey, 'update_pull_request',
189 190 repoid=pull_request.target_repo.repo_name,
190 191 pullrequestid=999999,
191 192 reviewers=[{'username': 'bad_name'}])
192 193 response = api_call(self.app, params)
193 194
194 195 expected = 'pull request `999999` does not exist'
195 196 assert_error(id_, expected, response.body)
196 197
197 198 @pytest.mark.backends("git", "hg")
198 199 def test_api_update_pull_request_no_perms_to_update(
199 200 self, user_util, pr_util):
200 201 user = user_util.create_user()
201 202 pull_request = pr_util.create_pull_request()
202 203
203 204 id_, params = build_data(
204 205 user.api_key, 'update_pull_request',
205 206 repoid=pull_request.target_repo.repo_name,
206 207 pullrequestid=pull_request.pull_request_id,)
207 208 response = api_call(self.app, params)
208 209
209 210 expected = ('pull request `%s` update failed, '
210 211 'no permission to update.') % pull_request.pull_request_id
211 212
212 213 assert_error(id_, expected, response.body)
@@ -1,883 +1,903 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23
24 24 from rhodecode import events
25 25 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
26 26 from rhodecode.api.utils import (
27 27 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 28 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
29 29 validate_repo_permissions, resolve_ref_or_error)
30 30 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
31 31 from rhodecode.lib.base import vcs_operation_context
32 32 from rhodecode.lib.utils2 import str2bool
33 33 from rhodecode.model.changeset_status import ChangesetStatusModel
34 34 from rhodecode.model.comment import CommentsModel
35 35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
36 36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
37 37 from rhodecode.model.settings import SettingsModel
38 38 from rhodecode.model.validation_schema import Invalid
39 39 from rhodecode.model.validation_schema.schemas.reviewer_schema import(
40 40 ReviewerListSchema)
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 @jsonrpc_method()
46 def get_pull_request(request, apiuser, repoid, pullrequestid):
46 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None)):
47 47 """
48 48 Get a pull request based on the given ID.
49 49
50 50 :param apiuser: This is filled automatically from the |authtoken|.
51 51 :type apiuser: AuthUser
52 :param repoid: Repository name or repository ID from where the pull
53 request was opened.
52 :param repoid: Optional, repository name or repository ID from where
53 the pull request was opened.
54 54 :type repoid: str or int
55 55 :param pullrequestid: ID of the requested pull request.
56 56 :type pullrequestid: int
57 57
58 58 Example output:
59 59
60 60 .. code-block:: bash
61 61
62 62 "id": <id_given_in_input>,
63 63 "result":
64 64 {
65 65 "pull_request_id": "<pull_request_id>",
66 66 "url": "<url>",
67 67 "title": "<title>",
68 68 "description": "<description>",
69 69 "status" : "<status>",
70 70 "created_on": "<date_time_created>",
71 71 "updated_on": "<date_time_updated>",
72 72 "commit_ids": [
73 73 ...
74 74 "<commit_id>",
75 75 "<commit_id>",
76 76 ...
77 77 ],
78 78 "review_status": "<review_status>",
79 79 "mergeable": {
80 80 "status": "<bool>",
81 81 "message": "<message>",
82 82 },
83 83 "source": {
84 84 "clone_url": "<clone_url>",
85 85 "repository": "<repository_name>",
86 86 "reference":
87 87 {
88 88 "name": "<name>",
89 89 "type": "<type>",
90 90 "commit_id": "<commit_id>",
91 91 }
92 92 },
93 93 "target": {
94 94 "clone_url": "<clone_url>",
95 95 "repository": "<repository_name>",
96 96 "reference":
97 97 {
98 98 "name": "<name>",
99 99 "type": "<type>",
100 100 "commit_id": "<commit_id>",
101 101 }
102 102 },
103 103 "merge": {
104 104 "clone_url": "<clone_url>",
105 105 "reference":
106 106 {
107 107 "name": "<name>",
108 108 "type": "<type>",
109 109 "commit_id": "<commit_id>",
110 110 }
111 111 },
112 112 "author": <user_obj>,
113 113 "reviewers": [
114 114 ...
115 115 {
116 116 "user": "<user_obj>",
117 117 "review_status": "<review_status>",
118 118 }
119 119 ...
120 120 ]
121 121 },
122 122 "error": null
123 123 """
124 get_repo_or_error(repoid)
124
125 125 pull_request = get_pull_request_or_error(pullrequestid)
126 if Optional.extract(repoid):
127 repo = get_repo_or_error(repoid)
128 else:
129 repo = pull_request.target_repo
130
126 131 if not PullRequestModel().check_user_read(
127 132 pull_request, apiuser, api=True):
128 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
133 raise JSONRPCError('repository `%s` or pull request `%s` '
134 'does not exist' % (repoid, pullrequestid))
129 135 data = pull_request.get_api_data()
130 136 return data
131 137
132 138
133 139 @jsonrpc_method()
134 140 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
135 141 """
136 142 Get all pull requests from the repository specified in `repoid`.
137 143
138 144 :param apiuser: This is filled automatically from the |authtoken|.
139 145 :type apiuser: AuthUser
140 :param repoid: Repository name or repository ID.
146 :param repoid: Optional repository name or repository ID.
141 147 :type repoid: str or int
142 148 :param status: Only return pull requests with the specified status.
143 149 Valid options are.
144 150 * ``new`` (default)
145 151 * ``open``
146 152 * ``closed``
147 153 :type status: str
148 154
149 155 Example output:
150 156
151 157 .. code-block:: bash
152 158
153 159 "id": <id_given_in_input>,
154 160 "result":
155 161 [
156 162 ...
157 163 {
158 164 "pull_request_id": "<pull_request_id>",
159 165 "url": "<url>",
160 166 "title" : "<title>",
161 167 "description": "<description>",
162 168 "status": "<status>",
163 169 "created_on": "<date_time_created>",
164 170 "updated_on": "<date_time_updated>",
165 171 "commit_ids": [
166 172 ...
167 173 "<commit_id>",
168 174 "<commit_id>",
169 175 ...
170 176 ],
171 177 "review_status": "<review_status>",
172 178 "mergeable": {
173 179 "status": "<bool>",
174 180 "message: "<message>",
175 181 },
176 182 "source": {
177 183 "clone_url": "<clone_url>",
178 184 "reference":
179 185 {
180 186 "name": "<name>",
181 187 "type": "<type>",
182 188 "commit_id": "<commit_id>",
183 189 }
184 190 },
185 191 "target": {
186 192 "clone_url": "<clone_url>",
187 193 "reference":
188 194 {
189 195 "name": "<name>",
190 196 "type": "<type>",
191 197 "commit_id": "<commit_id>",
192 198 }
193 199 },
194 200 "merge": {
195 201 "clone_url": "<clone_url>",
196 202 "reference":
197 203 {
198 204 "name": "<name>",
199 205 "type": "<type>",
200 206 "commit_id": "<commit_id>",
201 207 }
202 208 },
203 209 "author": <user_obj>,
204 210 "reviewers": [
205 211 ...
206 212 {
207 213 "user": "<user_obj>",
208 214 "review_status": "<review_status>",
209 215 }
210 216 ...
211 217 ]
212 218 }
213 219 ...
214 220 ],
215 221 "error": null
216 222
217 223 """
218 224 repo = get_repo_or_error(repoid)
219 225 if not has_superadmin_permission(apiuser):
220 226 _perms = (
221 227 'repository.admin', 'repository.write', 'repository.read',)
222 228 validate_repo_permissions(apiuser, repoid, repo, _perms)
223 229
224 230 status = Optional.extract(status)
225 231 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
226 232 data = [pr.get_api_data() for pr in pull_requests]
227 233 return data
228 234
229 235
230 236 @jsonrpc_method()
231 237 def merge_pull_request(
232 request, apiuser, repoid, pullrequestid,
238 request, apiuser, pullrequestid, repoid=Optional(None),
233 239 userid=Optional(OAttr('apiuser'))):
234 240 """
235 241 Merge the pull request specified by `pullrequestid` into its target
236 242 repository.
237 243
238 244 :param apiuser: This is filled automatically from the |authtoken|.
239 245 :type apiuser: AuthUser
240 :param repoid: The Repository name or repository ID of the
246 :param repoid: Optional, repository name or repository ID of the
241 247 target repository to which the |pr| is to be merged.
242 248 :type repoid: str or int
243 249 :param pullrequestid: ID of the pull request which shall be merged.
244 250 :type pullrequestid: int
245 251 :param userid: Merge the pull request as this user.
246 252 :type userid: Optional(str or int)
247 253
248 254 Example output:
249 255
250 256 .. code-block:: bash
251 257
252 258 "id": <id_given_in_input>,
253 259 "result": {
254 260 "executed": "<bool>",
255 261 "failure_reason": "<int>",
256 262 "merge_commit_id": "<merge_commit_id>",
257 263 "possible": "<bool>",
258 264 "merge_ref": {
259 265 "commit_id": "<commit_id>",
260 266 "type": "<type>",
261 267 "name": "<name>"
262 268 }
263 269 },
264 270 "error": null
265 271 """
272 pull_request = get_pull_request_or_error(pullrequestid)
273 if Optional.extract(repoid):
266 274 repo = get_repo_or_error(repoid)
275 else:
276 repo = pull_request.target_repo
277
267 278 if not isinstance(userid, Optional):
268 279 if (has_superadmin_permission(apiuser) or
269 280 HasRepoPermissionAnyApi('repository.admin')(
270 281 user=apiuser, repo_name=repo.repo_name)):
271 282 apiuser = get_user_or_error(userid)
272 283 else:
273 284 raise JSONRPCError('userid is not the same as your user')
274 285
275 pull_request = get_pull_request_or_error(pullrequestid)
276
277 286 check = MergeCheck.validate(
278 287 pull_request, user=apiuser, translator=request.translate)
279 288 merge_possible = not check.failed
280 289
281 290 if not merge_possible:
282 291 error_messages = []
283 292 for err_type, error_msg in check.errors:
284 293 error_msg = request.translate(error_msg)
285 294 error_messages.append(error_msg)
286 295
287 296 reasons = ','.join(error_messages)
288 297 raise JSONRPCError(
289 298 'merge not possible for following reasons: {}'.format(reasons))
290 299
291 300 target_repo = pull_request.target_repo
292 301 extras = vcs_operation_context(
293 302 request.environ, repo_name=target_repo.repo_name,
294 303 username=apiuser.username, action='push',
295 304 scm=target_repo.repo_type)
296 305 merge_response = PullRequestModel().merge(
297 306 pull_request, apiuser, extras=extras)
298 307 if merge_response.executed:
299 308 PullRequestModel().close_pull_request(
300 309 pull_request.pull_request_id, apiuser)
301 310
302 311 Session().commit()
303 312
304 313 # In previous versions the merge response directly contained the merge
305 314 # commit id. It is now contained in the merge reference object. To be
306 315 # backwards compatible we have to extract it again.
307 316 merge_response = merge_response._asdict()
308 317 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
309 318
310 319 return merge_response
311 320
312 321
313 322 @jsonrpc_method()
314 323 def get_pull_request_comments(
315 324 request, apiuser, pullrequestid, repoid=Optional(None)):
316 325 """
317 326 Get all comments of pull request specified with the `pullrequestid`
318 327
319 328 :param apiuser: This is filled automatically from the |authtoken|.
320 329 :type apiuser: AuthUser
321 330 :param repoid: Optional repository name or repository ID.
322 331 :type repoid: str or int
323 332 :param pullrequestid: The pull request ID.
324 333 :type pullrequestid: int
325 334
326 335 Example output:
327 336
328 337 .. code-block:: bash
329 338
330 339 id : <id_given_in_input>
331 340 result : [
332 341 {
333 342 "comment_author": {
334 343 "active": true,
335 344 "full_name_or_username": "Tom Gore",
336 345 "username": "admin"
337 346 },
338 347 "comment_created_on": "2017-01-02T18:43:45.533",
339 348 "comment_f_path": null,
340 349 "comment_id": 25,
341 350 "comment_lineno": null,
342 351 "comment_status": {
343 352 "status": "under_review",
344 353 "status_lbl": "Under Review"
345 354 },
346 355 "comment_text": "Example text",
347 356 "comment_type": null,
348 357 "pull_request_version": null
349 358 }
350 359 ],
351 360 error : null
352 361 """
353 362
354 363 pull_request = get_pull_request_or_error(pullrequestid)
355 364 if Optional.extract(repoid):
356 365 repo = get_repo_or_error(repoid)
357 366 else:
358 367 repo = pull_request.target_repo
359 368
360 369 if not PullRequestModel().check_user_read(
361 370 pull_request, apiuser, api=True):
362 371 raise JSONRPCError('repository `%s` or pull request `%s` '
363 372 'does not exist' % (repoid, pullrequestid))
364 373
365 374 (pull_request_latest,
366 375 pull_request_at_ver,
367 376 pull_request_display_obj,
368 377 at_version) = PullRequestModel().get_pr_version(
369 378 pull_request.pull_request_id, version=None)
370 379
371 380 versions = pull_request_display_obj.versions()
372 381 ver_map = {
373 382 ver.pull_request_version_id: cnt
374 383 for cnt, ver in enumerate(versions, 1)
375 384 }
376 385
377 386 # GENERAL COMMENTS with versions #
378 387 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
379 388 q = q.order_by(ChangesetComment.comment_id.asc())
380 389 general_comments = q.all()
381 390
382 391 # INLINE COMMENTS with versions #
383 392 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
384 393 q = q.order_by(ChangesetComment.comment_id.asc())
385 394 inline_comments = q.all()
386 395
387 396 data = []
388 397 for comment in inline_comments + general_comments:
389 398 full_data = comment.get_api_data()
390 399 pr_version_id = None
391 400 if comment.pull_request_version_id:
392 401 pr_version_id = 'v{}'.format(
393 402 ver_map[comment.pull_request_version_id])
394 403
395 404 # sanitize some entries
396 405
397 406 full_data['pull_request_version'] = pr_version_id
398 407 full_data['comment_author'] = {
399 408 'username': full_data['comment_author'].username,
400 409 'full_name_or_username': full_data['comment_author'].full_name_or_username,
401 410 'active': full_data['comment_author'].active,
402 411 }
403 412
404 413 if full_data['comment_status']:
405 414 full_data['comment_status'] = {
406 415 'status': full_data['comment_status'][0].status,
407 416 'status_lbl': full_data['comment_status'][0].status_lbl,
408 417 }
409 418 else:
410 419 full_data['comment_status'] = {}
411 420
412 421 data.append(full_data)
413 422 return data
414 423
415 424
416 425 @jsonrpc_method()
417 426 def comment_pull_request(
418 request, apiuser, repoid, pullrequestid, message=Optional(None),
419 commit_id=Optional(None), status=Optional(None),
427 request, apiuser, pullrequestid, repoid=Optional(None),
428 message=Optional(None), commit_id=Optional(None), status=Optional(None),
420 429 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
421 430 resolves_comment_id=Optional(None),
422 431 userid=Optional(OAttr('apiuser'))):
423 432 """
424 433 Comment on the pull request specified with the `pullrequestid`,
425 434 in the |repo| specified by the `repoid`, and optionally change the
426 435 review status.
427 436
428 437 :param apiuser: This is filled automatically from the |authtoken|.
429 438 :type apiuser: AuthUser
430 :param repoid: The repository name or repository ID.
439 :param repoid: Optional repository name or repository ID.
431 440 :type repoid: str or int
432 441 :param pullrequestid: The pull request ID.
433 442 :type pullrequestid: int
434 443 :param commit_id: Specify the commit_id for which to set a comment. If
435 444 given commit_id is different than latest in the PR status
436 445 change won't be performed.
437 446 :type commit_id: str
438 447 :param message: The text content of the comment.
439 448 :type message: str
440 449 :param status: (**Optional**) Set the approval status of the pull
441 450 request. One of: 'not_reviewed', 'approved', 'rejected',
442 451 'under_review'
443 452 :type status: str
444 453 :param comment_type: Comment type, one of: 'note', 'todo'
445 454 :type comment_type: Optional(str), default: 'note'
446 455 :param userid: Comment on the pull request as this user
447 456 :type userid: Optional(str or int)
448 457
449 458 Example output:
450 459
451 460 .. code-block:: bash
452 461
453 462 id : <id_given_in_input>
454 463 result : {
455 464 "pull_request_id": "<Integer>",
456 465 "comment_id": "<Integer>",
457 466 "status": {"given": <given_status>,
458 467 "was_changed": <bool status_was_actually_changed> },
459 468 },
460 469 error : null
461 470 """
471 pull_request = get_pull_request_or_error(pullrequestid)
472 if Optional.extract(repoid):
462 473 repo = get_repo_or_error(repoid)
474 else:
475 repo = pull_request.target_repo
476
463 477 if not isinstance(userid, Optional):
464 478 if (has_superadmin_permission(apiuser) or
465 479 HasRepoPermissionAnyApi('repository.admin')(
466 480 user=apiuser, repo_name=repo.repo_name)):
467 481 apiuser = get_user_or_error(userid)
468 482 else:
469 483 raise JSONRPCError('userid is not the same as your user')
470 484
471 pull_request = get_pull_request_or_error(pullrequestid)
472 485 if not PullRequestModel().check_user_read(
473 486 pull_request, apiuser, api=True):
474 487 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
475 488 message = Optional.extract(message)
476 489 status = Optional.extract(status)
477 490 commit_id = Optional.extract(commit_id)
478 491 comment_type = Optional.extract(comment_type)
479 492 resolves_comment_id = Optional.extract(resolves_comment_id)
480 493
481 494 if not message and not status:
482 495 raise JSONRPCError(
483 496 'Both message and status parameters are missing. '
484 497 'At least one is required.')
485 498
486 499 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
487 500 status is not None):
488 501 raise JSONRPCError('Unknown comment status: `%s`' % status)
489 502
490 503 if commit_id and commit_id not in pull_request.revisions:
491 504 raise JSONRPCError(
492 505 'Invalid commit_id `%s` for this pull request.' % commit_id)
493 506
494 507 allowed_to_change_status = PullRequestModel().check_user_change_status(
495 508 pull_request, apiuser)
496 509
497 510 # if commit_id is passed re-validated if user is allowed to change status
498 511 # based on latest commit_id from the PR
499 512 if commit_id:
500 513 commit_idx = pull_request.revisions.index(commit_id)
501 514 if commit_idx != 0:
502 515 allowed_to_change_status = False
503 516
504 517 if resolves_comment_id:
505 518 comment = ChangesetComment.get(resolves_comment_id)
506 519 if not comment:
507 520 raise JSONRPCError(
508 521 'Invalid resolves_comment_id `%s` for this pull request.'
509 522 % resolves_comment_id)
510 523 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
511 524 raise JSONRPCError(
512 525 'Comment `%s` is wrong type for setting status to resolved.'
513 526 % resolves_comment_id)
514 527
515 528 text = message
516 529 status_label = ChangesetStatus.get_status_lbl(status)
517 530 if status and allowed_to_change_status:
518 531 st_message = ('Status change %(transition_icon)s %(status)s'
519 532 % {'transition_icon': '>', 'status': status_label})
520 533 text = message or st_message
521 534
522 535 rc_config = SettingsModel().get_all_settings()
523 536 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
524 537
525 538 status_change = status and allowed_to_change_status
526 539 comment = CommentsModel().create(
527 540 text=text,
528 541 repo=pull_request.target_repo.repo_id,
529 542 user=apiuser.user_id,
530 543 pull_request=pull_request.pull_request_id,
531 544 f_path=None,
532 545 line_no=None,
533 546 status_change=(status_label if status_change else None),
534 547 status_change_type=(status if status_change else None),
535 548 closing_pr=False,
536 549 renderer=renderer,
537 550 comment_type=comment_type,
538 551 resolves_comment_id=resolves_comment_id
539 552 )
540 553
541 554 if allowed_to_change_status and status:
542 555 ChangesetStatusModel().set_status(
543 556 pull_request.target_repo.repo_id,
544 557 status,
545 558 apiuser.user_id,
546 559 comment,
547 560 pull_request=pull_request.pull_request_id
548 561 )
549 562 Session().flush()
550 563
551 564 Session().commit()
552 565 data = {
553 566 'pull_request_id': pull_request.pull_request_id,
554 567 'comment_id': comment.comment_id if comment else None,
555 568 'status': {'given': status, 'was_changed': status_change},
556 569 }
557 570 return data
558 571
559 572
560 573 @jsonrpc_method()
561 574 def create_pull_request(
562 575 request, apiuser, source_repo, target_repo, source_ref, target_ref,
563 576 title, description=Optional(''), reviewers=Optional(None)):
564 577 """
565 578 Creates a new pull request.
566 579
567 580 Accepts refs in the following formats:
568 581
569 582 * branch:<branch_name>:<sha>
570 583 * branch:<branch_name>
571 584 * bookmark:<bookmark_name>:<sha> (Mercurial only)
572 585 * bookmark:<bookmark_name> (Mercurial only)
573 586
574 587 :param apiuser: This is filled automatically from the |authtoken|.
575 588 :type apiuser: AuthUser
576 589 :param source_repo: Set the source repository name.
577 590 :type source_repo: str
578 591 :param target_repo: Set the target repository name.
579 592 :type target_repo: str
580 593 :param source_ref: Set the source ref name.
581 594 :type source_ref: str
582 595 :param target_ref: Set the target ref name.
583 596 :type target_ref: str
584 597 :param title: Set the pull request title.
585 598 :type title: str
586 599 :param description: Set the pull request description.
587 600 :type description: Optional(str)
588 601 :param reviewers: Set the new pull request reviewers list.
589 602 Reviewer defined by review rules will be added automatically to the
590 603 defined list.
591 604 :type reviewers: Optional(list)
592 605 Accepts username strings or objects of the format:
593 606
594 607 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
595 608 """
596 609
597 610 source_db_repo = get_repo_or_error(source_repo)
598 611 target_db_repo = get_repo_or_error(target_repo)
599 612 if not has_superadmin_permission(apiuser):
600 613 _perms = ('repository.admin', 'repository.write', 'repository.read',)
601 614 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
602 615
603 616 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
604 617 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
605 618 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
606 619 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
607 620 source_scm = source_db_repo.scm_instance()
608 621 target_scm = target_db_repo.scm_instance()
609 622
610 623 commit_ranges = target_scm.compare(
611 624 target_commit.raw_id, source_commit.raw_id, source_scm,
612 625 merge=True, pre_load=[])
613 626
614 627 ancestor = target_scm.get_common_ancestor(
615 628 target_commit.raw_id, source_commit.raw_id, source_scm)
616 629
617 630 if not commit_ranges:
618 631 raise JSONRPCError('no commits found')
619 632
620 633 if not ancestor:
621 634 raise JSONRPCError('no common ancestor found')
622 635
623 636 reviewer_objects = Optional.extract(reviewers) or []
624 637
625 638 if reviewer_objects:
626 639 schema = ReviewerListSchema()
627 640 try:
628 641 reviewer_objects = schema.deserialize(reviewer_objects)
629 642 except Invalid as err:
630 643 raise JSONRPCValidationError(colander_exc=err)
631 644
632 645 # validate users
633 646 for reviewer_object in reviewer_objects:
634 647 user = get_user_or_error(reviewer_object['username'])
635 648 reviewer_object['user_id'] = user.user_id
636 649
637 650 get_default_reviewers_data, get_validated_reviewers = \
638 651 PullRequestModel().get_reviewer_functions()
639 652
640 653 reviewer_rules = get_default_reviewers_data(
641 654 apiuser.get_instance(), source_db_repo,
642 655 source_commit, target_db_repo, target_commit)
643 656
644 657 # specified rules are later re-validated, thus we can assume users will
645 658 # eventually provide those that meet the reviewer criteria.
646 659 if not reviewer_objects:
647 660 reviewer_objects = reviewer_rules['reviewers']
648 661
649 662 try:
650 663 reviewers = get_validated_reviewers(
651 664 reviewer_objects, reviewer_rules)
652 665 except ValueError as e:
653 666 raise JSONRPCError('Reviewers Validation: {}'.format(e))
654 667
655 668 pull_request_model = PullRequestModel()
656 669 pull_request = pull_request_model.create(
657 670 created_by=apiuser.user_id,
658 671 source_repo=source_repo,
659 672 source_ref=full_source_ref,
660 673 target_repo=target_repo,
661 674 target_ref=full_target_ref,
662 675 revisions=reversed(
663 676 [commit.raw_id for commit in reversed(commit_ranges)]),
664 677 reviewers=reviewers,
665 678 title=title,
666 679 description=Optional.extract(description)
667 680 )
668 681
669 682 Session().commit()
670 683 data = {
671 684 'msg': 'Created new pull request `{}`'.format(title),
672 685 'pull_request_id': pull_request.pull_request_id,
673 686 }
674 687 return data
675 688
676 689
677 690 @jsonrpc_method()
678 691 def update_pull_request(
679 request, apiuser, repoid, pullrequestid, title=Optional(''),
680 description=Optional(''), reviewers=Optional(None),
692 request, apiuser, pullrequestid, repoid=Optional(None),
693 title=Optional(''), description=Optional(''), reviewers=Optional(None),
681 694 update_commits=Optional(None)):
682 695 """
683 696 Updates a pull request.
684 697
685 698 :param apiuser: This is filled automatically from the |authtoken|.
686 699 :type apiuser: AuthUser
687 :param repoid: The repository name or repository ID.
700 :param repoid: Optional repository name or repository ID.
688 701 :type repoid: str or int
689 702 :param pullrequestid: The pull request ID.
690 703 :type pullrequestid: int
691 704 :param title: Set the pull request title.
692 705 :type title: str
693 706 :param description: Update pull request description.
694 707 :type description: Optional(str)
695 708 :param reviewers: Update pull request reviewers list with new value.
696 709 :type reviewers: Optional(list)
697 710 Accepts username strings or objects of the format:
698 711
699 712 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
700 713
701 714 :param update_commits: Trigger update of commits for this pull request
702 715 :type: update_commits: Optional(bool)
703 716
704 717 Example output:
705 718
706 719 .. code-block:: bash
707 720
708 721 id : <id_given_in_input>
709 722 result : {
710 723 "msg": "Updated pull request `63`",
711 724 "pull_request": <pull_request_object>,
712 725 "updated_reviewers": {
713 726 "added": [
714 727 "username"
715 728 ],
716 729 "removed": []
717 730 },
718 731 "updated_commits": {
719 732 "added": [
720 733 "<sha1_hash>"
721 734 ],
722 735 "common": [
723 736 "<sha1_hash>",
724 737 "<sha1_hash>",
725 738 ],
726 739 "removed": []
727 740 }
728 741 }
729 742 error : null
730 743 """
731 744
745 pull_request = get_pull_request_or_error(pullrequestid)
746 if Optional.extract(repoid):
732 747 repo = get_repo_or_error(repoid)
733 pull_request = get_pull_request_or_error(pullrequestid)
748 else:
749 repo = pull_request.target_repo
750
734 751 if not PullRequestModel().check_user_update(
735 752 pull_request, apiuser, api=True):
736 753 raise JSONRPCError(
737 754 'pull request `%s` update failed, no permission to update.' % (
738 755 pullrequestid,))
739 756 if pull_request.is_closed():
740 757 raise JSONRPCError(
741 758 'pull request `%s` update failed, pull request is closed' % (
742 759 pullrequestid,))
743 760
744 761 reviewer_objects = Optional.extract(reviewers) or []
745 762
746 763 if reviewer_objects:
747 764 schema = ReviewerListSchema()
748 765 try:
749 766 reviewer_objects = schema.deserialize(reviewer_objects)
750 767 except Invalid as err:
751 768 raise JSONRPCValidationError(colander_exc=err)
752 769
753 770 # validate users
754 771 for reviewer_object in reviewer_objects:
755 772 user = get_user_or_error(reviewer_object['username'])
756 773 reviewer_object['user_id'] = user.user_id
757 774
758 775 get_default_reviewers_data, get_validated_reviewers = \
759 776 PullRequestModel().get_reviewer_functions()
760 777
761 778 # re-use stored rules
762 779 reviewer_rules = pull_request.reviewer_data
763 780 try:
764 781 reviewers = get_validated_reviewers(
765 782 reviewer_objects, reviewer_rules)
766 783 except ValueError as e:
767 784 raise JSONRPCError('Reviewers Validation: {}'.format(e))
768 785 else:
769 786 reviewers = []
770 787
771 788 title = Optional.extract(title)
772 789 description = Optional.extract(description)
773 790 if title or description:
774 791 PullRequestModel().edit(
775 792 pull_request, title or pull_request.title,
776 793 description or pull_request.description, apiuser)
777 794 Session().commit()
778 795
779 796 commit_changes = {"added": [], "common": [], "removed": []}
780 797 if str2bool(Optional.extract(update_commits)):
781 798 if PullRequestModel().has_valid_update_type(pull_request):
782 799 update_response = PullRequestModel().update_commits(
783 800 pull_request)
784 801 commit_changes = update_response.changes or commit_changes
785 802 Session().commit()
786 803
787 804 reviewers_changes = {"added": [], "removed": []}
788 805 if reviewers:
789 806 added_reviewers, removed_reviewers = \
790 807 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
791 808
792 809 reviewers_changes['added'] = sorted(
793 810 [get_user_or_error(n).username for n in added_reviewers])
794 811 reviewers_changes['removed'] = sorted(
795 812 [get_user_or_error(n).username for n in removed_reviewers])
796 813 Session().commit()
797 814
798 815 data = {
799 816 'msg': 'Updated pull request `{}`'.format(
800 817 pull_request.pull_request_id),
801 818 'pull_request': pull_request.get_api_data(),
802 819 'updated_commits': commit_changes,
803 820 'updated_reviewers': reviewers_changes
804 821 }
805 822
806 823 return data
807 824
808 825
809 826 @jsonrpc_method()
810 827 def close_pull_request(
811 request, apiuser, repoid, pullrequestid,
828 request, apiuser, pullrequestid, repoid=Optional(None),
812 829 userid=Optional(OAttr('apiuser')), message=Optional('')):
813 830 """
814 831 Close the pull request specified by `pullrequestid`.
815 832
816 833 :param apiuser: This is filled automatically from the |authtoken|.
817 834 :type apiuser: AuthUser
818 835 :param repoid: Repository name or repository ID to which the pull
819 836 request belongs.
820 837 :type repoid: str or int
821 838 :param pullrequestid: ID of the pull request to be closed.
822 839 :type pullrequestid: int
823 840 :param userid: Close the pull request as this user.
824 841 :type userid: Optional(str or int)
825 842 :param message: Optional message to close the Pull Request with. If not
826 843 specified it will be generated automatically.
827 844 :type message: Optional(str)
828 845
829 846 Example output:
830 847
831 848 .. code-block:: bash
832 849
833 850 "id": <id_given_in_input>,
834 851 "result": {
835 852 "pull_request_id": "<int>",
836 853 "close_status": "<str:status_lbl>,
837 854 "closed": "<bool>"
838 855 },
839 856 "error": null
840 857
841 858 """
842 859 _ = request.translate
843 860
861 pull_request = get_pull_request_or_error(pullrequestid)
862 if Optional.extract(repoid):
844 863 repo = get_repo_or_error(repoid)
864 else:
865 repo = pull_request.target_repo
866
845 867 if not isinstance(userid, Optional):
846 868 if (has_superadmin_permission(apiuser) or
847 869 HasRepoPermissionAnyApi('repository.admin')(
848 870 user=apiuser, repo_name=repo.repo_name)):
849 871 apiuser = get_user_or_error(userid)
850 872 else:
851 873 raise JSONRPCError('userid is not the same as your user')
852 874
853 pull_request = get_pull_request_or_error(pullrequestid)
854
855 875 if pull_request.is_closed():
856 876 raise JSONRPCError(
857 877 'pull request `%s` is already closed' % (pullrequestid,))
858 878
859 879 # only owner or admin or person with write permissions
860 880 allowed_to_close = PullRequestModel().check_user_update(
861 881 pull_request, apiuser, api=True)
862 882
863 883 if not allowed_to_close:
864 884 raise JSONRPCError(
865 885 'pull request `%s` close failed, no permission to close.' % (
866 886 pullrequestid,))
867 887
868 888 # message we're using to close the PR, else it's automatically generated
869 889 message = Optional.extract(message)
870 890
871 891 # finally close the PR, with proper message comment
872 892 comment, status = PullRequestModel().close_pull_request_with_comment(
873 893 pull_request, apiuser, repo, message=message)
874 894 status_lbl = ChangesetStatus.get_status_lbl(status)
875 895
876 896 Session().commit()
877 897
878 898 data = {
879 899 'pull_request_id': pull_request.pull_request_id,
880 900 'close_status': status_lbl,
881 901 'closed': True,
882 902 }
883 903 return data
General Comments 0
You need to be logged in to leave comments. Login now