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