##// END OF EJS Templates
api: Include merge reference into API data of a PR.
Martin Bornhold -
r1054:122c7b13 default
parent child Browse files
Show More

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

@@ -1,666 +1,678 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 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.api import jsonrpc_method, JSONRPCError
24 from rhodecode.api import jsonrpc_method, JSONRPCError
25 from rhodecode.api.utils import (
25 from rhodecode.api.utils import (
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
28 has_repo_permissions, resolve_ref_or_error)
28 has_repo_permissions, resolve_ref_or_error)
29 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
29 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
30 from rhodecode.lib.base import vcs_operation_context
30 from rhodecode.lib.base import vcs_operation_context
31 from rhodecode.lib.utils2 import str2bool
31 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.model.changeset_status import ChangesetStatusModel
32 from rhodecode.model.changeset_status import ChangesetStatusModel
33 from rhodecode.model.comment import ChangesetCommentsModel
33 from rhodecode.model.comment import ChangesetCommentsModel
34 from rhodecode.model.db import Session, ChangesetStatus
34 from rhodecode.model.db import Session, ChangesetStatus
35 from rhodecode.model.pull_request import PullRequestModel
35 from rhodecode.model.pull_request import PullRequestModel
36 from rhodecode.model.settings import SettingsModel
36 from rhodecode.model.settings import SettingsModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 @jsonrpc_method()
41 @jsonrpc_method()
42 def get_pull_request(request, apiuser, repoid, pullrequestid):
42 def get_pull_request(request, apiuser, repoid, pullrequestid):
43 """
43 """
44 Get a pull request based on the given ID.
44 Get a pull request based on the given ID.
45
45
46 :param apiuser: This is filled automatically from the |authtoken|.
46 :param apiuser: This is filled automatically from the |authtoken|.
47 :type apiuser: AuthUser
47 :type apiuser: AuthUser
48 :param repoid: Repository name or repository ID from where the pull
48 :param repoid: Repository name or repository ID from where the pull
49 request was opened.
49 request was opened.
50 :type repoid: str or int
50 :type repoid: str or int
51 :param pullrequestid: ID of the requested pull request.
51 :param pullrequestid: ID of the requested pull request.
52 :type pullrequestid: int
52 :type pullrequestid: int
53
53
54 Example output:
54 Example output:
55
55
56 .. code-block:: bash
56 .. code-block:: bash
57
57
58 "id": <id_given_in_input>,
58 "id": <id_given_in_input>,
59 "result":
59 "result":
60 {
60 {
61 "pull_request_id": "<pull_request_id>",
61 "pull_request_id": "<pull_request_id>",
62 "url": "<url>",
62 "url": "<url>",
63 "title": "<title>",
63 "title": "<title>",
64 "description": "<description>",
64 "description": "<description>",
65 "status" : "<status>",
65 "status" : "<status>",
66 "created_on": "<date_time_created>",
66 "created_on": "<date_time_created>",
67 "updated_on": "<date_time_updated>",
67 "updated_on": "<date_time_updated>",
68 "commit_ids": [
68 "commit_ids": [
69 ...
69 ...
70 "<commit_id>",
70 "<commit_id>",
71 "<commit_id>",
71 "<commit_id>",
72 ...
72 ...
73 ],
73 ],
74 "review_status": "<review_status>",
74 "review_status": "<review_status>",
75 "mergeable": {
75 "mergeable": {
76 "status": "<bool>",
76 "status": "<bool>",
77 "message": "<message>",
77 "message": "<message>",
78 },
78 },
79 "source": {
79 "source": {
80 "clone_url": "<clone_url>",
80 "clone_url": "<clone_url>",
81 "repository": "<repository_name>",
81 "repository": "<repository_name>",
82 "reference":
82 "reference":
83 {
83 {
84 "name": "<name>",
84 "name": "<name>",
85 "type": "<type>",
85 "type": "<type>",
86 "commit_id": "<commit_id>",
86 "commit_id": "<commit_id>",
87 }
87 }
88 },
88 },
89 "target": {
89 "target": {
90 "clone_url": "<clone_url>",
90 "clone_url": "<clone_url>",
91 "repository": "<repository_name>",
91 "repository": "<repository_name>",
92 "reference":
92 "reference":
93 {
93 {
94 "name": "<name>",
94 "name": "<name>",
95 "type": "<type>",
95 "type": "<type>",
96 "commit_id": "<commit_id>",
96 "commit_id": "<commit_id>",
97 }
97 }
98 },
98 },
99 "shadow": {
99 "merge": {
100 "clone_url": "<clone_url>",
100 "clone_url": "<clone_url>",
101 "reference":
102 {
103 "name": "<name>",
104 "type": "<type>",
105 "commit_id": "<commit_id>",
106 }
101 },
107 },
102 "author": <user_obj>,
108 "author": <user_obj>,
103 "reviewers": [
109 "reviewers": [
104 ...
110 ...
105 {
111 {
106 "user": "<user_obj>",
112 "user": "<user_obj>",
107 "review_status": "<review_status>",
113 "review_status": "<review_status>",
108 }
114 }
109 ...
115 ...
110 ]
116 ]
111 },
117 },
112 "error": null
118 "error": null
113 """
119 """
114 get_repo_or_error(repoid)
120 get_repo_or_error(repoid)
115 pull_request = get_pull_request_or_error(pullrequestid)
121 pull_request = get_pull_request_or_error(pullrequestid)
116 if not PullRequestModel().check_user_read(
122 if not PullRequestModel().check_user_read(
117 pull_request, apiuser, api=True):
123 pull_request, apiuser, api=True):
118 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
124 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
119 data = pull_request.get_api_data()
125 data = pull_request.get_api_data()
120 return data
126 return data
121
127
122
128
123 @jsonrpc_method()
129 @jsonrpc_method()
124 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
130 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
125 """
131 """
126 Get all pull requests from the repository specified in `repoid`.
132 Get all pull requests from the repository specified in `repoid`.
127
133
128 :param apiuser: This is filled automatically from the |authtoken|.
134 :param apiuser: This is filled automatically from the |authtoken|.
129 :type apiuser: AuthUser
135 :type apiuser: AuthUser
130 :param repoid: Repository name or repository ID.
136 :param repoid: Repository name or repository ID.
131 :type repoid: str or int
137 :type repoid: str or int
132 :param status: Only return pull requests with the specified status.
138 :param status: Only return pull requests with the specified status.
133 Valid options are.
139 Valid options are.
134 * ``new`` (default)
140 * ``new`` (default)
135 * ``open``
141 * ``open``
136 * ``closed``
142 * ``closed``
137 :type status: str
143 :type status: str
138
144
139 Example output:
145 Example output:
140
146
141 .. code-block:: bash
147 .. code-block:: bash
142
148
143 "id": <id_given_in_input>,
149 "id": <id_given_in_input>,
144 "result":
150 "result":
145 [
151 [
146 ...
152 ...
147 {
153 {
148 "pull_request_id": "<pull_request_id>",
154 "pull_request_id": "<pull_request_id>",
149 "url": "<url>",
155 "url": "<url>",
150 "title" : "<title>",
156 "title" : "<title>",
151 "description": "<description>",
157 "description": "<description>",
152 "status": "<status>",
158 "status": "<status>",
153 "created_on": "<date_time_created>",
159 "created_on": "<date_time_created>",
154 "updated_on": "<date_time_updated>",
160 "updated_on": "<date_time_updated>",
155 "commit_ids": [
161 "commit_ids": [
156 ...
162 ...
157 "<commit_id>",
163 "<commit_id>",
158 "<commit_id>",
164 "<commit_id>",
159 ...
165 ...
160 ],
166 ],
161 "review_status": "<review_status>",
167 "review_status": "<review_status>",
162 "mergeable": {
168 "mergeable": {
163 "status": "<bool>",
169 "status": "<bool>",
164 "message: "<message>",
170 "message: "<message>",
165 },
171 },
166 "source": {
172 "source": {
167 "clone_url": "<clone_url>",
173 "clone_url": "<clone_url>",
168 "reference":
174 "reference":
169 {
175 {
170 "name": "<name>",
176 "name": "<name>",
171 "type": "<type>",
177 "type": "<type>",
172 "commit_id": "<commit_id>",
178 "commit_id": "<commit_id>",
173 }
179 }
174 },
180 },
175 "target": {
181 "target": {
176 "clone_url": "<clone_url>",
182 "clone_url": "<clone_url>",
177 "reference":
183 "reference":
178 {
184 {
179 "name": "<name>",
185 "name": "<name>",
180 "type": "<type>",
186 "type": "<type>",
181 "commit_id": "<commit_id>",
187 "commit_id": "<commit_id>",
182 }
188 }
183 },
189 },
184 "shadow": {
190 "merge": {
185 "clone_url": "<clone_url>",
191 "clone_url": "<clone_url>",
192 "reference":
193 {
194 "name": "<name>",
195 "type": "<type>",
196 "commit_id": "<commit_id>",
197 }
186 },
198 },
187 "author": <user_obj>,
199 "author": <user_obj>,
188 "reviewers": [
200 "reviewers": [
189 ...
201 ...
190 {
202 {
191 "user": "<user_obj>",
203 "user": "<user_obj>",
192 "review_status": "<review_status>",
204 "review_status": "<review_status>",
193 }
205 }
194 ...
206 ...
195 ]
207 ]
196 }
208 }
197 ...
209 ...
198 ],
210 ],
199 "error": null
211 "error": null
200
212
201 """
213 """
202 repo = get_repo_or_error(repoid)
214 repo = get_repo_or_error(repoid)
203 if not has_superadmin_permission(apiuser):
215 if not has_superadmin_permission(apiuser):
204 _perms = (
216 _perms = (
205 'repository.admin', 'repository.write', 'repository.read',)
217 'repository.admin', 'repository.write', 'repository.read',)
206 has_repo_permissions(apiuser, repoid, repo, _perms)
218 has_repo_permissions(apiuser, repoid, repo, _perms)
207
219
208 status = Optional.extract(status)
220 status = Optional.extract(status)
209 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
221 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
210 data = [pr.get_api_data() for pr in pull_requests]
222 data = [pr.get_api_data() for pr in pull_requests]
211 return data
223 return data
212
224
213
225
214 @jsonrpc_method()
226 @jsonrpc_method()
215 def merge_pull_request(request, apiuser, repoid, pullrequestid,
227 def merge_pull_request(request, apiuser, repoid, pullrequestid,
216 userid=Optional(OAttr('apiuser'))):
228 userid=Optional(OAttr('apiuser'))):
217 """
229 """
218 Merge the pull request specified by `pullrequestid` into its target
230 Merge the pull request specified by `pullrequestid` into its target
219 repository.
231 repository.
220
232
221 :param apiuser: This is filled automatically from the |authtoken|.
233 :param apiuser: This is filled automatically from the |authtoken|.
222 :type apiuser: AuthUser
234 :type apiuser: AuthUser
223 :param repoid: The Repository name or repository ID of the
235 :param repoid: The Repository name or repository ID of the
224 target repository to which the |pr| is to be merged.
236 target repository to which the |pr| is to be merged.
225 :type repoid: str or int
237 :type repoid: str or int
226 :param pullrequestid: ID of the pull request which shall be merged.
238 :param pullrequestid: ID of the pull request which shall be merged.
227 :type pullrequestid: int
239 :type pullrequestid: int
228 :param userid: Merge the pull request as this user.
240 :param userid: Merge the pull request as this user.
229 :type userid: Optional(str or int)
241 :type userid: Optional(str or int)
230
242
231 Example output:
243 Example output:
232
244
233 .. code-block:: bash
245 .. code-block:: bash
234
246
235 "id": <id_given_in_input>,
247 "id": <id_given_in_input>,
236 "result":
248 "result":
237 {
249 {
238 "executed": "<bool>",
250 "executed": "<bool>",
239 "failure_reason": "<int>",
251 "failure_reason": "<int>",
240 "merge_commit_id": "<merge_commit_id>",
252 "merge_commit_id": "<merge_commit_id>",
241 "possible": "<bool>"
253 "possible": "<bool>"
242 },
254 },
243 "error": null
255 "error": null
244
256
245 """
257 """
246 repo = get_repo_or_error(repoid)
258 repo = get_repo_or_error(repoid)
247 if not isinstance(userid, Optional):
259 if not isinstance(userid, Optional):
248 if (has_superadmin_permission(apiuser) or
260 if (has_superadmin_permission(apiuser) or
249 HasRepoPermissionAnyApi('repository.admin')(
261 HasRepoPermissionAnyApi('repository.admin')(
250 user=apiuser, repo_name=repo.repo_name)):
262 user=apiuser, repo_name=repo.repo_name)):
251 apiuser = get_user_or_error(userid)
263 apiuser = get_user_or_error(userid)
252 else:
264 else:
253 raise JSONRPCError('userid is not the same as your user')
265 raise JSONRPCError('userid is not the same as your user')
254
266
255 pull_request = get_pull_request_or_error(pullrequestid)
267 pull_request = get_pull_request_or_error(pullrequestid)
256 if not PullRequestModel().check_user_merge(
268 if not PullRequestModel().check_user_merge(
257 pull_request, apiuser, api=True):
269 pull_request, apiuser, api=True):
258 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
270 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
259 if pull_request.is_closed():
271 if pull_request.is_closed():
260 raise JSONRPCError(
272 raise JSONRPCError(
261 'pull request `%s` merge failed, pull request is closed' % (
273 'pull request `%s` merge failed, pull request is closed' % (
262 pullrequestid,))
274 pullrequestid,))
263
275
264 target_repo = pull_request.target_repo
276 target_repo = pull_request.target_repo
265 extras = vcs_operation_context(
277 extras = vcs_operation_context(
266 request.environ, repo_name=target_repo.repo_name,
278 request.environ, repo_name=target_repo.repo_name,
267 username=apiuser.username, action='push',
279 username=apiuser.username, action='push',
268 scm=target_repo.repo_type)
280 scm=target_repo.repo_type)
269 data = PullRequestModel().merge(pull_request, apiuser, extras=extras)
281 data = PullRequestModel().merge(pull_request, apiuser, extras=extras)
270 if data.executed:
282 if data.executed:
271 PullRequestModel().close_pull_request(
283 PullRequestModel().close_pull_request(
272 pull_request.pull_request_id, apiuser)
284 pull_request.pull_request_id, apiuser)
273
285
274 Session().commit()
286 Session().commit()
275 return data
287 return data
276
288
277
289
278 @jsonrpc_method()
290 @jsonrpc_method()
279 def close_pull_request(request, apiuser, repoid, pullrequestid,
291 def close_pull_request(request, apiuser, repoid, pullrequestid,
280 userid=Optional(OAttr('apiuser'))):
292 userid=Optional(OAttr('apiuser'))):
281 """
293 """
282 Close the pull request specified by `pullrequestid`.
294 Close the pull request specified by `pullrequestid`.
283
295
284 :param apiuser: This is filled automatically from the |authtoken|.
296 :param apiuser: This is filled automatically from the |authtoken|.
285 :type apiuser: AuthUser
297 :type apiuser: AuthUser
286 :param repoid: Repository name or repository ID to which the pull
298 :param repoid: Repository name or repository ID to which the pull
287 request belongs.
299 request belongs.
288 :type repoid: str or int
300 :type repoid: str or int
289 :param pullrequestid: ID of the pull request to be closed.
301 :param pullrequestid: ID of the pull request to be closed.
290 :type pullrequestid: int
302 :type pullrequestid: int
291 :param userid: Close the pull request as this user.
303 :param userid: Close the pull request as this user.
292 :type userid: Optional(str or int)
304 :type userid: Optional(str or int)
293
305
294 Example output:
306 Example output:
295
307
296 .. code-block:: bash
308 .. code-block:: bash
297
309
298 "id": <id_given_in_input>,
310 "id": <id_given_in_input>,
299 "result":
311 "result":
300 {
312 {
301 "pull_request_id": "<int>",
313 "pull_request_id": "<int>",
302 "closed": "<bool>"
314 "closed": "<bool>"
303 },
315 },
304 "error": null
316 "error": null
305
317
306 """
318 """
307 repo = get_repo_or_error(repoid)
319 repo = get_repo_or_error(repoid)
308 if not isinstance(userid, Optional):
320 if not isinstance(userid, Optional):
309 if (has_superadmin_permission(apiuser) or
321 if (has_superadmin_permission(apiuser) or
310 HasRepoPermissionAnyApi('repository.admin')(
322 HasRepoPermissionAnyApi('repository.admin')(
311 user=apiuser, repo_name=repo.repo_name)):
323 user=apiuser, repo_name=repo.repo_name)):
312 apiuser = get_user_or_error(userid)
324 apiuser = get_user_or_error(userid)
313 else:
325 else:
314 raise JSONRPCError('userid is not the same as your user')
326 raise JSONRPCError('userid is not the same as your user')
315
327
316 pull_request = get_pull_request_or_error(pullrequestid)
328 pull_request = get_pull_request_or_error(pullrequestid)
317 if not PullRequestModel().check_user_update(
329 if not PullRequestModel().check_user_update(
318 pull_request, apiuser, api=True):
330 pull_request, apiuser, api=True):
319 raise JSONRPCError(
331 raise JSONRPCError(
320 'pull request `%s` close failed, no permission to close.' % (
332 'pull request `%s` close failed, no permission to close.' % (
321 pullrequestid,))
333 pullrequestid,))
322 if pull_request.is_closed():
334 if pull_request.is_closed():
323 raise JSONRPCError(
335 raise JSONRPCError(
324 'pull request `%s` is already closed' % (pullrequestid,))
336 'pull request `%s` is already closed' % (pullrequestid,))
325
337
326 PullRequestModel().close_pull_request(
338 PullRequestModel().close_pull_request(
327 pull_request.pull_request_id, apiuser)
339 pull_request.pull_request_id, apiuser)
328 Session().commit()
340 Session().commit()
329 data = {
341 data = {
330 'pull_request_id': pull_request.pull_request_id,
342 'pull_request_id': pull_request.pull_request_id,
331 'closed': True,
343 'closed': True,
332 }
344 }
333 return data
345 return data
334
346
335
347
336 @jsonrpc_method()
348 @jsonrpc_method()
337 def comment_pull_request(request, apiuser, repoid, pullrequestid,
349 def comment_pull_request(request, apiuser, repoid, pullrequestid,
338 message=Optional(None), status=Optional(None),
350 message=Optional(None), status=Optional(None),
339 userid=Optional(OAttr('apiuser'))):
351 userid=Optional(OAttr('apiuser'))):
340 """
352 """
341 Comment on the pull request specified with the `pullrequestid`,
353 Comment on the pull request specified with the `pullrequestid`,
342 in the |repo| specified by the `repoid`, and optionally change the
354 in the |repo| specified by the `repoid`, and optionally change the
343 review status.
355 review status.
344
356
345 :param apiuser: This is filled automatically from the |authtoken|.
357 :param apiuser: This is filled automatically from the |authtoken|.
346 :type apiuser: AuthUser
358 :type apiuser: AuthUser
347 :param repoid: The repository name or repository ID.
359 :param repoid: The repository name or repository ID.
348 :type repoid: str or int
360 :type repoid: str or int
349 :param pullrequestid: The pull request ID.
361 :param pullrequestid: The pull request ID.
350 :type pullrequestid: int
362 :type pullrequestid: int
351 :param message: The text content of the comment.
363 :param message: The text content of the comment.
352 :type message: str
364 :type message: str
353 :param status: (**Optional**) Set the approval status of the pull
365 :param status: (**Optional**) Set the approval status of the pull
354 request. Valid options are:
366 request. Valid options are:
355 * not_reviewed
367 * not_reviewed
356 * approved
368 * approved
357 * rejected
369 * rejected
358 * under_review
370 * under_review
359 :type status: str
371 :type status: str
360 :param userid: Comment on the pull request as this user
372 :param userid: Comment on the pull request as this user
361 :type userid: Optional(str or int)
373 :type userid: Optional(str or int)
362
374
363 Example output:
375 Example output:
364
376
365 .. code-block:: bash
377 .. code-block:: bash
366
378
367 id : <id_given_in_input>
379 id : <id_given_in_input>
368 result :
380 result :
369 {
381 {
370 "pull_request_id": "<Integer>",
382 "pull_request_id": "<Integer>",
371 "comment_id": "<Integer>"
383 "comment_id": "<Integer>"
372 }
384 }
373 error : null
385 error : null
374 """
386 """
375 repo = get_repo_or_error(repoid)
387 repo = get_repo_or_error(repoid)
376 if not isinstance(userid, Optional):
388 if not isinstance(userid, Optional):
377 if (has_superadmin_permission(apiuser) or
389 if (has_superadmin_permission(apiuser) or
378 HasRepoPermissionAnyApi('repository.admin')(
390 HasRepoPermissionAnyApi('repository.admin')(
379 user=apiuser, repo_name=repo.repo_name)):
391 user=apiuser, repo_name=repo.repo_name)):
380 apiuser = get_user_or_error(userid)
392 apiuser = get_user_or_error(userid)
381 else:
393 else:
382 raise JSONRPCError('userid is not the same as your user')
394 raise JSONRPCError('userid is not the same as your user')
383
395
384 pull_request = get_pull_request_or_error(pullrequestid)
396 pull_request = get_pull_request_or_error(pullrequestid)
385 if not PullRequestModel().check_user_read(
397 if not PullRequestModel().check_user_read(
386 pull_request, apiuser, api=True):
398 pull_request, apiuser, api=True):
387 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
399 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
388 message = Optional.extract(message)
400 message = Optional.extract(message)
389 status = Optional.extract(status)
401 status = Optional.extract(status)
390 if not message and not status:
402 if not message and not status:
391 raise JSONRPCError('message and status parameter missing')
403 raise JSONRPCError('message and status parameter missing')
392
404
393 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
405 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
394 status is not None):
406 status is not None):
395 raise JSONRPCError('unknown comment status`%s`' % status)
407 raise JSONRPCError('unknown comment status`%s`' % status)
396
408
397 allowed_to_change_status = PullRequestModel().check_user_change_status(
409 allowed_to_change_status = PullRequestModel().check_user_change_status(
398 pull_request, apiuser)
410 pull_request, apiuser)
399 text = message
411 text = message
400 if status and allowed_to_change_status:
412 if status and allowed_to_change_status:
401 st_message = (('Status change %(transition_icon)s %(status)s')
413 st_message = (('Status change %(transition_icon)s %(status)s')
402 % {'transition_icon': '>',
414 % {'transition_icon': '>',
403 'status': ChangesetStatus.get_status_lbl(status)})
415 'status': ChangesetStatus.get_status_lbl(status)})
404 text = message or st_message
416 text = message or st_message
405
417
406 rc_config = SettingsModel().get_all_settings()
418 rc_config = SettingsModel().get_all_settings()
407 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
419 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
408 comment = ChangesetCommentsModel().create(
420 comment = ChangesetCommentsModel().create(
409 text=text,
421 text=text,
410 repo=pull_request.target_repo.repo_id,
422 repo=pull_request.target_repo.repo_id,
411 user=apiuser.user_id,
423 user=apiuser.user_id,
412 pull_request=pull_request.pull_request_id,
424 pull_request=pull_request.pull_request_id,
413 f_path=None,
425 f_path=None,
414 line_no=None,
426 line_no=None,
415 status_change=(ChangesetStatus.get_status_lbl(status)
427 status_change=(ChangesetStatus.get_status_lbl(status)
416 if status and allowed_to_change_status else None),
428 if status and allowed_to_change_status else None),
417 status_change_type=(status
429 status_change_type=(status
418 if status and allowed_to_change_status else None),
430 if status and allowed_to_change_status else None),
419 closing_pr=False,
431 closing_pr=False,
420 renderer=renderer
432 renderer=renderer
421 )
433 )
422
434
423 if allowed_to_change_status and status:
435 if allowed_to_change_status and status:
424 ChangesetStatusModel().set_status(
436 ChangesetStatusModel().set_status(
425 pull_request.target_repo.repo_id,
437 pull_request.target_repo.repo_id,
426 status,
438 status,
427 apiuser.user_id,
439 apiuser.user_id,
428 comment,
440 comment,
429 pull_request=pull_request.pull_request_id
441 pull_request=pull_request.pull_request_id
430 )
442 )
431 Session().flush()
443 Session().flush()
432
444
433 Session().commit()
445 Session().commit()
434 data = {
446 data = {
435 'pull_request_id': pull_request.pull_request_id,
447 'pull_request_id': pull_request.pull_request_id,
436 'comment_id': comment.comment_id,
448 'comment_id': comment.comment_id,
437 'status': status
449 'status': status
438 }
450 }
439 return data
451 return data
440
452
441
453
442 @jsonrpc_method()
454 @jsonrpc_method()
443 def create_pull_request(
455 def create_pull_request(
444 request, apiuser, source_repo, target_repo, source_ref, target_ref,
456 request, apiuser, source_repo, target_repo, source_ref, target_ref,
445 title, description=Optional(''), reviewers=Optional(None)):
457 title, description=Optional(''), reviewers=Optional(None)):
446 """
458 """
447 Creates a new pull request.
459 Creates a new pull request.
448
460
449 Accepts refs in the following formats:
461 Accepts refs in the following formats:
450
462
451 * branch:<branch_name>:<sha>
463 * branch:<branch_name>:<sha>
452 * branch:<branch_name>
464 * branch:<branch_name>
453 * bookmark:<bookmark_name>:<sha> (Mercurial only)
465 * bookmark:<bookmark_name>:<sha> (Mercurial only)
454 * bookmark:<bookmark_name> (Mercurial only)
466 * bookmark:<bookmark_name> (Mercurial only)
455
467
456 :param apiuser: This is filled automatically from the |authtoken|.
468 :param apiuser: This is filled automatically from the |authtoken|.
457 :type apiuser: AuthUser
469 :type apiuser: AuthUser
458 :param source_repo: Set the source repository name.
470 :param source_repo: Set the source repository name.
459 :type source_repo: str
471 :type source_repo: str
460 :param target_repo: Set the target repository name.
472 :param target_repo: Set the target repository name.
461 :type target_repo: str
473 :type target_repo: str
462 :param source_ref: Set the source ref name.
474 :param source_ref: Set the source ref name.
463 :type source_ref: str
475 :type source_ref: str
464 :param target_ref: Set the target ref name.
476 :param target_ref: Set the target ref name.
465 :type target_ref: str
477 :type target_ref: str
466 :param title: Set the pull request title.
478 :param title: Set the pull request title.
467 :type title: str
479 :type title: str
468 :param description: Set the pull request description.
480 :param description: Set the pull request description.
469 :type description: Optional(str)
481 :type description: Optional(str)
470 :param reviewers: Set the new pull request reviewers list.
482 :param reviewers: Set the new pull request reviewers list.
471 :type reviewers: Optional(list)
483 :type reviewers: Optional(list)
472 Accepts username strings or objects of the format:
484 Accepts username strings or objects of the format:
473 {
485 {
474 'username': 'nick', 'reasons': ['original author']
486 'username': 'nick', 'reasons': ['original author']
475 }
487 }
476 """
488 """
477
489
478 source = get_repo_or_error(source_repo)
490 source = get_repo_or_error(source_repo)
479 target = get_repo_or_error(target_repo)
491 target = get_repo_or_error(target_repo)
480 if not has_superadmin_permission(apiuser):
492 if not has_superadmin_permission(apiuser):
481 _perms = ('repository.admin', 'repository.write', 'repository.read',)
493 _perms = ('repository.admin', 'repository.write', 'repository.read',)
482 has_repo_permissions(apiuser, source_repo, source, _perms)
494 has_repo_permissions(apiuser, source_repo, source, _perms)
483
495
484 full_source_ref = resolve_ref_or_error(source_ref, source)
496 full_source_ref = resolve_ref_or_error(source_ref, source)
485 full_target_ref = resolve_ref_or_error(target_ref, target)
497 full_target_ref = resolve_ref_or_error(target_ref, target)
486 source_commit = get_commit_or_error(full_source_ref, source)
498 source_commit = get_commit_or_error(full_source_ref, source)
487 target_commit = get_commit_or_error(full_target_ref, target)
499 target_commit = get_commit_or_error(full_target_ref, target)
488 source_scm = source.scm_instance()
500 source_scm = source.scm_instance()
489 target_scm = target.scm_instance()
501 target_scm = target.scm_instance()
490
502
491 commit_ranges = target_scm.compare(
503 commit_ranges = target_scm.compare(
492 target_commit.raw_id, source_commit.raw_id, source_scm,
504 target_commit.raw_id, source_commit.raw_id, source_scm,
493 merge=True, pre_load=[])
505 merge=True, pre_load=[])
494
506
495 ancestor = target_scm.get_common_ancestor(
507 ancestor = target_scm.get_common_ancestor(
496 target_commit.raw_id, source_commit.raw_id, source_scm)
508 target_commit.raw_id, source_commit.raw_id, source_scm)
497
509
498 if not commit_ranges:
510 if not commit_ranges:
499 raise JSONRPCError('no commits found')
511 raise JSONRPCError('no commits found')
500
512
501 if not ancestor:
513 if not ancestor:
502 raise JSONRPCError('no common ancestor found')
514 raise JSONRPCError('no common ancestor found')
503
515
504 reviewer_objects = Optional.extract(reviewers) or []
516 reviewer_objects = Optional.extract(reviewers) or []
505 if not isinstance(reviewer_objects, list):
517 if not isinstance(reviewer_objects, list):
506 raise JSONRPCError('reviewers should be specified as a list')
518 raise JSONRPCError('reviewers should be specified as a list')
507
519
508 reviewers_reasons = []
520 reviewers_reasons = []
509 for reviewer_object in reviewer_objects:
521 for reviewer_object in reviewer_objects:
510 reviewer_reasons = []
522 reviewer_reasons = []
511 if isinstance(reviewer_object, (basestring, int)):
523 if isinstance(reviewer_object, (basestring, int)):
512 reviewer_username = reviewer_object
524 reviewer_username = reviewer_object
513 else:
525 else:
514 reviewer_username = reviewer_object['username']
526 reviewer_username = reviewer_object['username']
515 reviewer_reasons = reviewer_object.get('reasons', [])
527 reviewer_reasons = reviewer_object.get('reasons', [])
516
528
517 user = get_user_or_error(reviewer_username)
529 user = get_user_or_error(reviewer_username)
518 reviewers_reasons.append((user.user_id, reviewer_reasons))
530 reviewers_reasons.append((user.user_id, reviewer_reasons))
519
531
520 pull_request_model = PullRequestModel()
532 pull_request_model = PullRequestModel()
521 pull_request = pull_request_model.create(
533 pull_request = pull_request_model.create(
522 created_by=apiuser.user_id,
534 created_by=apiuser.user_id,
523 source_repo=source_repo,
535 source_repo=source_repo,
524 source_ref=full_source_ref,
536 source_ref=full_source_ref,
525 target_repo=target_repo,
537 target_repo=target_repo,
526 target_ref=full_target_ref,
538 target_ref=full_target_ref,
527 revisions=reversed(
539 revisions=reversed(
528 [commit.raw_id for commit in reversed(commit_ranges)]),
540 [commit.raw_id for commit in reversed(commit_ranges)]),
529 reviewers=reviewers_reasons,
541 reviewers=reviewers_reasons,
530 title=title,
542 title=title,
531 description=Optional.extract(description)
543 description=Optional.extract(description)
532 )
544 )
533
545
534 Session().commit()
546 Session().commit()
535 data = {
547 data = {
536 'msg': 'Created new pull request `{}`'.format(title),
548 'msg': 'Created new pull request `{}`'.format(title),
537 'pull_request_id': pull_request.pull_request_id,
549 'pull_request_id': pull_request.pull_request_id,
538 }
550 }
539 return data
551 return data
540
552
541
553
542 @jsonrpc_method()
554 @jsonrpc_method()
543 def update_pull_request(
555 def update_pull_request(
544 request, apiuser, repoid, pullrequestid, title=Optional(''),
556 request, apiuser, repoid, pullrequestid, title=Optional(''),
545 description=Optional(''), reviewers=Optional(None),
557 description=Optional(''), reviewers=Optional(None),
546 update_commits=Optional(None), close_pull_request=Optional(None)):
558 update_commits=Optional(None), close_pull_request=Optional(None)):
547 """
559 """
548 Updates a pull request.
560 Updates a pull request.
549
561
550 :param apiuser: This is filled automatically from the |authtoken|.
562 :param apiuser: This is filled automatically from the |authtoken|.
551 :type apiuser: AuthUser
563 :type apiuser: AuthUser
552 :param repoid: The repository name or repository ID.
564 :param repoid: The repository name or repository ID.
553 :type repoid: str or int
565 :type repoid: str or int
554 :param pullrequestid: The pull request ID.
566 :param pullrequestid: The pull request ID.
555 :type pullrequestid: int
567 :type pullrequestid: int
556 :param title: Set the pull request title.
568 :param title: Set the pull request title.
557 :type title: str
569 :type title: str
558 :param description: Update pull request description.
570 :param description: Update pull request description.
559 :type description: Optional(str)
571 :type description: Optional(str)
560 :param reviewers: Update pull request reviewers list with new value.
572 :param reviewers: Update pull request reviewers list with new value.
561 :type reviewers: Optional(list)
573 :type reviewers: Optional(list)
562 :param update_commits: Trigger update of commits for this pull request
574 :param update_commits: Trigger update of commits for this pull request
563 :type: update_commits: Optional(bool)
575 :type: update_commits: Optional(bool)
564 :param close_pull_request: Close this pull request with rejected state
576 :param close_pull_request: Close this pull request with rejected state
565 :type: close_pull_request: Optional(bool)
577 :type: close_pull_request: Optional(bool)
566
578
567 Example output:
579 Example output:
568
580
569 .. code-block:: bash
581 .. code-block:: bash
570
582
571 id : <id_given_in_input>
583 id : <id_given_in_input>
572 result :
584 result :
573 {
585 {
574 "msg": "Updated pull request `63`",
586 "msg": "Updated pull request `63`",
575 "pull_request": <pull_request_object>,
587 "pull_request": <pull_request_object>,
576 "updated_reviewers": {
588 "updated_reviewers": {
577 "added": [
589 "added": [
578 "username"
590 "username"
579 ],
591 ],
580 "removed": []
592 "removed": []
581 },
593 },
582 "updated_commits": {
594 "updated_commits": {
583 "added": [
595 "added": [
584 "<sha1_hash>"
596 "<sha1_hash>"
585 ],
597 ],
586 "common": [
598 "common": [
587 "<sha1_hash>",
599 "<sha1_hash>",
588 "<sha1_hash>",
600 "<sha1_hash>",
589 ],
601 ],
590 "removed": []
602 "removed": []
591 }
603 }
592 }
604 }
593 error : null
605 error : null
594 """
606 """
595
607
596 repo = get_repo_or_error(repoid)
608 repo = get_repo_or_error(repoid)
597 pull_request = get_pull_request_or_error(pullrequestid)
609 pull_request = get_pull_request_or_error(pullrequestid)
598 if not PullRequestModel().check_user_update(
610 if not PullRequestModel().check_user_update(
599 pull_request, apiuser, api=True):
611 pull_request, apiuser, api=True):
600 raise JSONRPCError(
612 raise JSONRPCError(
601 'pull request `%s` update failed, no permission to update.' % (
613 'pull request `%s` update failed, no permission to update.' % (
602 pullrequestid,))
614 pullrequestid,))
603 if pull_request.is_closed():
615 if pull_request.is_closed():
604 raise JSONRPCError(
616 raise JSONRPCError(
605 'pull request `%s` update failed, pull request is closed' % (
617 'pull request `%s` update failed, pull request is closed' % (
606 pullrequestid,))
618 pullrequestid,))
607
619
608 reviewer_objects = Optional.extract(reviewers) or []
620 reviewer_objects = Optional.extract(reviewers) or []
609 if not isinstance(reviewer_objects, list):
621 if not isinstance(reviewer_objects, list):
610 raise JSONRPCError('reviewers should be specified as a list')
622 raise JSONRPCError('reviewers should be specified as a list')
611
623
612 reviewers_reasons = []
624 reviewers_reasons = []
613 reviewer_ids = set()
625 reviewer_ids = set()
614 for reviewer_object in reviewer_objects:
626 for reviewer_object in reviewer_objects:
615 reviewer_reasons = []
627 reviewer_reasons = []
616 if isinstance(reviewer_object, (int, basestring)):
628 if isinstance(reviewer_object, (int, basestring)):
617 reviewer_username = reviewer_object
629 reviewer_username = reviewer_object
618 else:
630 else:
619 reviewer_username = reviewer_object['username']
631 reviewer_username = reviewer_object['username']
620 reviewer_reasons = reviewer_object.get('reasons', [])
632 reviewer_reasons = reviewer_object.get('reasons', [])
621
633
622 user = get_user_or_error(reviewer_username)
634 user = get_user_or_error(reviewer_username)
623 reviewer_ids.add(user.user_id)
635 reviewer_ids.add(user.user_id)
624 reviewers_reasons.append((user.user_id, reviewer_reasons))
636 reviewers_reasons.append((user.user_id, reviewer_reasons))
625
637
626 title = Optional.extract(title)
638 title = Optional.extract(title)
627 description = Optional.extract(description)
639 description = Optional.extract(description)
628 if title or description:
640 if title or description:
629 PullRequestModel().edit(
641 PullRequestModel().edit(
630 pull_request, title or pull_request.title,
642 pull_request, title or pull_request.title,
631 description or pull_request.description)
643 description or pull_request.description)
632 Session().commit()
644 Session().commit()
633
645
634 commit_changes = {"added": [], "common": [], "removed": []}
646 commit_changes = {"added": [], "common": [], "removed": []}
635 if str2bool(Optional.extract(update_commits)):
647 if str2bool(Optional.extract(update_commits)):
636 if PullRequestModel().has_valid_update_type(pull_request):
648 if PullRequestModel().has_valid_update_type(pull_request):
637 _version, _commit_changes = PullRequestModel().update_commits(
649 _version, _commit_changes = PullRequestModel().update_commits(
638 pull_request)
650 pull_request)
639 commit_changes = _commit_changes or commit_changes
651 commit_changes = _commit_changes or commit_changes
640 Session().commit()
652 Session().commit()
641
653
642 reviewers_changes = {"added": [], "removed": []}
654 reviewers_changes = {"added": [], "removed": []}
643 if reviewer_ids:
655 if reviewer_ids:
644 added_reviewers, removed_reviewers = \
656 added_reviewers, removed_reviewers = \
645 PullRequestModel().update_reviewers(pull_request, reviewers_reasons)
657 PullRequestModel().update_reviewers(pull_request, reviewers_reasons)
646
658
647 reviewers_changes['added'] = sorted(
659 reviewers_changes['added'] = sorted(
648 [get_user_or_error(n).username for n in added_reviewers])
660 [get_user_or_error(n).username for n in added_reviewers])
649 reviewers_changes['removed'] = sorted(
661 reviewers_changes['removed'] = sorted(
650 [get_user_or_error(n).username for n in removed_reviewers])
662 [get_user_or_error(n).username for n in removed_reviewers])
651 Session().commit()
663 Session().commit()
652
664
653 if str2bool(Optional.extract(close_pull_request)):
665 if str2bool(Optional.extract(close_pull_request)):
654 PullRequestModel().close_pull_request_with_comment(
666 PullRequestModel().close_pull_request_with_comment(
655 pull_request, apiuser, repo)
667 pull_request, apiuser, repo)
656 Session().commit()
668 Session().commit()
657
669
658 data = {
670 data = {
659 'msg': 'Updated pull request `{}`'.format(
671 'msg': 'Updated pull request `{}`'.format(
660 pull_request.pull_request_id),
672 pull_request.pull_request_id),
661 'pull_request': pull_request.get_api_data(),
673 'pull_request': pull_request.get_api_data(),
662 'updated_commits': commit_changes,
674 'updated_commits': commit_changes,
663 'updated_reviewers': reviewers_changes
675 'updated_reviewers': reviewers_changes
664 }
676 }
665 return data
677 return data
666
678
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now