##// END OF EJS Templates
api: add send_email flag for comments api to allow commenting without email notification....
bart -
r4196:bb0e450f stable
parent child Browse files
Show More
@@ -1,60 +1,61 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
22 import pytest
23
23
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestGetMethod(object):
28 class TestGetMethod(object):
29 def test_get_methods_no_matches(self):
29 def test_get_methods_no_matches(self):
30 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
30 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
31 response = api_call(self.app, params)
31 response = api_call(self.app, params)
32
32
33 expected = []
33 expected = []
34 assert_ok(id_, expected, given=response.body)
34 assert_ok(id_, expected, given=response.body)
35
35
36 def test_get_methods(self):
36 def test_get_methods(self):
37 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
37 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 expected = ['changeset_comment', 'comment_pull_request',
40 expected = ['changeset_comment', 'comment_pull_request',
41 'get_pull_request_comments', 'comment_commit', 'get_repo_comments']
41 'get_pull_request_comments', 'comment_commit', 'get_repo_comments']
42 assert_ok(id_, expected, given=response.body)
42 assert_ok(id_, expected, given=response.body)
43
43
44 def test_get_methods_on_single_match(self):
44 def test_get_methods_on_single_match(self):
45 id_, params = build_data(self.apikey, 'get_method',
45 id_, params = build_data(self.apikey, 'get_method',
46 pattern='*comment_commit*')
46 pattern='*comment_commit*')
47 response = api_call(self.app, params)
47 response = api_call(self.app, params)
48
48
49 expected = ['comment_commit',
49 expected = ['comment_commit',
50 {'apiuser': '<RequiredType>',
50 {'apiuser': '<RequiredType>',
51 'comment_type': "<Optional:u'note'>",
51 'comment_type': "<Optional:u'note'>",
52 'commit_id': '<RequiredType>',
52 'commit_id': '<RequiredType>',
53 'extra_recipients': '<Optional:[]>',
53 'extra_recipients': '<Optional:[]>',
54 'message': '<RequiredType>',
54 'message': '<RequiredType>',
55 'repoid': '<RequiredType>',
55 'repoid': '<RequiredType>',
56 'request': '<RequiredType>',
56 'request': '<RequiredType>',
57 'resolves_comment_id': '<Optional:None>',
57 'resolves_comment_id': '<Optional:None>',
58 'status': '<Optional:None>',
58 'status': '<Optional:None>',
59 'userid': '<Optional:<OptionalAttr:apiuser>>'}]
59 'userid': '<Optional:<OptionalAttr:apiuser>>',
60 'send_email': '<Optional:True>'}]
60 assert_ok(id_, expected, given=response.body)
61 assert_ok(id_, expected, given=response.body)
@@ -1,1011 +1,1015 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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, validate_set_owner_permissions)
29 validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions)
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, PullRequest
35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment, PullRequest
36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
37 from rhodecode.model.settings import SettingsModel
37 from rhodecode.model.settings import SettingsModel
38 from rhodecode.model.validation_schema import Invalid
38 from rhodecode.model.validation_schema import Invalid
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, pullrequestid, repoid=Optional(None),
46 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None),
47 merge_state=Optional(False)):
47 merge_state=Optional(False)):
48 """
48 """
49 Get a pull request based on the given ID.
49 Get a pull request based on the given ID.
50
50
51 :param apiuser: This is filled automatically from the |authtoken|.
51 :param apiuser: This is filled automatically from the |authtoken|.
52 :type apiuser: AuthUser
52 :type apiuser: AuthUser
53 :param repoid: Optional, repository name or repository ID from where
53 :param repoid: Optional, repository name or repository ID from where
54 the pull request was opened.
54 the pull request was opened.
55 :type repoid: str or int
55 :type repoid: str or int
56 :param pullrequestid: ID of the requested pull request.
56 :param pullrequestid: ID of the requested pull request.
57 :type pullrequestid: int
57 :type pullrequestid: int
58 :param merge_state: Optional calculate merge state for each repository.
58 :param merge_state: Optional calculate merge state for each repository.
59 This could result in longer time to fetch the data
59 This could result in longer time to fetch the data
60 :type merge_state: bool
60 :type merge_state: bool
61
61
62 Example output:
62 Example output:
63
63
64 .. code-block:: bash
64 .. code-block:: bash
65
65
66 "id": <id_given_in_input>,
66 "id": <id_given_in_input>,
67 "result":
67 "result":
68 {
68 {
69 "pull_request_id": "<pull_request_id>",
69 "pull_request_id": "<pull_request_id>",
70 "url": "<url>",
70 "url": "<url>",
71 "title": "<title>",
71 "title": "<title>",
72 "description": "<description>",
72 "description": "<description>",
73 "status" : "<status>",
73 "status" : "<status>",
74 "created_on": "<date_time_created>",
74 "created_on": "<date_time_created>",
75 "updated_on": "<date_time_updated>",
75 "updated_on": "<date_time_updated>",
76 "commit_ids": [
76 "commit_ids": [
77 ...
77 ...
78 "<commit_id>",
78 "<commit_id>",
79 "<commit_id>",
79 "<commit_id>",
80 ...
80 ...
81 ],
81 ],
82 "review_status": "<review_status>",
82 "review_status": "<review_status>",
83 "mergeable": {
83 "mergeable": {
84 "status": "<bool>",
84 "status": "<bool>",
85 "message": "<message>",
85 "message": "<message>",
86 },
86 },
87 "source": {
87 "source": {
88 "clone_url": "<clone_url>",
88 "clone_url": "<clone_url>",
89 "repository": "<repository_name>",
89 "repository": "<repository_name>",
90 "reference":
90 "reference":
91 {
91 {
92 "name": "<name>",
92 "name": "<name>",
93 "type": "<type>",
93 "type": "<type>",
94 "commit_id": "<commit_id>",
94 "commit_id": "<commit_id>",
95 }
95 }
96 },
96 },
97 "target": {
97 "target": {
98 "clone_url": "<clone_url>",
98 "clone_url": "<clone_url>",
99 "repository": "<repository_name>",
99 "repository": "<repository_name>",
100 "reference":
100 "reference":
101 {
101 {
102 "name": "<name>",
102 "name": "<name>",
103 "type": "<type>",
103 "type": "<type>",
104 "commit_id": "<commit_id>",
104 "commit_id": "<commit_id>",
105 }
105 }
106 },
106 },
107 "merge": {
107 "merge": {
108 "clone_url": "<clone_url>",
108 "clone_url": "<clone_url>",
109 "reference":
109 "reference":
110 {
110 {
111 "name": "<name>",
111 "name": "<name>",
112 "type": "<type>",
112 "type": "<type>",
113 "commit_id": "<commit_id>",
113 "commit_id": "<commit_id>",
114 }
114 }
115 },
115 },
116 "author": <user_obj>,
116 "author": <user_obj>,
117 "reviewers": [
117 "reviewers": [
118 ...
118 ...
119 {
119 {
120 "user": "<user_obj>",
120 "user": "<user_obj>",
121 "review_status": "<review_status>",
121 "review_status": "<review_status>",
122 }
122 }
123 ...
123 ...
124 ]
124 ]
125 },
125 },
126 "error": null
126 "error": null
127 """
127 """
128
128
129 pull_request = get_pull_request_or_error(pullrequestid)
129 pull_request = get_pull_request_or_error(pullrequestid)
130 if Optional.extract(repoid):
130 if Optional.extract(repoid):
131 repo = get_repo_or_error(repoid)
131 repo = get_repo_or_error(repoid)
132 else:
132 else:
133 repo = pull_request.target_repo
133 repo = pull_request.target_repo
134
134
135 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
135 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
136 raise JSONRPCError('repository `%s` or pull request `%s` '
136 raise JSONRPCError('repository `%s` or pull request `%s` '
137 'does not exist' % (repoid, pullrequestid))
137 'does not exist' % (repoid, pullrequestid))
138
138
139 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
139 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
140 # otherwise we can lock the repo on calculation of merge state while update/merge
140 # otherwise we can lock the repo on calculation of merge state while update/merge
141 # is happening.
141 # is happening.
142 pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED
142 pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED
143 merge_state = Optional.extract(merge_state, binary=True) and pr_created
143 merge_state = Optional.extract(merge_state, binary=True) and pr_created
144 data = pull_request.get_api_data(with_merge_state=merge_state)
144 data = pull_request.get_api_data(with_merge_state=merge_state)
145 return data
145 return data
146
146
147
147
148 @jsonrpc_method()
148 @jsonrpc_method()
149 def get_pull_requests(request, apiuser, repoid, status=Optional('new'),
149 def get_pull_requests(request, apiuser, repoid, status=Optional('new'),
150 merge_state=Optional(False)):
150 merge_state=Optional(False)):
151 """
151 """
152 Get all pull requests from the repository specified in `repoid`.
152 Get all pull requests from the repository specified in `repoid`.
153
153
154 :param apiuser: This is filled automatically from the |authtoken|.
154 :param apiuser: This is filled automatically from the |authtoken|.
155 :type apiuser: AuthUser
155 :type apiuser: AuthUser
156 :param repoid: Optional repository name or repository ID.
156 :param repoid: Optional repository name or repository ID.
157 :type repoid: str or int
157 :type repoid: str or int
158 :param status: Only return pull requests with the specified status.
158 :param status: Only return pull requests with the specified status.
159 Valid options are.
159 Valid options are.
160 * ``new`` (default)
160 * ``new`` (default)
161 * ``open``
161 * ``open``
162 * ``closed``
162 * ``closed``
163 :type status: str
163 :type status: str
164 :param merge_state: Optional calculate merge state for each repository.
164 :param merge_state: Optional calculate merge state for each repository.
165 This could result in longer time to fetch the data
165 This could result in longer time to fetch the data
166 :type merge_state: bool
166 :type merge_state: bool
167
167
168 Example output:
168 Example output:
169
169
170 .. code-block:: bash
170 .. code-block:: bash
171
171
172 "id": <id_given_in_input>,
172 "id": <id_given_in_input>,
173 "result":
173 "result":
174 [
174 [
175 ...
175 ...
176 {
176 {
177 "pull_request_id": "<pull_request_id>",
177 "pull_request_id": "<pull_request_id>",
178 "url": "<url>",
178 "url": "<url>",
179 "title" : "<title>",
179 "title" : "<title>",
180 "description": "<description>",
180 "description": "<description>",
181 "status": "<status>",
181 "status": "<status>",
182 "created_on": "<date_time_created>",
182 "created_on": "<date_time_created>",
183 "updated_on": "<date_time_updated>",
183 "updated_on": "<date_time_updated>",
184 "commit_ids": [
184 "commit_ids": [
185 ...
185 ...
186 "<commit_id>",
186 "<commit_id>",
187 "<commit_id>",
187 "<commit_id>",
188 ...
188 ...
189 ],
189 ],
190 "review_status": "<review_status>",
190 "review_status": "<review_status>",
191 "mergeable": {
191 "mergeable": {
192 "status": "<bool>",
192 "status": "<bool>",
193 "message: "<message>",
193 "message: "<message>",
194 },
194 },
195 "source": {
195 "source": {
196 "clone_url": "<clone_url>",
196 "clone_url": "<clone_url>",
197 "reference":
197 "reference":
198 {
198 {
199 "name": "<name>",
199 "name": "<name>",
200 "type": "<type>",
200 "type": "<type>",
201 "commit_id": "<commit_id>",
201 "commit_id": "<commit_id>",
202 }
202 }
203 },
203 },
204 "target": {
204 "target": {
205 "clone_url": "<clone_url>",
205 "clone_url": "<clone_url>",
206 "reference":
206 "reference":
207 {
207 {
208 "name": "<name>",
208 "name": "<name>",
209 "type": "<type>",
209 "type": "<type>",
210 "commit_id": "<commit_id>",
210 "commit_id": "<commit_id>",
211 }
211 }
212 },
212 },
213 "merge": {
213 "merge": {
214 "clone_url": "<clone_url>",
214 "clone_url": "<clone_url>",
215 "reference":
215 "reference":
216 {
216 {
217 "name": "<name>",
217 "name": "<name>",
218 "type": "<type>",
218 "type": "<type>",
219 "commit_id": "<commit_id>",
219 "commit_id": "<commit_id>",
220 }
220 }
221 },
221 },
222 "author": <user_obj>,
222 "author": <user_obj>,
223 "reviewers": [
223 "reviewers": [
224 ...
224 ...
225 {
225 {
226 "user": "<user_obj>",
226 "user": "<user_obj>",
227 "review_status": "<review_status>",
227 "review_status": "<review_status>",
228 }
228 }
229 ...
229 ...
230 ]
230 ]
231 }
231 }
232 ...
232 ...
233 ],
233 ],
234 "error": null
234 "error": null
235
235
236 """
236 """
237 repo = get_repo_or_error(repoid)
237 repo = get_repo_or_error(repoid)
238 if not has_superadmin_permission(apiuser):
238 if not has_superadmin_permission(apiuser):
239 _perms = (
239 _perms = (
240 'repository.admin', 'repository.write', 'repository.read',)
240 'repository.admin', 'repository.write', 'repository.read',)
241 validate_repo_permissions(apiuser, repoid, repo, _perms)
241 validate_repo_permissions(apiuser, repoid, repo, _perms)
242
242
243 status = Optional.extract(status)
243 status = Optional.extract(status)
244 merge_state = Optional.extract(merge_state, binary=True)
244 merge_state = Optional.extract(merge_state, binary=True)
245 pull_requests = PullRequestModel().get_all(repo, statuses=[status],
245 pull_requests = PullRequestModel().get_all(repo, statuses=[status],
246 order_by='id', order_dir='desc')
246 order_by='id', order_dir='desc')
247 data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests]
247 data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests]
248 return data
248 return data
249
249
250
250
251 @jsonrpc_method()
251 @jsonrpc_method()
252 def merge_pull_request(
252 def merge_pull_request(
253 request, apiuser, pullrequestid, repoid=Optional(None),
253 request, apiuser, pullrequestid, repoid=Optional(None),
254 userid=Optional(OAttr('apiuser'))):
254 userid=Optional(OAttr('apiuser'))):
255 """
255 """
256 Merge the pull request specified by `pullrequestid` into its target
256 Merge the pull request specified by `pullrequestid` into its target
257 repository.
257 repository.
258
258
259 :param apiuser: This is filled automatically from the |authtoken|.
259 :param apiuser: This is filled automatically from the |authtoken|.
260 :type apiuser: AuthUser
260 :type apiuser: AuthUser
261 :param repoid: Optional, repository name or repository ID of the
261 :param repoid: Optional, repository name or repository ID of the
262 target repository to which the |pr| is to be merged.
262 target repository to which the |pr| is to be merged.
263 :type repoid: str or int
263 :type repoid: str or int
264 :param pullrequestid: ID of the pull request which shall be merged.
264 :param pullrequestid: ID of the pull request which shall be merged.
265 :type pullrequestid: int
265 :type pullrequestid: int
266 :param userid: Merge the pull request as this user.
266 :param userid: Merge the pull request as this user.
267 :type userid: Optional(str or int)
267 :type userid: Optional(str or int)
268
268
269 Example output:
269 Example output:
270
270
271 .. code-block:: bash
271 .. code-block:: bash
272
272
273 "id": <id_given_in_input>,
273 "id": <id_given_in_input>,
274 "result": {
274 "result": {
275 "executed": "<bool>",
275 "executed": "<bool>",
276 "failure_reason": "<int>",
276 "failure_reason": "<int>",
277 "merge_status_message": "<str>",
277 "merge_status_message": "<str>",
278 "merge_commit_id": "<merge_commit_id>",
278 "merge_commit_id": "<merge_commit_id>",
279 "possible": "<bool>",
279 "possible": "<bool>",
280 "merge_ref": {
280 "merge_ref": {
281 "commit_id": "<commit_id>",
281 "commit_id": "<commit_id>",
282 "type": "<type>",
282 "type": "<type>",
283 "name": "<name>"
283 "name": "<name>"
284 }
284 }
285 },
285 },
286 "error": null
286 "error": null
287 """
287 """
288 pull_request = get_pull_request_or_error(pullrequestid)
288 pull_request = get_pull_request_or_error(pullrequestid)
289 if Optional.extract(repoid):
289 if Optional.extract(repoid):
290 repo = get_repo_or_error(repoid)
290 repo = get_repo_or_error(repoid)
291 else:
291 else:
292 repo = pull_request.target_repo
292 repo = pull_request.target_repo
293 auth_user = apiuser
293 auth_user = apiuser
294 if not isinstance(userid, Optional):
294 if not isinstance(userid, Optional):
295 if (has_superadmin_permission(apiuser) or
295 if (has_superadmin_permission(apiuser) or
296 HasRepoPermissionAnyApi('repository.admin')(
296 HasRepoPermissionAnyApi('repository.admin')(
297 user=apiuser, repo_name=repo.repo_name)):
297 user=apiuser, repo_name=repo.repo_name)):
298 apiuser = get_user_or_error(userid)
298 apiuser = get_user_or_error(userid)
299 auth_user = apiuser.AuthUser()
299 auth_user = apiuser.AuthUser()
300 else:
300 else:
301 raise JSONRPCError('userid is not the same as your user')
301 raise JSONRPCError('userid is not the same as your user')
302
302
303 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
303 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
304 raise JSONRPCError(
304 raise JSONRPCError(
305 'Operation forbidden because pull request is in state {}, '
305 'Operation forbidden because pull request is in state {}, '
306 'only state {} is allowed.'.format(
306 'only state {} is allowed.'.format(
307 pull_request.pull_request_state, PullRequest.STATE_CREATED))
307 pull_request.pull_request_state, PullRequest.STATE_CREATED))
308
308
309 with pull_request.set_state(PullRequest.STATE_UPDATING):
309 with pull_request.set_state(PullRequest.STATE_UPDATING):
310 check = MergeCheck.validate(pull_request, auth_user=auth_user,
310 check = MergeCheck.validate(pull_request, auth_user=auth_user,
311 translator=request.translate)
311 translator=request.translate)
312 merge_possible = not check.failed
312 merge_possible = not check.failed
313
313
314 if not merge_possible:
314 if not merge_possible:
315 error_messages = []
315 error_messages = []
316 for err_type, error_msg in check.errors:
316 for err_type, error_msg in check.errors:
317 error_msg = request.translate(error_msg)
317 error_msg = request.translate(error_msg)
318 error_messages.append(error_msg)
318 error_messages.append(error_msg)
319
319
320 reasons = ','.join(error_messages)
320 reasons = ','.join(error_messages)
321 raise JSONRPCError(
321 raise JSONRPCError(
322 'merge not possible for following reasons: {}'.format(reasons))
322 'merge not possible for following reasons: {}'.format(reasons))
323
323
324 target_repo = pull_request.target_repo
324 target_repo = pull_request.target_repo
325 extras = vcs_operation_context(
325 extras = vcs_operation_context(
326 request.environ, repo_name=target_repo.repo_name,
326 request.environ, repo_name=target_repo.repo_name,
327 username=auth_user.username, action='push',
327 username=auth_user.username, action='push',
328 scm=target_repo.repo_type)
328 scm=target_repo.repo_type)
329 with pull_request.set_state(PullRequest.STATE_UPDATING):
329 with pull_request.set_state(PullRequest.STATE_UPDATING):
330 merge_response = PullRequestModel().merge_repo(
330 merge_response = PullRequestModel().merge_repo(
331 pull_request, apiuser, extras=extras)
331 pull_request, apiuser, extras=extras)
332 if merge_response.executed:
332 if merge_response.executed:
333 PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user)
333 PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user)
334
334
335 Session().commit()
335 Session().commit()
336
336
337 # In previous versions the merge response directly contained the merge
337 # In previous versions the merge response directly contained the merge
338 # commit id. It is now contained in the merge reference object. To be
338 # commit id. It is now contained in the merge reference object. To be
339 # backwards compatible we have to extract it again.
339 # backwards compatible we have to extract it again.
340 merge_response = merge_response.asdict()
340 merge_response = merge_response.asdict()
341 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
341 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
342
342
343 return merge_response
343 return merge_response
344
344
345
345
346 @jsonrpc_method()
346 @jsonrpc_method()
347 def get_pull_request_comments(
347 def get_pull_request_comments(
348 request, apiuser, pullrequestid, repoid=Optional(None)):
348 request, apiuser, pullrequestid, repoid=Optional(None)):
349 """
349 """
350 Get all comments of pull request specified with the `pullrequestid`
350 Get all comments of pull request specified with the `pullrequestid`
351
351
352 :param apiuser: This is filled automatically from the |authtoken|.
352 :param apiuser: This is filled automatically from the |authtoken|.
353 :type apiuser: AuthUser
353 :type apiuser: AuthUser
354 :param repoid: Optional repository name or repository ID.
354 :param repoid: Optional repository name or repository ID.
355 :type repoid: str or int
355 :type repoid: str or int
356 :param pullrequestid: The pull request ID.
356 :param pullrequestid: The pull request ID.
357 :type pullrequestid: int
357 :type pullrequestid: int
358
358
359 Example output:
359 Example output:
360
360
361 .. code-block:: bash
361 .. code-block:: bash
362
362
363 id : <id_given_in_input>
363 id : <id_given_in_input>
364 result : [
364 result : [
365 {
365 {
366 "comment_author": {
366 "comment_author": {
367 "active": true,
367 "active": true,
368 "full_name_or_username": "Tom Gore",
368 "full_name_or_username": "Tom Gore",
369 "username": "admin"
369 "username": "admin"
370 },
370 },
371 "comment_created_on": "2017-01-02T18:43:45.533",
371 "comment_created_on": "2017-01-02T18:43:45.533",
372 "comment_f_path": null,
372 "comment_f_path": null,
373 "comment_id": 25,
373 "comment_id": 25,
374 "comment_lineno": null,
374 "comment_lineno": null,
375 "comment_status": {
375 "comment_status": {
376 "status": "under_review",
376 "status": "under_review",
377 "status_lbl": "Under Review"
377 "status_lbl": "Under Review"
378 },
378 },
379 "comment_text": "Example text",
379 "comment_text": "Example text",
380 "comment_type": null,
380 "comment_type": null,
381 "pull_request_version": null
381 "pull_request_version": null
382 }
382 }
383 ],
383 ],
384 error : null
384 error : null
385 """
385 """
386
386
387 pull_request = get_pull_request_or_error(pullrequestid)
387 pull_request = get_pull_request_or_error(pullrequestid)
388 if Optional.extract(repoid):
388 if Optional.extract(repoid):
389 repo = get_repo_or_error(repoid)
389 repo = get_repo_or_error(repoid)
390 else:
390 else:
391 repo = pull_request.target_repo
391 repo = pull_request.target_repo
392
392
393 if not PullRequestModel().check_user_read(
393 if not PullRequestModel().check_user_read(
394 pull_request, apiuser, api=True):
394 pull_request, apiuser, api=True):
395 raise JSONRPCError('repository `%s` or pull request `%s` '
395 raise JSONRPCError('repository `%s` or pull request `%s` '
396 'does not exist' % (repoid, pullrequestid))
396 'does not exist' % (repoid, pullrequestid))
397
397
398 (pull_request_latest,
398 (pull_request_latest,
399 pull_request_at_ver,
399 pull_request_at_ver,
400 pull_request_display_obj,
400 pull_request_display_obj,
401 at_version) = PullRequestModel().get_pr_version(
401 at_version) = PullRequestModel().get_pr_version(
402 pull_request.pull_request_id, version=None)
402 pull_request.pull_request_id, version=None)
403
403
404 versions = pull_request_display_obj.versions()
404 versions = pull_request_display_obj.versions()
405 ver_map = {
405 ver_map = {
406 ver.pull_request_version_id: cnt
406 ver.pull_request_version_id: cnt
407 for cnt, ver in enumerate(versions, 1)
407 for cnt, ver in enumerate(versions, 1)
408 }
408 }
409
409
410 # GENERAL COMMENTS with versions #
410 # GENERAL COMMENTS with versions #
411 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
411 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
412 q = q.order_by(ChangesetComment.comment_id.asc())
412 q = q.order_by(ChangesetComment.comment_id.asc())
413 general_comments = q.all()
413 general_comments = q.all()
414
414
415 # INLINE COMMENTS with versions #
415 # INLINE COMMENTS with versions #
416 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
416 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
417 q = q.order_by(ChangesetComment.comment_id.asc())
417 q = q.order_by(ChangesetComment.comment_id.asc())
418 inline_comments = q.all()
418 inline_comments = q.all()
419
419
420 data = []
420 data = []
421 for comment in inline_comments + general_comments:
421 for comment in inline_comments + general_comments:
422 full_data = comment.get_api_data()
422 full_data = comment.get_api_data()
423 pr_version_id = None
423 pr_version_id = None
424 if comment.pull_request_version_id:
424 if comment.pull_request_version_id:
425 pr_version_id = 'v{}'.format(
425 pr_version_id = 'v{}'.format(
426 ver_map[comment.pull_request_version_id])
426 ver_map[comment.pull_request_version_id])
427
427
428 # sanitize some entries
428 # sanitize some entries
429
429
430 full_data['pull_request_version'] = pr_version_id
430 full_data['pull_request_version'] = pr_version_id
431 full_data['comment_author'] = {
431 full_data['comment_author'] = {
432 'username': full_data['comment_author'].username,
432 'username': full_data['comment_author'].username,
433 'full_name_or_username': full_data['comment_author'].full_name_or_username,
433 'full_name_or_username': full_data['comment_author'].full_name_or_username,
434 'active': full_data['comment_author'].active,
434 'active': full_data['comment_author'].active,
435 }
435 }
436
436
437 if full_data['comment_status']:
437 if full_data['comment_status']:
438 full_data['comment_status'] = {
438 full_data['comment_status'] = {
439 'status': full_data['comment_status'][0].status,
439 'status': full_data['comment_status'][0].status,
440 'status_lbl': full_data['comment_status'][0].status_lbl,
440 'status_lbl': full_data['comment_status'][0].status_lbl,
441 }
441 }
442 else:
442 else:
443 full_data['comment_status'] = {}
443 full_data['comment_status'] = {}
444
444
445 data.append(full_data)
445 data.append(full_data)
446 return data
446 return data
447
447
448
448
449 @jsonrpc_method()
449 @jsonrpc_method()
450 def comment_pull_request(
450 def comment_pull_request(
451 request, apiuser, pullrequestid, repoid=Optional(None),
451 request, apiuser, pullrequestid, repoid=Optional(None),
452 message=Optional(None), commit_id=Optional(None), status=Optional(None),
452 message=Optional(None), commit_id=Optional(None), status=Optional(None),
453 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
453 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
454 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
454 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
455 userid=Optional(OAttr('apiuser'))):
455 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
456 """
456 """
457 Comment on the pull request specified with the `pullrequestid`,
457 Comment on the pull request specified with the `pullrequestid`,
458 in the |repo| specified by the `repoid`, and optionally change the
458 in the |repo| specified by the `repoid`, and optionally change the
459 review status.
459 review status.
460
460
461 :param apiuser: This is filled automatically from the |authtoken|.
461 :param apiuser: This is filled automatically from the |authtoken|.
462 :type apiuser: AuthUser
462 :type apiuser: AuthUser
463 :param repoid: Optional repository name or repository ID.
463 :param repoid: Optional repository name or repository ID.
464 :type repoid: str or int
464 :type repoid: str or int
465 :param pullrequestid: The pull request ID.
465 :param pullrequestid: The pull request ID.
466 :type pullrequestid: int
466 :type pullrequestid: int
467 :param commit_id: Specify the commit_id for which to set a comment. If
467 :param commit_id: Specify the commit_id for which to set a comment. If
468 given commit_id is different than latest in the PR status
468 given commit_id is different than latest in the PR status
469 change won't be performed.
469 change won't be performed.
470 :type commit_id: str
470 :type commit_id: str
471 :param message: The text content of the comment.
471 :param message: The text content of the comment.
472 :type message: str
472 :type message: str
473 :param status: (**Optional**) Set the approval status of the pull
473 :param status: (**Optional**) Set the approval status of the pull
474 request. One of: 'not_reviewed', 'approved', 'rejected',
474 request. One of: 'not_reviewed', 'approved', 'rejected',
475 'under_review'
475 'under_review'
476 :type status: str
476 :type status: str
477 :param comment_type: Comment type, one of: 'note', 'todo'
477 :param comment_type: Comment type, one of: 'note', 'todo'
478 :type comment_type: Optional(str), default: 'note'
478 :type comment_type: Optional(str), default: 'note'
479 :param resolves_comment_id: id of comment which this one will resolve
479 :param resolves_comment_id: id of comment which this one will resolve
480 :type resolves_comment_id: Optional(int)
480 :type resolves_comment_id: Optional(int)
481 :param extra_recipients: list of user ids or usernames to add
481 :param extra_recipients: list of user ids or usernames to add
482 notifications for this comment. Acts like a CC for notification
482 notifications for this comment. Acts like a CC for notification
483 :type extra_recipients: Optional(list)
483 :type extra_recipients: Optional(list)
484 :param userid: Comment on the pull request as this user
484 :param userid: Comment on the pull request as this user
485 :type userid: Optional(str or int)
485 :type userid: Optional(str or int)
486 :param send_email: Define if this comment should also send email notification
487 :type send_email: Optional(bool)
486
488
487 Example output:
489 Example output:
488
490
489 .. code-block:: bash
491 .. code-block:: bash
490
492
491 id : <id_given_in_input>
493 id : <id_given_in_input>
492 result : {
494 result : {
493 "pull_request_id": "<Integer>",
495 "pull_request_id": "<Integer>",
494 "comment_id": "<Integer>",
496 "comment_id": "<Integer>",
495 "status": {"given": <given_status>,
497 "status": {"given": <given_status>,
496 "was_changed": <bool status_was_actually_changed> },
498 "was_changed": <bool status_was_actually_changed> },
497 },
499 },
498 error : null
500 error : null
499 """
501 """
500 pull_request = get_pull_request_or_error(pullrequestid)
502 pull_request = get_pull_request_or_error(pullrequestid)
501 if Optional.extract(repoid):
503 if Optional.extract(repoid):
502 repo = get_repo_or_error(repoid)
504 repo = get_repo_or_error(repoid)
503 else:
505 else:
504 repo = pull_request.target_repo
506 repo = pull_request.target_repo
505
507
506 auth_user = apiuser
508 auth_user = apiuser
507 if not isinstance(userid, Optional):
509 if not isinstance(userid, Optional):
508 if (has_superadmin_permission(apiuser) or
510 if (has_superadmin_permission(apiuser) or
509 HasRepoPermissionAnyApi('repository.admin')(
511 HasRepoPermissionAnyApi('repository.admin')(
510 user=apiuser, repo_name=repo.repo_name)):
512 user=apiuser, repo_name=repo.repo_name)):
511 apiuser = get_user_or_error(userid)
513 apiuser = get_user_or_error(userid)
512 auth_user = apiuser.AuthUser()
514 auth_user = apiuser.AuthUser()
513 else:
515 else:
514 raise JSONRPCError('userid is not the same as your user')
516 raise JSONRPCError('userid is not the same as your user')
515
517
516 if pull_request.is_closed():
518 if pull_request.is_closed():
517 raise JSONRPCError(
519 raise JSONRPCError(
518 'pull request `%s` comment failed, pull request is closed' % (
520 'pull request `%s` comment failed, pull request is closed' % (
519 pullrequestid,))
521 pullrequestid,))
520
522
521 if not PullRequestModel().check_user_read(
523 if not PullRequestModel().check_user_read(
522 pull_request, apiuser, api=True):
524 pull_request, apiuser, api=True):
523 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
525 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
524 message = Optional.extract(message)
526 message = Optional.extract(message)
525 status = Optional.extract(status)
527 status = Optional.extract(status)
526 commit_id = Optional.extract(commit_id)
528 commit_id = Optional.extract(commit_id)
527 comment_type = Optional.extract(comment_type)
529 comment_type = Optional.extract(comment_type)
528 resolves_comment_id = Optional.extract(resolves_comment_id)
530 resolves_comment_id = Optional.extract(resolves_comment_id)
529 extra_recipients = Optional.extract(extra_recipients)
531 extra_recipients = Optional.extract(extra_recipients)
532 send_email = Optional.extract(send_email, binary=True)
530
533
531 if not message and not status:
534 if not message and not status:
532 raise JSONRPCError(
535 raise JSONRPCError(
533 'Both message and status parameters are missing. '
536 'Both message and status parameters are missing. '
534 'At least one is required.')
537 'At least one is required.')
535
538
536 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
539 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
537 status is not None):
540 status is not None):
538 raise JSONRPCError('Unknown comment status: `%s`' % status)
541 raise JSONRPCError('Unknown comment status: `%s`' % status)
539
542
540 if commit_id and commit_id not in pull_request.revisions:
543 if commit_id and commit_id not in pull_request.revisions:
541 raise JSONRPCError(
544 raise JSONRPCError(
542 'Invalid commit_id `%s` for this pull request.' % commit_id)
545 'Invalid commit_id `%s` for this pull request.' % commit_id)
543
546
544 allowed_to_change_status = PullRequestModel().check_user_change_status(
547 allowed_to_change_status = PullRequestModel().check_user_change_status(
545 pull_request, apiuser)
548 pull_request, apiuser)
546
549
547 # if commit_id is passed re-validated if user is allowed to change status
550 # if commit_id is passed re-validated if user is allowed to change status
548 # based on latest commit_id from the PR
551 # based on latest commit_id from the PR
549 if commit_id:
552 if commit_id:
550 commit_idx = pull_request.revisions.index(commit_id)
553 commit_idx = pull_request.revisions.index(commit_id)
551 if commit_idx != 0:
554 if commit_idx != 0:
552 allowed_to_change_status = False
555 allowed_to_change_status = False
553
556
554 if resolves_comment_id:
557 if resolves_comment_id:
555 comment = ChangesetComment.get(resolves_comment_id)
558 comment = ChangesetComment.get(resolves_comment_id)
556 if not comment:
559 if not comment:
557 raise JSONRPCError(
560 raise JSONRPCError(
558 'Invalid resolves_comment_id `%s` for this pull request.'
561 'Invalid resolves_comment_id `%s` for this pull request.'
559 % resolves_comment_id)
562 % resolves_comment_id)
560 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
563 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
561 raise JSONRPCError(
564 raise JSONRPCError(
562 'Comment `%s` is wrong type for setting status to resolved.'
565 'Comment `%s` is wrong type for setting status to resolved.'
563 % resolves_comment_id)
566 % resolves_comment_id)
564
567
565 text = message
568 text = message
566 status_label = ChangesetStatus.get_status_lbl(status)
569 status_label = ChangesetStatus.get_status_lbl(status)
567 if status and allowed_to_change_status:
570 if status and allowed_to_change_status:
568 st_message = ('Status change %(transition_icon)s %(status)s'
571 st_message = ('Status change %(transition_icon)s %(status)s'
569 % {'transition_icon': '>', 'status': status_label})
572 % {'transition_icon': '>', 'status': status_label})
570 text = message or st_message
573 text = message or st_message
571
574
572 rc_config = SettingsModel().get_all_settings()
575 rc_config = SettingsModel().get_all_settings()
573 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
576 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
574
577
575 status_change = status and allowed_to_change_status
578 status_change = status and allowed_to_change_status
576 comment = CommentsModel().create(
579 comment = CommentsModel().create(
577 text=text,
580 text=text,
578 repo=pull_request.target_repo.repo_id,
581 repo=pull_request.target_repo.repo_id,
579 user=apiuser.user_id,
582 user=apiuser.user_id,
580 pull_request=pull_request.pull_request_id,
583 pull_request=pull_request.pull_request_id,
581 f_path=None,
584 f_path=None,
582 line_no=None,
585 line_no=None,
583 status_change=(status_label if status_change else None),
586 status_change=(status_label if status_change else None),
584 status_change_type=(status if status_change else None),
587 status_change_type=(status if status_change else None),
585 closing_pr=False,
588 closing_pr=False,
586 renderer=renderer,
589 renderer=renderer,
587 comment_type=comment_type,
590 comment_type=comment_type,
588 resolves_comment_id=resolves_comment_id,
591 resolves_comment_id=resolves_comment_id,
589 auth_user=auth_user,
592 auth_user=auth_user,
590 extra_recipients=extra_recipients
593 extra_recipients=extra_recipients,
594 send_email=send_email
591 )
595 )
592
596
593 if allowed_to_change_status and status:
597 if allowed_to_change_status and status:
594 old_calculated_status = pull_request.calculated_review_status()
598 old_calculated_status = pull_request.calculated_review_status()
595 ChangesetStatusModel().set_status(
599 ChangesetStatusModel().set_status(
596 pull_request.target_repo.repo_id,
600 pull_request.target_repo.repo_id,
597 status,
601 status,
598 apiuser.user_id,
602 apiuser.user_id,
599 comment,
603 comment,
600 pull_request=pull_request.pull_request_id
604 pull_request=pull_request.pull_request_id
601 )
605 )
602 Session().flush()
606 Session().flush()
603
607
604 Session().commit()
608 Session().commit()
605
609
606 PullRequestModel().trigger_pull_request_hook(
610 PullRequestModel().trigger_pull_request_hook(
607 pull_request, apiuser, 'comment',
611 pull_request, apiuser, 'comment',
608 data={'comment': comment})
612 data={'comment': comment})
609
613
610 if allowed_to_change_status and status:
614 if allowed_to_change_status and status:
611 # we now calculate the status of pull request, and based on that
615 # we now calculate the status of pull request, and based on that
612 # calculation we set the commits status
616 # calculation we set the commits status
613 calculated_status = pull_request.calculated_review_status()
617 calculated_status = pull_request.calculated_review_status()
614 if old_calculated_status != calculated_status:
618 if old_calculated_status != calculated_status:
615 PullRequestModel().trigger_pull_request_hook(
619 PullRequestModel().trigger_pull_request_hook(
616 pull_request, apiuser, 'review_status_change',
620 pull_request, apiuser, 'review_status_change',
617 data={'status': calculated_status})
621 data={'status': calculated_status})
618
622
619 data = {
623 data = {
620 'pull_request_id': pull_request.pull_request_id,
624 'pull_request_id': pull_request.pull_request_id,
621 'comment_id': comment.comment_id if comment else None,
625 'comment_id': comment.comment_id if comment else None,
622 'status': {'given': status, 'was_changed': status_change},
626 'status': {'given': status, 'was_changed': status_change},
623 }
627 }
624 return data
628 return data
625
629
626
630
627 @jsonrpc_method()
631 @jsonrpc_method()
628 def create_pull_request(
632 def create_pull_request(
629 request, apiuser, source_repo, target_repo, source_ref, target_ref,
633 request, apiuser, source_repo, target_repo, source_ref, target_ref,
630 owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
634 owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
631 description_renderer=Optional(''), reviewers=Optional(None)):
635 description_renderer=Optional(''), reviewers=Optional(None)):
632 """
636 """
633 Creates a new pull request.
637 Creates a new pull request.
634
638
635 Accepts refs in the following formats:
639 Accepts refs in the following formats:
636
640
637 * branch:<branch_name>:<sha>
641 * branch:<branch_name>:<sha>
638 * branch:<branch_name>
642 * branch:<branch_name>
639 * bookmark:<bookmark_name>:<sha> (Mercurial only)
643 * bookmark:<bookmark_name>:<sha> (Mercurial only)
640 * bookmark:<bookmark_name> (Mercurial only)
644 * bookmark:<bookmark_name> (Mercurial only)
641
645
642 :param apiuser: This is filled automatically from the |authtoken|.
646 :param apiuser: This is filled automatically from the |authtoken|.
643 :type apiuser: AuthUser
647 :type apiuser: AuthUser
644 :param source_repo: Set the source repository name.
648 :param source_repo: Set the source repository name.
645 :type source_repo: str
649 :type source_repo: str
646 :param target_repo: Set the target repository name.
650 :param target_repo: Set the target repository name.
647 :type target_repo: str
651 :type target_repo: str
648 :param source_ref: Set the source ref name.
652 :param source_ref: Set the source ref name.
649 :type source_ref: str
653 :type source_ref: str
650 :param target_ref: Set the target ref name.
654 :param target_ref: Set the target ref name.
651 :type target_ref: str
655 :type target_ref: str
652 :param owner: user_id or username
656 :param owner: user_id or username
653 :type owner: Optional(str)
657 :type owner: Optional(str)
654 :param title: Optionally Set the pull request title, it's generated otherwise
658 :param title: Optionally Set the pull request title, it's generated otherwise
655 :type title: str
659 :type title: str
656 :param description: Set the pull request description.
660 :param description: Set the pull request description.
657 :type description: Optional(str)
661 :type description: Optional(str)
658 :type description_renderer: Optional(str)
662 :type description_renderer: Optional(str)
659 :param description_renderer: Set pull request renderer for the description.
663 :param description_renderer: Set pull request renderer for the description.
660 It should be 'rst', 'markdown' or 'plain'. If not give default
664 It should be 'rst', 'markdown' or 'plain'. If not give default
661 system renderer will be used
665 system renderer will be used
662 :param reviewers: Set the new pull request reviewers list.
666 :param reviewers: Set the new pull request reviewers list.
663 Reviewer defined by review rules will be added automatically to the
667 Reviewer defined by review rules will be added automatically to the
664 defined list.
668 defined list.
665 :type reviewers: Optional(list)
669 :type reviewers: Optional(list)
666 Accepts username strings or objects of the format:
670 Accepts username strings or objects of the format:
667
671
668 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
672 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
669 """
673 """
670
674
671 source_db_repo = get_repo_or_error(source_repo)
675 source_db_repo = get_repo_or_error(source_repo)
672 target_db_repo = get_repo_or_error(target_repo)
676 target_db_repo = get_repo_or_error(target_repo)
673 if not has_superadmin_permission(apiuser):
677 if not has_superadmin_permission(apiuser):
674 _perms = ('repository.admin', 'repository.write', 'repository.read',)
678 _perms = ('repository.admin', 'repository.write', 'repository.read',)
675 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
679 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
676
680
677 owner = validate_set_owner_permissions(apiuser, owner)
681 owner = validate_set_owner_permissions(apiuser, owner)
678
682
679 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
683 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
680 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
684 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
681
685
682 source_scm = source_db_repo.scm_instance()
686 source_scm = source_db_repo.scm_instance()
683 target_scm = target_db_repo.scm_instance()
687 target_scm = target_db_repo.scm_instance()
684
688
685 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
689 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
686 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
690 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
687
691
688 ancestor = source_scm.get_common_ancestor(
692 ancestor = source_scm.get_common_ancestor(
689 source_commit.raw_id, target_commit.raw_id, target_scm)
693 source_commit.raw_id, target_commit.raw_id, target_scm)
690 if not ancestor:
694 if not ancestor:
691 raise JSONRPCError('no common ancestor found')
695 raise JSONRPCError('no common ancestor found')
692
696
693 # recalculate target ref based on ancestor
697 # recalculate target ref based on ancestor
694 target_ref_type, target_ref_name, __ = full_target_ref.split(':')
698 target_ref_type, target_ref_name, __ = full_target_ref.split(':')
695 full_target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
699 full_target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
696
700
697 commit_ranges = target_scm.compare(
701 commit_ranges = target_scm.compare(
698 target_commit.raw_id, source_commit.raw_id, source_scm,
702 target_commit.raw_id, source_commit.raw_id, source_scm,
699 merge=True, pre_load=[])
703 merge=True, pre_load=[])
700
704
701 if not commit_ranges:
705 if not commit_ranges:
702 raise JSONRPCError('no commits found')
706 raise JSONRPCError('no commits found')
703
707
704 reviewer_objects = Optional.extract(reviewers) or []
708 reviewer_objects = Optional.extract(reviewers) or []
705
709
706 # serialize and validate passed in given reviewers
710 # serialize and validate passed in given reviewers
707 if reviewer_objects:
711 if reviewer_objects:
708 schema = ReviewerListSchema()
712 schema = ReviewerListSchema()
709 try:
713 try:
710 reviewer_objects = schema.deserialize(reviewer_objects)
714 reviewer_objects = schema.deserialize(reviewer_objects)
711 except Invalid as err:
715 except Invalid as err:
712 raise JSONRPCValidationError(colander_exc=err)
716 raise JSONRPCValidationError(colander_exc=err)
713
717
714 # validate users
718 # validate users
715 for reviewer_object in reviewer_objects:
719 for reviewer_object in reviewer_objects:
716 user = get_user_or_error(reviewer_object['username'])
720 user = get_user_or_error(reviewer_object['username'])
717 reviewer_object['user_id'] = user.user_id
721 reviewer_object['user_id'] = user.user_id
718
722
719 get_default_reviewers_data, validate_default_reviewers = \
723 get_default_reviewers_data, validate_default_reviewers = \
720 PullRequestModel().get_reviewer_functions()
724 PullRequestModel().get_reviewer_functions()
721
725
722 # recalculate reviewers logic, to make sure we can validate this
726 # recalculate reviewers logic, to make sure we can validate this
723 reviewer_rules = get_default_reviewers_data(
727 reviewer_rules = get_default_reviewers_data(
724 owner, source_db_repo,
728 owner, source_db_repo,
725 source_commit, target_db_repo, target_commit)
729 source_commit, target_db_repo, target_commit)
726
730
727 # now MERGE our given with the calculated
731 # now MERGE our given with the calculated
728 reviewer_objects = reviewer_rules['reviewers'] + reviewer_objects
732 reviewer_objects = reviewer_rules['reviewers'] + reviewer_objects
729
733
730 try:
734 try:
731 reviewers = validate_default_reviewers(
735 reviewers = validate_default_reviewers(
732 reviewer_objects, reviewer_rules)
736 reviewer_objects, reviewer_rules)
733 except ValueError as e:
737 except ValueError as e:
734 raise JSONRPCError('Reviewers Validation: {}'.format(e))
738 raise JSONRPCError('Reviewers Validation: {}'.format(e))
735
739
736 title = Optional.extract(title)
740 title = Optional.extract(title)
737 if not title:
741 if not title:
738 title_source_ref = source_ref.split(':', 2)[1]
742 title_source_ref = source_ref.split(':', 2)[1]
739 title = PullRequestModel().generate_pullrequest_title(
743 title = PullRequestModel().generate_pullrequest_title(
740 source=source_repo,
744 source=source_repo,
741 source_ref=title_source_ref,
745 source_ref=title_source_ref,
742 target=target_repo
746 target=target_repo
743 )
747 )
744 # fetch renderer, if set fallback to plain in case of PR
748 # fetch renderer, if set fallback to plain in case of PR
745 rc_config = SettingsModel().get_all_settings()
749 rc_config = SettingsModel().get_all_settings()
746 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
750 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
747 description = Optional.extract(description)
751 description = Optional.extract(description)
748 description_renderer = Optional.extract(description_renderer) or default_system_renderer
752 description_renderer = Optional.extract(description_renderer) or default_system_renderer
749
753
750 pull_request = PullRequestModel().create(
754 pull_request = PullRequestModel().create(
751 created_by=owner.user_id,
755 created_by=owner.user_id,
752 source_repo=source_repo,
756 source_repo=source_repo,
753 source_ref=full_source_ref,
757 source_ref=full_source_ref,
754 target_repo=target_repo,
758 target_repo=target_repo,
755 target_ref=full_target_ref,
759 target_ref=full_target_ref,
756 revisions=[commit.raw_id for commit in reversed(commit_ranges)],
760 revisions=[commit.raw_id for commit in reversed(commit_ranges)],
757 reviewers=reviewers,
761 reviewers=reviewers,
758 title=title,
762 title=title,
759 description=description,
763 description=description,
760 description_renderer=description_renderer,
764 description_renderer=description_renderer,
761 reviewer_data=reviewer_rules,
765 reviewer_data=reviewer_rules,
762 auth_user=apiuser
766 auth_user=apiuser
763 )
767 )
764
768
765 Session().commit()
769 Session().commit()
766 data = {
770 data = {
767 'msg': 'Created new pull request `{}`'.format(title),
771 'msg': 'Created new pull request `{}`'.format(title),
768 'pull_request_id': pull_request.pull_request_id,
772 'pull_request_id': pull_request.pull_request_id,
769 }
773 }
770 return data
774 return data
771
775
772
776
773 @jsonrpc_method()
777 @jsonrpc_method()
774 def update_pull_request(
778 def update_pull_request(
775 request, apiuser, pullrequestid, repoid=Optional(None),
779 request, apiuser, pullrequestid, repoid=Optional(None),
776 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
780 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
777 reviewers=Optional(None), update_commits=Optional(None)):
781 reviewers=Optional(None), update_commits=Optional(None)):
778 """
782 """
779 Updates a pull request.
783 Updates a pull request.
780
784
781 :param apiuser: This is filled automatically from the |authtoken|.
785 :param apiuser: This is filled automatically from the |authtoken|.
782 :type apiuser: AuthUser
786 :type apiuser: AuthUser
783 :param repoid: Optional repository name or repository ID.
787 :param repoid: Optional repository name or repository ID.
784 :type repoid: str or int
788 :type repoid: str or int
785 :param pullrequestid: The pull request ID.
789 :param pullrequestid: The pull request ID.
786 :type pullrequestid: int
790 :type pullrequestid: int
787 :param title: Set the pull request title.
791 :param title: Set the pull request title.
788 :type title: str
792 :type title: str
789 :param description: Update pull request description.
793 :param description: Update pull request description.
790 :type description: Optional(str)
794 :type description: Optional(str)
791 :type description_renderer: Optional(str)
795 :type description_renderer: Optional(str)
792 :param description_renderer: Update pull request renderer for the description.
796 :param description_renderer: Update pull request renderer for the description.
793 It should be 'rst', 'markdown' or 'plain'
797 It should be 'rst', 'markdown' or 'plain'
794 :param reviewers: Update pull request reviewers list with new value.
798 :param reviewers: Update pull request reviewers list with new value.
795 :type reviewers: Optional(list)
799 :type reviewers: Optional(list)
796 Accepts username strings or objects of the format:
800 Accepts username strings or objects of the format:
797
801
798 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
802 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
799
803
800 :param update_commits: Trigger update of commits for this pull request
804 :param update_commits: Trigger update of commits for this pull request
801 :type: update_commits: Optional(bool)
805 :type: update_commits: Optional(bool)
802
806
803 Example output:
807 Example output:
804
808
805 .. code-block:: bash
809 .. code-block:: bash
806
810
807 id : <id_given_in_input>
811 id : <id_given_in_input>
808 result : {
812 result : {
809 "msg": "Updated pull request `63`",
813 "msg": "Updated pull request `63`",
810 "pull_request": <pull_request_object>,
814 "pull_request": <pull_request_object>,
811 "updated_reviewers": {
815 "updated_reviewers": {
812 "added": [
816 "added": [
813 "username"
817 "username"
814 ],
818 ],
815 "removed": []
819 "removed": []
816 },
820 },
817 "updated_commits": {
821 "updated_commits": {
818 "added": [
822 "added": [
819 "<sha1_hash>"
823 "<sha1_hash>"
820 ],
824 ],
821 "common": [
825 "common": [
822 "<sha1_hash>",
826 "<sha1_hash>",
823 "<sha1_hash>",
827 "<sha1_hash>",
824 ],
828 ],
825 "removed": []
829 "removed": []
826 }
830 }
827 }
831 }
828 error : null
832 error : null
829 """
833 """
830
834
831 pull_request = get_pull_request_or_error(pullrequestid)
835 pull_request = get_pull_request_or_error(pullrequestid)
832 if Optional.extract(repoid):
836 if Optional.extract(repoid):
833 repo = get_repo_or_error(repoid)
837 repo = get_repo_or_error(repoid)
834 else:
838 else:
835 repo = pull_request.target_repo
839 repo = pull_request.target_repo
836
840
837 if not PullRequestModel().check_user_update(
841 if not PullRequestModel().check_user_update(
838 pull_request, apiuser, api=True):
842 pull_request, apiuser, api=True):
839 raise JSONRPCError(
843 raise JSONRPCError(
840 'pull request `%s` update failed, no permission to update.' % (
844 'pull request `%s` update failed, no permission to update.' % (
841 pullrequestid,))
845 pullrequestid,))
842 if pull_request.is_closed():
846 if pull_request.is_closed():
843 raise JSONRPCError(
847 raise JSONRPCError(
844 'pull request `%s` update failed, pull request is closed' % (
848 'pull request `%s` update failed, pull request is closed' % (
845 pullrequestid,))
849 pullrequestid,))
846
850
847 reviewer_objects = Optional.extract(reviewers) or []
851 reviewer_objects = Optional.extract(reviewers) or []
848
852
849 if reviewer_objects:
853 if reviewer_objects:
850 schema = ReviewerListSchema()
854 schema = ReviewerListSchema()
851 try:
855 try:
852 reviewer_objects = schema.deserialize(reviewer_objects)
856 reviewer_objects = schema.deserialize(reviewer_objects)
853 except Invalid as err:
857 except Invalid as err:
854 raise JSONRPCValidationError(colander_exc=err)
858 raise JSONRPCValidationError(colander_exc=err)
855
859
856 # validate users
860 # validate users
857 for reviewer_object in reviewer_objects:
861 for reviewer_object in reviewer_objects:
858 user = get_user_or_error(reviewer_object['username'])
862 user = get_user_or_error(reviewer_object['username'])
859 reviewer_object['user_id'] = user.user_id
863 reviewer_object['user_id'] = user.user_id
860
864
861 get_default_reviewers_data, get_validated_reviewers = \
865 get_default_reviewers_data, get_validated_reviewers = \
862 PullRequestModel().get_reviewer_functions()
866 PullRequestModel().get_reviewer_functions()
863
867
864 # re-use stored rules
868 # re-use stored rules
865 reviewer_rules = pull_request.reviewer_data
869 reviewer_rules = pull_request.reviewer_data
866 try:
870 try:
867 reviewers = get_validated_reviewers(
871 reviewers = get_validated_reviewers(
868 reviewer_objects, reviewer_rules)
872 reviewer_objects, reviewer_rules)
869 except ValueError as e:
873 except ValueError as e:
870 raise JSONRPCError('Reviewers Validation: {}'.format(e))
874 raise JSONRPCError('Reviewers Validation: {}'.format(e))
871 else:
875 else:
872 reviewers = []
876 reviewers = []
873
877
874 title = Optional.extract(title)
878 title = Optional.extract(title)
875 description = Optional.extract(description)
879 description = Optional.extract(description)
876 description_renderer = Optional.extract(description_renderer)
880 description_renderer = Optional.extract(description_renderer)
877
881
878 if title or description:
882 if title or description:
879 PullRequestModel().edit(
883 PullRequestModel().edit(
880 pull_request,
884 pull_request,
881 title or pull_request.title,
885 title or pull_request.title,
882 description or pull_request.description,
886 description or pull_request.description,
883 description_renderer or pull_request.description_renderer,
887 description_renderer or pull_request.description_renderer,
884 apiuser)
888 apiuser)
885 Session().commit()
889 Session().commit()
886
890
887 commit_changes = {"added": [], "common": [], "removed": []}
891 commit_changes = {"added": [], "common": [], "removed": []}
888 if str2bool(Optional.extract(update_commits)):
892 if str2bool(Optional.extract(update_commits)):
889
893
890 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
894 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
891 raise JSONRPCError(
895 raise JSONRPCError(
892 'Operation forbidden because pull request is in state {}, '
896 'Operation forbidden because pull request is in state {}, '
893 'only state {} is allowed.'.format(
897 'only state {} is allowed.'.format(
894 pull_request.pull_request_state, PullRequest.STATE_CREATED))
898 pull_request.pull_request_state, PullRequest.STATE_CREATED))
895
899
896 with pull_request.set_state(PullRequest.STATE_UPDATING):
900 with pull_request.set_state(PullRequest.STATE_UPDATING):
897 if PullRequestModel().has_valid_update_type(pull_request):
901 if PullRequestModel().has_valid_update_type(pull_request):
898 db_user = apiuser.get_instance()
902 db_user = apiuser.get_instance()
899 update_response = PullRequestModel().update_commits(
903 update_response = PullRequestModel().update_commits(
900 pull_request, db_user)
904 pull_request, db_user)
901 commit_changes = update_response.changes or commit_changes
905 commit_changes = update_response.changes or commit_changes
902 Session().commit()
906 Session().commit()
903
907
904 reviewers_changes = {"added": [], "removed": []}
908 reviewers_changes = {"added": [], "removed": []}
905 if reviewers:
909 if reviewers:
906 old_calculated_status = pull_request.calculated_review_status()
910 old_calculated_status = pull_request.calculated_review_status()
907 added_reviewers, removed_reviewers = \
911 added_reviewers, removed_reviewers = \
908 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
912 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
909
913
910 reviewers_changes['added'] = sorted(
914 reviewers_changes['added'] = sorted(
911 [get_user_or_error(n).username for n in added_reviewers])
915 [get_user_or_error(n).username for n in added_reviewers])
912 reviewers_changes['removed'] = sorted(
916 reviewers_changes['removed'] = sorted(
913 [get_user_or_error(n).username for n in removed_reviewers])
917 [get_user_or_error(n).username for n in removed_reviewers])
914 Session().commit()
918 Session().commit()
915
919
916 # trigger status changed if change in reviewers changes the status
920 # trigger status changed if change in reviewers changes the status
917 calculated_status = pull_request.calculated_review_status()
921 calculated_status = pull_request.calculated_review_status()
918 if old_calculated_status != calculated_status:
922 if old_calculated_status != calculated_status:
919 PullRequestModel().trigger_pull_request_hook(
923 PullRequestModel().trigger_pull_request_hook(
920 pull_request, apiuser, 'review_status_change',
924 pull_request, apiuser, 'review_status_change',
921 data={'status': calculated_status})
925 data={'status': calculated_status})
922
926
923 data = {
927 data = {
924 'msg': 'Updated pull request `{}`'.format(
928 'msg': 'Updated pull request `{}`'.format(
925 pull_request.pull_request_id),
929 pull_request.pull_request_id),
926 'pull_request': pull_request.get_api_data(),
930 'pull_request': pull_request.get_api_data(),
927 'updated_commits': commit_changes,
931 'updated_commits': commit_changes,
928 'updated_reviewers': reviewers_changes
932 'updated_reviewers': reviewers_changes
929 }
933 }
930
934
931 return data
935 return data
932
936
933
937
934 @jsonrpc_method()
938 @jsonrpc_method()
935 def close_pull_request(
939 def close_pull_request(
936 request, apiuser, pullrequestid, repoid=Optional(None),
940 request, apiuser, pullrequestid, repoid=Optional(None),
937 userid=Optional(OAttr('apiuser')), message=Optional('')):
941 userid=Optional(OAttr('apiuser')), message=Optional('')):
938 """
942 """
939 Close the pull request specified by `pullrequestid`.
943 Close the pull request specified by `pullrequestid`.
940
944
941 :param apiuser: This is filled automatically from the |authtoken|.
945 :param apiuser: This is filled automatically from the |authtoken|.
942 :type apiuser: AuthUser
946 :type apiuser: AuthUser
943 :param repoid: Repository name or repository ID to which the pull
947 :param repoid: Repository name or repository ID to which the pull
944 request belongs.
948 request belongs.
945 :type repoid: str or int
949 :type repoid: str or int
946 :param pullrequestid: ID of the pull request to be closed.
950 :param pullrequestid: ID of the pull request to be closed.
947 :type pullrequestid: int
951 :type pullrequestid: int
948 :param userid: Close the pull request as this user.
952 :param userid: Close the pull request as this user.
949 :type userid: Optional(str or int)
953 :type userid: Optional(str or int)
950 :param message: Optional message to close the Pull Request with. If not
954 :param message: Optional message to close the Pull Request with. If not
951 specified it will be generated automatically.
955 specified it will be generated automatically.
952 :type message: Optional(str)
956 :type message: Optional(str)
953
957
954 Example output:
958 Example output:
955
959
956 .. code-block:: bash
960 .. code-block:: bash
957
961
958 "id": <id_given_in_input>,
962 "id": <id_given_in_input>,
959 "result": {
963 "result": {
960 "pull_request_id": "<int>",
964 "pull_request_id": "<int>",
961 "close_status": "<str:status_lbl>,
965 "close_status": "<str:status_lbl>,
962 "closed": "<bool>"
966 "closed": "<bool>"
963 },
967 },
964 "error": null
968 "error": null
965
969
966 """
970 """
967 _ = request.translate
971 _ = request.translate
968
972
969 pull_request = get_pull_request_or_error(pullrequestid)
973 pull_request = get_pull_request_or_error(pullrequestid)
970 if Optional.extract(repoid):
974 if Optional.extract(repoid):
971 repo = get_repo_or_error(repoid)
975 repo = get_repo_or_error(repoid)
972 else:
976 else:
973 repo = pull_request.target_repo
977 repo = pull_request.target_repo
974
978
975 if not isinstance(userid, Optional):
979 if not isinstance(userid, Optional):
976 if (has_superadmin_permission(apiuser) or
980 if (has_superadmin_permission(apiuser) or
977 HasRepoPermissionAnyApi('repository.admin')(
981 HasRepoPermissionAnyApi('repository.admin')(
978 user=apiuser, repo_name=repo.repo_name)):
982 user=apiuser, repo_name=repo.repo_name)):
979 apiuser = get_user_or_error(userid)
983 apiuser = get_user_or_error(userid)
980 else:
984 else:
981 raise JSONRPCError('userid is not the same as your user')
985 raise JSONRPCError('userid is not the same as your user')
982
986
983 if pull_request.is_closed():
987 if pull_request.is_closed():
984 raise JSONRPCError(
988 raise JSONRPCError(
985 'pull request `%s` is already closed' % (pullrequestid,))
989 'pull request `%s` is already closed' % (pullrequestid,))
986
990
987 # only owner or admin or person with write permissions
991 # only owner or admin or person with write permissions
988 allowed_to_close = PullRequestModel().check_user_update(
992 allowed_to_close = PullRequestModel().check_user_update(
989 pull_request, apiuser, api=True)
993 pull_request, apiuser, api=True)
990
994
991 if not allowed_to_close:
995 if not allowed_to_close:
992 raise JSONRPCError(
996 raise JSONRPCError(
993 'pull request `%s` close failed, no permission to close.' % (
997 'pull request `%s` close failed, no permission to close.' % (
994 pullrequestid,))
998 pullrequestid,))
995
999
996 # message we're using to close the PR, else it's automatically generated
1000 # message we're using to close the PR, else it's automatically generated
997 message = Optional.extract(message)
1001 message = Optional.extract(message)
998
1002
999 # finally close the PR, with proper message comment
1003 # finally close the PR, with proper message comment
1000 comment, status = PullRequestModel().close_pull_request_with_comment(
1004 comment, status = PullRequestModel().close_pull_request_with_comment(
1001 pull_request, apiuser, repo, message=message, auth_user=apiuser)
1005 pull_request, apiuser, repo, message=message, auth_user=apiuser)
1002 status_lbl = ChangesetStatus.get_status_lbl(status)
1006 status_lbl = ChangesetStatus.get_status_lbl(status)
1003
1007
1004 Session().commit()
1008 Session().commit()
1005
1009
1006 data = {
1010 data = {
1007 'pull_request_id': pull_request.pull_request_id,
1011 'pull_request_id': pull_request.pull_request_id,
1008 'close_status': status_lbl,
1012 'close_status': status_lbl,
1009 'closed': True,
1013 'closed': True,
1010 }
1014 }
1011 return data
1015 return data
@@ -1,2339 +1,2343 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 import logging
21 import logging
22 import time
22 import time
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.api import (
25 from rhodecode.api import (
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 validate_set_owner_permissions)
31 validate_set_owner_permissions)
32 from rhodecode.lib import audit_logger, rc_cache
32 from rhodecode.lib import audit_logger, rc_cache
33 from rhodecode.lib import repo_maintenance
33 from rhodecode.lib import repo_maintenance
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 from rhodecode.lib.celerylib.utils import get_task_id
35 from rhodecode.lib.celerylib.utils import get_task_id
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
39 from rhodecode.lib.vcs import RepositoryError
39 from rhodecode.lib.vcs import RepositoryError
40 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
40 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
41 from rhodecode.model.changeset_status import ChangesetStatusModel
41 from rhodecode.model.changeset_status import ChangesetStatusModel
42 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.db import (
43 from rhodecode.model.db import (
44 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
44 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
45 ChangesetComment)
45 ChangesetComment)
46 from rhodecode.model.permission import PermissionModel
46 from rhodecode.model.permission import PermissionModel
47 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.scm import ScmModel, RepoList
48 from rhodecode.model.scm import ScmModel, RepoList
49 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
49 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
50 from rhodecode.model import validation_schema
50 from rhodecode.model import validation_schema
51 from rhodecode.model.validation_schema.schemas import repo_schema
51 from rhodecode.model.validation_schema.schemas import repo_schema
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 @jsonrpc_method()
56 @jsonrpc_method()
57 def get_repo(request, apiuser, repoid, cache=Optional(True)):
57 def get_repo(request, apiuser, repoid, cache=Optional(True)):
58 """
58 """
59 Gets an existing repository by its name or repository_id.
59 Gets an existing repository by its name or repository_id.
60
60
61 The members section so the output returns users groups or users
61 The members section so the output returns users groups or users
62 associated with that repository.
62 associated with that repository.
63
63
64 This command can only be run using an |authtoken| with admin rights,
64 This command can only be run using an |authtoken| with admin rights,
65 or users with at least read rights to the |repo|.
65 or users with at least read rights to the |repo|.
66
66
67 :param apiuser: This is filled automatically from the |authtoken|.
67 :param apiuser: This is filled automatically from the |authtoken|.
68 :type apiuser: AuthUser
68 :type apiuser: AuthUser
69 :param repoid: The repository name or repository id.
69 :param repoid: The repository name or repository id.
70 :type repoid: str or int
70 :type repoid: str or int
71 :param cache: use the cached value for last changeset
71 :param cache: use the cached value for last changeset
72 :type: cache: Optional(bool)
72 :type: cache: Optional(bool)
73
73
74 Example output:
74 Example output:
75
75
76 .. code-block:: bash
76 .. code-block:: bash
77
77
78 {
78 {
79 "error": null,
79 "error": null,
80 "id": <repo_id>,
80 "id": <repo_id>,
81 "result": {
81 "result": {
82 "clone_uri": null,
82 "clone_uri": null,
83 "created_on": "timestamp",
83 "created_on": "timestamp",
84 "description": "repo description",
84 "description": "repo description",
85 "enable_downloads": false,
85 "enable_downloads": false,
86 "enable_locking": false,
86 "enable_locking": false,
87 "enable_statistics": false,
87 "enable_statistics": false,
88 "followers": [
88 "followers": [
89 {
89 {
90 "active": true,
90 "active": true,
91 "admin": false,
91 "admin": false,
92 "api_key": "****************************************",
92 "api_key": "****************************************",
93 "api_keys": [
93 "api_keys": [
94 "****************************************"
94 "****************************************"
95 ],
95 ],
96 "email": "user@example.com",
96 "email": "user@example.com",
97 "emails": [
97 "emails": [
98 "user@example.com"
98 "user@example.com"
99 ],
99 ],
100 "extern_name": "rhodecode",
100 "extern_name": "rhodecode",
101 "extern_type": "rhodecode",
101 "extern_type": "rhodecode",
102 "firstname": "username",
102 "firstname": "username",
103 "ip_addresses": [],
103 "ip_addresses": [],
104 "language": null,
104 "language": null,
105 "last_login": "2015-09-16T17:16:35.854",
105 "last_login": "2015-09-16T17:16:35.854",
106 "lastname": "surname",
106 "lastname": "surname",
107 "user_id": <user_id>,
107 "user_id": <user_id>,
108 "username": "name"
108 "username": "name"
109 }
109 }
110 ],
110 ],
111 "fork_of": "parent-repo",
111 "fork_of": "parent-repo",
112 "landing_rev": [
112 "landing_rev": [
113 "rev",
113 "rev",
114 "tip"
114 "tip"
115 ],
115 ],
116 "last_changeset": {
116 "last_changeset": {
117 "author": "User <user@example.com>",
117 "author": "User <user@example.com>",
118 "branch": "default",
118 "branch": "default",
119 "date": "timestamp",
119 "date": "timestamp",
120 "message": "last commit message",
120 "message": "last commit message",
121 "parents": [
121 "parents": [
122 {
122 {
123 "raw_id": "commit-id"
123 "raw_id": "commit-id"
124 }
124 }
125 ],
125 ],
126 "raw_id": "commit-id",
126 "raw_id": "commit-id",
127 "revision": <revision number>,
127 "revision": <revision number>,
128 "short_id": "short id"
128 "short_id": "short id"
129 },
129 },
130 "lock_reason": null,
130 "lock_reason": null,
131 "locked_by": null,
131 "locked_by": null,
132 "locked_date": null,
132 "locked_date": null,
133 "owner": "owner-name",
133 "owner": "owner-name",
134 "permissions": [
134 "permissions": [
135 {
135 {
136 "name": "super-admin-name",
136 "name": "super-admin-name",
137 "origin": "super-admin",
137 "origin": "super-admin",
138 "permission": "repository.admin",
138 "permission": "repository.admin",
139 "type": "user"
139 "type": "user"
140 },
140 },
141 {
141 {
142 "name": "owner-name",
142 "name": "owner-name",
143 "origin": "owner",
143 "origin": "owner",
144 "permission": "repository.admin",
144 "permission": "repository.admin",
145 "type": "user"
145 "type": "user"
146 },
146 },
147 {
147 {
148 "name": "user-group-name",
148 "name": "user-group-name",
149 "origin": "permission",
149 "origin": "permission",
150 "permission": "repository.write",
150 "permission": "repository.write",
151 "type": "user_group"
151 "type": "user_group"
152 }
152 }
153 ],
153 ],
154 "private": true,
154 "private": true,
155 "repo_id": 676,
155 "repo_id": 676,
156 "repo_name": "user-group/repo-name",
156 "repo_name": "user-group/repo-name",
157 "repo_type": "hg"
157 "repo_type": "hg"
158 }
158 }
159 }
159 }
160 """
160 """
161
161
162 repo = get_repo_or_error(repoid)
162 repo = get_repo_or_error(repoid)
163 cache = Optional.extract(cache)
163 cache = Optional.extract(cache)
164
164
165 include_secrets = False
165 include_secrets = False
166 if has_superadmin_permission(apiuser):
166 if has_superadmin_permission(apiuser):
167 include_secrets = True
167 include_secrets = True
168 else:
168 else:
169 # check if we have at least read permission for this repo !
169 # check if we have at least read permission for this repo !
170 _perms = (
170 _perms = (
171 'repository.admin', 'repository.write', 'repository.read',)
171 'repository.admin', 'repository.write', 'repository.read',)
172 validate_repo_permissions(apiuser, repoid, repo, _perms)
172 validate_repo_permissions(apiuser, repoid, repo, _perms)
173
173
174 permissions = []
174 permissions = []
175 for _user in repo.permissions():
175 for _user in repo.permissions():
176 user_data = {
176 user_data = {
177 'name': _user.username,
177 'name': _user.username,
178 'permission': _user.permission,
178 'permission': _user.permission,
179 'origin': get_origin(_user),
179 'origin': get_origin(_user),
180 'type': "user",
180 'type': "user",
181 }
181 }
182 permissions.append(user_data)
182 permissions.append(user_data)
183
183
184 for _user_group in repo.permission_user_groups():
184 for _user_group in repo.permission_user_groups():
185 user_group_data = {
185 user_group_data = {
186 'name': _user_group.users_group_name,
186 'name': _user_group.users_group_name,
187 'permission': _user_group.permission,
187 'permission': _user_group.permission,
188 'origin': get_origin(_user_group),
188 'origin': get_origin(_user_group),
189 'type': "user_group",
189 'type': "user_group",
190 }
190 }
191 permissions.append(user_group_data)
191 permissions.append(user_group_data)
192
192
193 following_users = [
193 following_users = [
194 user.user.get_api_data(include_secrets=include_secrets)
194 user.user.get_api_data(include_secrets=include_secrets)
195 for user in repo.followers]
195 for user in repo.followers]
196
196
197 if not cache:
197 if not cache:
198 repo.update_commit_cache()
198 repo.update_commit_cache()
199 data = repo.get_api_data(include_secrets=include_secrets)
199 data = repo.get_api_data(include_secrets=include_secrets)
200 data['permissions'] = permissions
200 data['permissions'] = permissions
201 data['followers'] = following_users
201 data['followers'] = following_users
202 return data
202 return data
203
203
204
204
205 @jsonrpc_method()
205 @jsonrpc_method()
206 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
206 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
207 """
207 """
208 Lists all existing repositories.
208 Lists all existing repositories.
209
209
210 This command can only be run using an |authtoken| with admin rights,
210 This command can only be run using an |authtoken| with admin rights,
211 or users with at least read rights to |repos|.
211 or users with at least read rights to |repos|.
212
212
213 :param apiuser: This is filled automatically from the |authtoken|.
213 :param apiuser: This is filled automatically from the |authtoken|.
214 :type apiuser: AuthUser
214 :type apiuser: AuthUser
215 :param root: specify root repository group to fetch repositories.
215 :param root: specify root repository group to fetch repositories.
216 filters the returned repositories to be members of given root group.
216 filters the returned repositories to be members of given root group.
217 :type root: Optional(None)
217 :type root: Optional(None)
218 :param traverse: traverse given root into subrepositories. With this flag
218 :param traverse: traverse given root into subrepositories. With this flag
219 set to False, it will only return top-level repositories from `root`.
219 set to False, it will only return top-level repositories from `root`.
220 if root is empty it will return just top-level repositories.
220 if root is empty it will return just top-level repositories.
221 :type traverse: Optional(True)
221 :type traverse: Optional(True)
222
222
223
223
224 Example output:
224 Example output:
225
225
226 .. code-block:: bash
226 .. code-block:: bash
227
227
228 id : <id_given_in_input>
228 id : <id_given_in_input>
229 result: [
229 result: [
230 {
230 {
231 "repo_id" : "<repo_id>",
231 "repo_id" : "<repo_id>",
232 "repo_name" : "<reponame>"
232 "repo_name" : "<reponame>"
233 "repo_type" : "<repo_type>",
233 "repo_type" : "<repo_type>",
234 "clone_uri" : "<clone_uri>",
234 "clone_uri" : "<clone_uri>",
235 "private": : "<bool>",
235 "private": : "<bool>",
236 "created_on" : "<datetimecreated>",
236 "created_on" : "<datetimecreated>",
237 "description" : "<description>",
237 "description" : "<description>",
238 "landing_rev": "<landing_rev>",
238 "landing_rev": "<landing_rev>",
239 "owner": "<repo_owner>",
239 "owner": "<repo_owner>",
240 "fork_of": "<name_of_fork_parent>",
240 "fork_of": "<name_of_fork_parent>",
241 "enable_downloads": "<bool>",
241 "enable_downloads": "<bool>",
242 "enable_locking": "<bool>",
242 "enable_locking": "<bool>",
243 "enable_statistics": "<bool>",
243 "enable_statistics": "<bool>",
244 },
244 },
245 ...
245 ...
246 ]
246 ]
247 error: null
247 error: null
248 """
248 """
249
249
250 include_secrets = has_superadmin_permission(apiuser)
250 include_secrets = has_superadmin_permission(apiuser)
251 _perms = ('repository.read', 'repository.write', 'repository.admin',)
251 _perms = ('repository.read', 'repository.write', 'repository.admin',)
252 extras = {'user': apiuser}
252 extras = {'user': apiuser}
253
253
254 root = Optional.extract(root)
254 root = Optional.extract(root)
255 traverse = Optional.extract(traverse, binary=True)
255 traverse = Optional.extract(traverse, binary=True)
256
256
257 if root:
257 if root:
258 # verify parent existance, if it's empty return an error
258 # verify parent existance, if it's empty return an error
259 parent = RepoGroup.get_by_group_name(root)
259 parent = RepoGroup.get_by_group_name(root)
260 if not parent:
260 if not parent:
261 raise JSONRPCError(
261 raise JSONRPCError(
262 'Root repository group `{}` does not exist'.format(root))
262 'Root repository group `{}` does not exist'.format(root))
263
263
264 if traverse:
264 if traverse:
265 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
265 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
266 else:
266 else:
267 repos = RepoModel().get_repos_for_root(root=parent)
267 repos = RepoModel().get_repos_for_root(root=parent)
268 else:
268 else:
269 if traverse:
269 if traverse:
270 repos = RepoModel().get_all()
270 repos = RepoModel().get_all()
271 else:
271 else:
272 # return just top-level
272 # return just top-level
273 repos = RepoModel().get_repos_for_root(root=None)
273 repos = RepoModel().get_repos_for_root(root=None)
274
274
275 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
275 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
276 return [repo.get_api_data(include_secrets=include_secrets)
276 return [repo.get_api_data(include_secrets=include_secrets)
277 for repo in repo_list]
277 for repo in repo_list]
278
278
279
279
280 @jsonrpc_method()
280 @jsonrpc_method()
281 def get_repo_changeset(request, apiuser, repoid, revision,
281 def get_repo_changeset(request, apiuser, repoid, revision,
282 details=Optional('basic')):
282 details=Optional('basic')):
283 """
283 """
284 Returns information about a changeset.
284 Returns information about a changeset.
285
285
286 Additionally parameters define the amount of details returned by
286 Additionally parameters define the amount of details returned by
287 this function.
287 this function.
288
288
289 This command can only be run using an |authtoken| with admin rights,
289 This command can only be run using an |authtoken| with admin rights,
290 or users with at least read rights to the |repo|.
290 or users with at least read rights to the |repo|.
291
291
292 :param apiuser: This is filled automatically from the |authtoken|.
292 :param apiuser: This is filled automatically from the |authtoken|.
293 :type apiuser: AuthUser
293 :type apiuser: AuthUser
294 :param repoid: The repository name or repository id
294 :param repoid: The repository name or repository id
295 :type repoid: str or int
295 :type repoid: str or int
296 :param revision: revision for which listing should be done
296 :param revision: revision for which listing should be done
297 :type revision: str
297 :type revision: str
298 :param details: details can be 'basic|extended|full' full gives diff
298 :param details: details can be 'basic|extended|full' full gives diff
299 info details like the diff itself, and number of changed files etc.
299 info details like the diff itself, and number of changed files etc.
300 :type details: Optional(str)
300 :type details: Optional(str)
301
301
302 """
302 """
303 repo = get_repo_or_error(repoid)
303 repo = get_repo_or_error(repoid)
304 if not has_superadmin_permission(apiuser):
304 if not has_superadmin_permission(apiuser):
305 _perms = (
305 _perms = (
306 'repository.admin', 'repository.write', 'repository.read',)
306 'repository.admin', 'repository.write', 'repository.read',)
307 validate_repo_permissions(apiuser, repoid, repo, _perms)
307 validate_repo_permissions(apiuser, repoid, repo, _perms)
308
308
309 changes_details = Optional.extract(details)
309 changes_details = Optional.extract(details)
310 _changes_details_types = ['basic', 'extended', 'full']
310 _changes_details_types = ['basic', 'extended', 'full']
311 if changes_details not in _changes_details_types:
311 if changes_details not in _changes_details_types:
312 raise JSONRPCError(
312 raise JSONRPCError(
313 'ret_type must be one of %s' % (
313 'ret_type must be one of %s' % (
314 ','.join(_changes_details_types)))
314 ','.join(_changes_details_types)))
315
315
316 pre_load = ['author', 'branch', 'date', 'message', 'parents',
316 pre_load = ['author', 'branch', 'date', 'message', 'parents',
317 'status', '_commit', '_file_paths']
317 'status', '_commit', '_file_paths']
318
318
319 try:
319 try:
320 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
320 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
321 except TypeError as e:
321 except TypeError as e:
322 raise JSONRPCError(safe_str(e))
322 raise JSONRPCError(safe_str(e))
323 _cs_json = cs.__json__()
323 _cs_json = cs.__json__()
324 _cs_json['diff'] = build_commit_data(cs, changes_details)
324 _cs_json['diff'] = build_commit_data(cs, changes_details)
325 if changes_details == 'full':
325 if changes_details == 'full':
326 _cs_json['refs'] = cs._get_refs()
326 _cs_json['refs'] = cs._get_refs()
327 return _cs_json
327 return _cs_json
328
328
329
329
330 @jsonrpc_method()
330 @jsonrpc_method()
331 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
331 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
332 details=Optional('basic')):
332 details=Optional('basic')):
333 """
333 """
334 Returns a set of commits limited by the number starting
334 Returns a set of commits limited by the number starting
335 from the `start_rev` option.
335 from the `start_rev` option.
336
336
337 Additional parameters define the amount of details returned by this
337 Additional parameters define the amount of details returned by this
338 function.
338 function.
339
339
340 This command can only be run using an |authtoken| with admin rights,
340 This command can only be run using an |authtoken| with admin rights,
341 or users with at least read rights to |repos|.
341 or users with at least read rights to |repos|.
342
342
343 :param apiuser: This is filled automatically from the |authtoken|.
343 :param apiuser: This is filled automatically from the |authtoken|.
344 :type apiuser: AuthUser
344 :type apiuser: AuthUser
345 :param repoid: The repository name or repository ID.
345 :param repoid: The repository name or repository ID.
346 :type repoid: str or int
346 :type repoid: str or int
347 :param start_rev: The starting revision from where to get changesets.
347 :param start_rev: The starting revision from where to get changesets.
348 :type start_rev: str
348 :type start_rev: str
349 :param limit: Limit the number of commits to this amount
349 :param limit: Limit the number of commits to this amount
350 :type limit: str or int
350 :type limit: str or int
351 :param details: Set the level of detail returned. Valid option are:
351 :param details: Set the level of detail returned. Valid option are:
352 ``basic``, ``extended`` and ``full``.
352 ``basic``, ``extended`` and ``full``.
353 :type details: Optional(str)
353 :type details: Optional(str)
354
354
355 .. note::
355 .. note::
356
356
357 Setting the parameter `details` to the value ``full`` is extensive
357 Setting the parameter `details` to the value ``full`` is extensive
358 and returns details like the diff itself, and the number
358 and returns details like the diff itself, and the number
359 of changed files.
359 of changed files.
360
360
361 """
361 """
362 repo = get_repo_or_error(repoid)
362 repo = get_repo_or_error(repoid)
363 if not has_superadmin_permission(apiuser):
363 if not has_superadmin_permission(apiuser):
364 _perms = (
364 _perms = (
365 'repository.admin', 'repository.write', 'repository.read',)
365 'repository.admin', 'repository.write', 'repository.read',)
366 validate_repo_permissions(apiuser, repoid, repo, _perms)
366 validate_repo_permissions(apiuser, repoid, repo, _perms)
367
367
368 changes_details = Optional.extract(details)
368 changes_details = Optional.extract(details)
369 _changes_details_types = ['basic', 'extended', 'full']
369 _changes_details_types = ['basic', 'extended', 'full']
370 if changes_details not in _changes_details_types:
370 if changes_details not in _changes_details_types:
371 raise JSONRPCError(
371 raise JSONRPCError(
372 'ret_type must be one of %s' % (
372 'ret_type must be one of %s' % (
373 ','.join(_changes_details_types)))
373 ','.join(_changes_details_types)))
374
374
375 limit = int(limit)
375 limit = int(limit)
376 pre_load = ['author', 'branch', 'date', 'message', 'parents',
376 pre_load = ['author', 'branch', 'date', 'message', 'parents',
377 'status', '_commit', '_file_paths']
377 'status', '_commit', '_file_paths']
378
378
379 vcs_repo = repo.scm_instance()
379 vcs_repo = repo.scm_instance()
380 # SVN needs a special case to distinguish its index and commit id
380 # SVN needs a special case to distinguish its index and commit id
381 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
381 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
382 start_rev = vcs_repo.commit_ids[0]
382 start_rev = vcs_repo.commit_ids[0]
383
383
384 try:
384 try:
385 commits = vcs_repo.get_commits(
385 commits = vcs_repo.get_commits(
386 start_id=start_rev, pre_load=pre_load, translate_tags=False)
386 start_id=start_rev, pre_load=pre_load, translate_tags=False)
387 except TypeError as e:
387 except TypeError as e:
388 raise JSONRPCError(safe_str(e))
388 raise JSONRPCError(safe_str(e))
389 except Exception:
389 except Exception:
390 log.exception('Fetching of commits failed')
390 log.exception('Fetching of commits failed')
391 raise JSONRPCError('Error occurred during commit fetching')
391 raise JSONRPCError('Error occurred during commit fetching')
392
392
393 ret = []
393 ret = []
394 for cnt, commit in enumerate(commits):
394 for cnt, commit in enumerate(commits):
395 if cnt >= limit != -1:
395 if cnt >= limit != -1:
396 break
396 break
397 _cs_json = commit.__json__()
397 _cs_json = commit.__json__()
398 _cs_json['diff'] = build_commit_data(commit, changes_details)
398 _cs_json['diff'] = build_commit_data(commit, changes_details)
399 if changes_details == 'full':
399 if changes_details == 'full':
400 _cs_json['refs'] = {
400 _cs_json['refs'] = {
401 'branches': [commit.branch],
401 'branches': [commit.branch],
402 'bookmarks': getattr(commit, 'bookmarks', []),
402 'bookmarks': getattr(commit, 'bookmarks', []),
403 'tags': commit.tags
403 'tags': commit.tags
404 }
404 }
405 ret.append(_cs_json)
405 ret.append(_cs_json)
406 return ret
406 return ret
407
407
408
408
409 @jsonrpc_method()
409 @jsonrpc_method()
410 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
410 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
411 ret_type=Optional('all'), details=Optional('basic'),
411 ret_type=Optional('all'), details=Optional('basic'),
412 max_file_bytes=Optional(None)):
412 max_file_bytes=Optional(None)):
413 """
413 """
414 Returns a list of nodes and children in a flat list for a given
414 Returns a list of nodes and children in a flat list for a given
415 path at given revision.
415 path at given revision.
416
416
417 It's possible to specify ret_type to show only `files` or `dirs`.
417 It's possible to specify ret_type to show only `files` or `dirs`.
418
418
419 This command can only be run using an |authtoken| with admin rights,
419 This command can only be run using an |authtoken| with admin rights,
420 or users with at least read rights to |repos|.
420 or users with at least read rights to |repos|.
421
421
422 :param apiuser: This is filled automatically from the |authtoken|.
422 :param apiuser: This is filled automatically from the |authtoken|.
423 :type apiuser: AuthUser
423 :type apiuser: AuthUser
424 :param repoid: The repository name or repository ID.
424 :param repoid: The repository name or repository ID.
425 :type repoid: str or int
425 :type repoid: str or int
426 :param revision: The revision for which listing should be done.
426 :param revision: The revision for which listing should be done.
427 :type revision: str
427 :type revision: str
428 :param root_path: The path from which to start displaying.
428 :param root_path: The path from which to start displaying.
429 :type root_path: str
429 :type root_path: str
430 :param ret_type: Set the return type. Valid options are
430 :param ret_type: Set the return type. Valid options are
431 ``all`` (default), ``files`` and ``dirs``.
431 ``all`` (default), ``files`` and ``dirs``.
432 :type ret_type: Optional(str)
432 :type ret_type: Optional(str)
433 :param details: Returns extended information about nodes, such as
433 :param details: Returns extended information about nodes, such as
434 md5, binary, and or content.
434 md5, binary, and or content.
435 The valid options are ``basic`` and ``full``.
435 The valid options are ``basic`` and ``full``.
436 :type details: Optional(str)
436 :type details: Optional(str)
437 :param max_file_bytes: Only return file content under this file size bytes
437 :param max_file_bytes: Only return file content under this file size bytes
438 :type details: Optional(int)
438 :type details: Optional(int)
439
439
440 Example output:
440 Example output:
441
441
442 .. code-block:: bash
442 .. code-block:: bash
443
443
444 id : <id_given_in_input>
444 id : <id_given_in_input>
445 result: [
445 result: [
446 {
446 {
447 "binary": false,
447 "binary": false,
448 "content": "File line",
448 "content": "File line",
449 "extension": "md",
449 "extension": "md",
450 "lines": 2,
450 "lines": 2,
451 "md5": "059fa5d29b19c0657e384749480f6422",
451 "md5": "059fa5d29b19c0657e384749480f6422",
452 "mimetype": "text/x-minidsrc",
452 "mimetype": "text/x-minidsrc",
453 "name": "file.md",
453 "name": "file.md",
454 "size": 580,
454 "size": 580,
455 "type": "file"
455 "type": "file"
456 },
456 },
457 ...
457 ...
458 ]
458 ]
459 error: null
459 error: null
460 """
460 """
461
461
462 repo = get_repo_or_error(repoid)
462 repo = get_repo_or_error(repoid)
463 if not has_superadmin_permission(apiuser):
463 if not has_superadmin_permission(apiuser):
464 _perms = ('repository.admin', 'repository.write', 'repository.read',)
464 _perms = ('repository.admin', 'repository.write', 'repository.read',)
465 validate_repo_permissions(apiuser, repoid, repo, _perms)
465 validate_repo_permissions(apiuser, repoid, repo, _perms)
466
466
467 ret_type = Optional.extract(ret_type)
467 ret_type = Optional.extract(ret_type)
468 details = Optional.extract(details)
468 details = Optional.extract(details)
469 _extended_types = ['basic', 'full']
469 _extended_types = ['basic', 'full']
470 if details not in _extended_types:
470 if details not in _extended_types:
471 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
471 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
472 extended_info = False
472 extended_info = False
473 content = False
473 content = False
474 if details == 'basic':
474 if details == 'basic':
475 extended_info = True
475 extended_info = True
476
476
477 if details == 'full':
477 if details == 'full':
478 extended_info = content = True
478 extended_info = content = True
479
479
480 _map = {}
480 _map = {}
481 try:
481 try:
482 # check if repo is not empty by any chance, skip quicker if it is.
482 # check if repo is not empty by any chance, skip quicker if it is.
483 _scm = repo.scm_instance()
483 _scm = repo.scm_instance()
484 if _scm.is_empty():
484 if _scm.is_empty():
485 return []
485 return []
486
486
487 _d, _f = ScmModel().get_nodes(
487 _d, _f = ScmModel().get_nodes(
488 repo, revision, root_path, flat=False,
488 repo, revision, root_path, flat=False,
489 extended_info=extended_info, content=content,
489 extended_info=extended_info, content=content,
490 max_file_bytes=max_file_bytes)
490 max_file_bytes=max_file_bytes)
491 _map = {
491 _map = {
492 'all': _d + _f,
492 'all': _d + _f,
493 'files': _f,
493 'files': _f,
494 'dirs': _d,
494 'dirs': _d,
495 }
495 }
496 return _map[ret_type]
496 return _map[ret_type]
497 except KeyError:
497 except KeyError:
498 raise JSONRPCError(
498 raise JSONRPCError(
499 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
499 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
500 except Exception:
500 except Exception:
501 log.exception("Exception occurred while trying to get repo nodes")
501 log.exception("Exception occurred while trying to get repo nodes")
502 raise JSONRPCError(
502 raise JSONRPCError(
503 'failed to get repo: `%s` nodes' % repo.repo_name
503 'failed to get repo: `%s` nodes' % repo.repo_name
504 )
504 )
505
505
506
506
507 @jsonrpc_method()
507 @jsonrpc_method()
508 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
508 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
509 max_file_bytes=Optional(None), details=Optional('basic'),
509 max_file_bytes=Optional(None), details=Optional('basic'),
510 cache=Optional(True)):
510 cache=Optional(True)):
511 """
511 """
512 Returns a single file from repository at given revision.
512 Returns a single file from repository at given revision.
513
513
514 This command can only be run using an |authtoken| with admin rights,
514 This command can only be run using an |authtoken| with admin rights,
515 or users with at least read rights to |repos|.
515 or users with at least read rights to |repos|.
516
516
517 :param apiuser: This is filled automatically from the |authtoken|.
517 :param apiuser: This is filled automatically from the |authtoken|.
518 :type apiuser: AuthUser
518 :type apiuser: AuthUser
519 :param repoid: The repository name or repository ID.
519 :param repoid: The repository name or repository ID.
520 :type repoid: str or int
520 :type repoid: str or int
521 :param commit_id: The revision for which listing should be done.
521 :param commit_id: The revision for which listing should be done.
522 :type commit_id: str
522 :type commit_id: str
523 :param file_path: The path from which to start displaying.
523 :param file_path: The path from which to start displaying.
524 :type file_path: str
524 :type file_path: str
525 :param details: Returns different set of information about nodes.
525 :param details: Returns different set of information about nodes.
526 The valid options are ``minimal`` ``basic`` and ``full``.
526 The valid options are ``minimal`` ``basic`` and ``full``.
527 :type details: Optional(str)
527 :type details: Optional(str)
528 :param max_file_bytes: Only return file content under this file size bytes
528 :param max_file_bytes: Only return file content under this file size bytes
529 :type max_file_bytes: Optional(int)
529 :type max_file_bytes: Optional(int)
530 :param cache: Use internal caches for fetching files. If disabled fetching
530 :param cache: Use internal caches for fetching files. If disabled fetching
531 files is slower but more memory efficient
531 files is slower but more memory efficient
532 :type cache: Optional(bool)
532 :type cache: Optional(bool)
533
533
534 Example output:
534 Example output:
535
535
536 .. code-block:: bash
536 .. code-block:: bash
537
537
538 id : <id_given_in_input>
538 id : <id_given_in_input>
539 result: {
539 result: {
540 "binary": false,
540 "binary": false,
541 "extension": "py",
541 "extension": "py",
542 "lines": 35,
542 "lines": 35,
543 "content": "....",
543 "content": "....",
544 "md5": "76318336366b0f17ee249e11b0c99c41",
544 "md5": "76318336366b0f17ee249e11b0c99c41",
545 "mimetype": "text/x-python",
545 "mimetype": "text/x-python",
546 "name": "python.py",
546 "name": "python.py",
547 "size": 817,
547 "size": 817,
548 "type": "file",
548 "type": "file",
549 }
549 }
550 error: null
550 error: null
551 """
551 """
552
552
553 repo = get_repo_or_error(repoid)
553 repo = get_repo_or_error(repoid)
554 if not has_superadmin_permission(apiuser):
554 if not has_superadmin_permission(apiuser):
555 _perms = ('repository.admin', 'repository.write', 'repository.read',)
555 _perms = ('repository.admin', 'repository.write', 'repository.read',)
556 validate_repo_permissions(apiuser, repoid, repo, _perms)
556 validate_repo_permissions(apiuser, repoid, repo, _perms)
557
557
558 cache = Optional.extract(cache, binary=True)
558 cache = Optional.extract(cache, binary=True)
559 details = Optional.extract(details)
559 details = Optional.extract(details)
560 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
560 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
561 if details not in _extended_types:
561 if details not in _extended_types:
562 raise JSONRPCError(
562 raise JSONRPCError(
563 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
563 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
564 extended_info = False
564 extended_info = False
565 content = False
565 content = False
566
566
567 if details == 'minimal':
567 if details == 'minimal':
568 extended_info = False
568 extended_info = False
569
569
570 elif details == 'basic':
570 elif details == 'basic':
571 extended_info = True
571 extended_info = True
572
572
573 elif details == 'full':
573 elif details == 'full':
574 extended_info = content = True
574 extended_info = content = True
575
575
576 try:
576 try:
577 # check if repo is not empty by any chance, skip quicker if it is.
577 # check if repo is not empty by any chance, skip quicker if it is.
578 _scm = repo.scm_instance()
578 _scm = repo.scm_instance()
579 if _scm.is_empty():
579 if _scm.is_empty():
580 return None
580 return None
581
581
582 node = ScmModel().get_node(
582 node = ScmModel().get_node(
583 repo, commit_id, file_path, extended_info=extended_info,
583 repo, commit_id, file_path, extended_info=extended_info,
584 content=content, max_file_bytes=max_file_bytes, cache=cache)
584 content=content, max_file_bytes=max_file_bytes, cache=cache)
585 except NodeDoesNotExistError:
585 except NodeDoesNotExistError:
586 raise JSONRPCError('There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
586 raise JSONRPCError('There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
587 repo.repo_name, file_path, commit_id))
587 repo.repo_name, file_path, commit_id))
588 except Exception:
588 except Exception:
589 log.exception("Exception occurred while trying to get repo %s file",
589 log.exception("Exception occurred while trying to get repo %s file",
590 repo.repo_name)
590 repo.repo_name)
591 raise JSONRPCError('failed to get repo: `{}` file at path {}'.format(
591 raise JSONRPCError('failed to get repo: `{}` file at path {}'.format(
592 repo.repo_name, file_path))
592 repo.repo_name, file_path))
593
593
594 return node
594 return node
595
595
596
596
597 @jsonrpc_method()
597 @jsonrpc_method()
598 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
598 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
599 """
599 """
600 Returns a list of tree nodes for path at given revision. This api is built
600 Returns a list of tree nodes for path at given revision. This api is built
601 strictly for usage in full text search building, and shouldn't be consumed
601 strictly for usage in full text search building, and shouldn't be consumed
602
602
603 This command can only be run using an |authtoken| with admin rights,
603 This command can only be run using an |authtoken| with admin rights,
604 or users with at least read rights to |repos|.
604 or users with at least read rights to |repos|.
605
605
606 """
606 """
607
607
608 repo = get_repo_or_error(repoid)
608 repo = get_repo_or_error(repoid)
609 if not has_superadmin_permission(apiuser):
609 if not has_superadmin_permission(apiuser):
610 _perms = ('repository.admin', 'repository.write', 'repository.read',)
610 _perms = ('repository.admin', 'repository.write', 'repository.read',)
611 validate_repo_permissions(apiuser, repoid, repo, _perms)
611 validate_repo_permissions(apiuser, repoid, repo, _perms)
612
612
613 repo_id = repo.repo_id
613 repo_id = repo.repo_id
614 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
614 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
615 cache_on = cache_seconds > 0
615 cache_on = cache_seconds > 0
616
616
617 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
617 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
618 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
618 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
619
619
620 def compute_fts_tree(cache_ver, repo_id, commit_id, root_path):
620 def compute_fts_tree(cache_ver, repo_id, commit_id, root_path):
621 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
621 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
622
622
623 try:
623 try:
624 # check if repo is not empty by any chance, skip quicker if it is.
624 # check if repo is not empty by any chance, skip quicker if it is.
625 _scm = repo.scm_instance()
625 _scm = repo.scm_instance()
626 if _scm.is_empty():
626 if _scm.is_empty():
627 return []
627 return []
628 except RepositoryError:
628 except RepositoryError:
629 log.exception("Exception occurred while trying to get repo nodes")
629 log.exception("Exception occurred while trying to get repo nodes")
630 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
630 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
631
631
632 try:
632 try:
633 # we need to resolve commit_id to a FULL sha for cache to work correctly.
633 # we need to resolve commit_id to a FULL sha for cache to work correctly.
634 # sending 'master' is a pointer that needs to be translated to current commit.
634 # sending 'master' is a pointer that needs to be translated to current commit.
635 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
635 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
636 log.debug(
636 log.debug(
637 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
637 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
638 'with caching: %s[TTL: %ss]' % (
638 'with caching: %s[TTL: %ss]' % (
639 repo_id, commit_id, cache_on, cache_seconds or 0))
639 repo_id, commit_id, cache_on, cache_seconds or 0))
640
640
641 tree_files = compute_fts_tree(rc_cache.FILE_TREE_CACHE_VER, repo_id, commit_id, root_path)
641 tree_files = compute_fts_tree(rc_cache.FILE_TREE_CACHE_VER, repo_id, commit_id, root_path)
642 return tree_files
642 return tree_files
643
643
644 except Exception:
644 except Exception:
645 log.exception("Exception occurred while trying to get repo nodes")
645 log.exception("Exception occurred while trying to get repo nodes")
646 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
646 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
647
647
648
648
649 @jsonrpc_method()
649 @jsonrpc_method()
650 def get_repo_refs(request, apiuser, repoid):
650 def get_repo_refs(request, apiuser, repoid):
651 """
651 """
652 Returns a dictionary of current references. It returns
652 Returns a dictionary of current references. It returns
653 bookmarks, branches, closed_branches, and tags for given repository
653 bookmarks, branches, closed_branches, and tags for given repository
654
654
655 It's possible to specify ret_type to show only `files` or `dirs`.
655 It's possible to specify ret_type to show only `files` or `dirs`.
656
656
657 This command can only be run using an |authtoken| with admin rights,
657 This command can only be run using an |authtoken| with admin rights,
658 or users with at least read rights to |repos|.
658 or users with at least read rights to |repos|.
659
659
660 :param apiuser: This is filled automatically from the |authtoken|.
660 :param apiuser: This is filled automatically from the |authtoken|.
661 :type apiuser: AuthUser
661 :type apiuser: AuthUser
662 :param repoid: The repository name or repository ID.
662 :param repoid: The repository name or repository ID.
663 :type repoid: str or int
663 :type repoid: str or int
664
664
665 Example output:
665 Example output:
666
666
667 .. code-block:: bash
667 .. code-block:: bash
668
668
669 id : <id_given_in_input>
669 id : <id_given_in_input>
670 "result": {
670 "result": {
671 "bookmarks": {
671 "bookmarks": {
672 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
672 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
673 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
673 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
674 },
674 },
675 "branches": {
675 "branches": {
676 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
676 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
677 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
677 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
678 },
678 },
679 "branches_closed": {},
679 "branches_closed": {},
680 "tags": {
680 "tags": {
681 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
681 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
682 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
682 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
683 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
683 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
684 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
684 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
685 }
685 }
686 }
686 }
687 error: null
687 error: null
688 """
688 """
689
689
690 repo = get_repo_or_error(repoid)
690 repo = get_repo_or_error(repoid)
691 if not has_superadmin_permission(apiuser):
691 if not has_superadmin_permission(apiuser):
692 _perms = ('repository.admin', 'repository.write', 'repository.read',)
692 _perms = ('repository.admin', 'repository.write', 'repository.read',)
693 validate_repo_permissions(apiuser, repoid, repo, _perms)
693 validate_repo_permissions(apiuser, repoid, repo, _perms)
694
694
695 try:
695 try:
696 # check if repo is not empty by any chance, skip quicker if it is.
696 # check if repo is not empty by any chance, skip quicker if it is.
697 vcs_instance = repo.scm_instance()
697 vcs_instance = repo.scm_instance()
698 refs = vcs_instance.refs()
698 refs = vcs_instance.refs()
699 return refs
699 return refs
700 except Exception:
700 except Exception:
701 log.exception("Exception occurred while trying to get repo refs")
701 log.exception("Exception occurred while trying to get repo refs")
702 raise JSONRPCError(
702 raise JSONRPCError(
703 'failed to get repo: `%s` references' % repo.repo_name
703 'failed to get repo: `%s` references' % repo.repo_name
704 )
704 )
705
705
706
706
707 @jsonrpc_method()
707 @jsonrpc_method()
708 def create_repo(
708 def create_repo(
709 request, apiuser, repo_name, repo_type,
709 request, apiuser, repo_name, repo_type,
710 owner=Optional(OAttr('apiuser')),
710 owner=Optional(OAttr('apiuser')),
711 description=Optional(''),
711 description=Optional(''),
712 private=Optional(False),
712 private=Optional(False),
713 clone_uri=Optional(None),
713 clone_uri=Optional(None),
714 push_uri=Optional(None),
714 push_uri=Optional(None),
715 landing_rev=Optional(None),
715 landing_rev=Optional(None),
716 enable_statistics=Optional(False),
716 enable_statistics=Optional(False),
717 enable_locking=Optional(False),
717 enable_locking=Optional(False),
718 enable_downloads=Optional(False),
718 enable_downloads=Optional(False),
719 copy_permissions=Optional(False)):
719 copy_permissions=Optional(False)):
720 """
720 """
721 Creates a repository.
721 Creates a repository.
722
722
723 * If the repository name contains "/", repository will be created inside
723 * If the repository name contains "/", repository will be created inside
724 a repository group or nested repository groups
724 a repository group or nested repository groups
725
725
726 For example "foo/bar/repo1" will create |repo| called "repo1" inside
726 For example "foo/bar/repo1" will create |repo| called "repo1" inside
727 group "foo/bar". You have to have permissions to access and write to
727 group "foo/bar". You have to have permissions to access and write to
728 the last repository group ("bar" in this example)
728 the last repository group ("bar" in this example)
729
729
730 This command can only be run using an |authtoken| with at least
730 This command can only be run using an |authtoken| with at least
731 permissions to create repositories, or write permissions to
731 permissions to create repositories, or write permissions to
732 parent repository groups.
732 parent repository groups.
733
733
734 :param apiuser: This is filled automatically from the |authtoken|.
734 :param apiuser: This is filled automatically from the |authtoken|.
735 :type apiuser: AuthUser
735 :type apiuser: AuthUser
736 :param repo_name: Set the repository name.
736 :param repo_name: Set the repository name.
737 :type repo_name: str
737 :type repo_name: str
738 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
738 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
739 :type repo_type: str
739 :type repo_type: str
740 :param owner: user_id or username
740 :param owner: user_id or username
741 :type owner: Optional(str)
741 :type owner: Optional(str)
742 :param description: Set the repository description.
742 :param description: Set the repository description.
743 :type description: Optional(str)
743 :type description: Optional(str)
744 :param private: set repository as private
744 :param private: set repository as private
745 :type private: bool
745 :type private: bool
746 :param clone_uri: set clone_uri
746 :param clone_uri: set clone_uri
747 :type clone_uri: str
747 :type clone_uri: str
748 :param push_uri: set push_uri
748 :param push_uri: set push_uri
749 :type push_uri: str
749 :type push_uri: str
750 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
750 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
751 :type landing_rev: str
751 :type landing_rev: str
752 :param enable_locking:
752 :param enable_locking:
753 :type enable_locking: bool
753 :type enable_locking: bool
754 :param enable_downloads:
754 :param enable_downloads:
755 :type enable_downloads: bool
755 :type enable_downloads: bool
756 :param enable_statistics:
756 :param enable_statistics:
757 :type enable_statistics: bool
757 :type enable_statistics: bool
758 :param copy_permissions: Copy permission from group in which the
758 :param copy_permissions: Copy permission from group in which the
759 repository is being created.
759 repository is being created.
760 :type copy_permissions: bool
760 :type copy_permissions: bool
761
761
762
762
763 Example output:
763 Example output:
764
764
765 .. code-block:: bash
765 .. code-block:: bash
766
766
767 id : <id_given_in_input>
767 id : <id_given_in_input>
768 result: {
768 result: {
769 "msg": "Created new repository `<reponame>`",
769 "msg": "Created new repository `<reponame>`",
770 "success": true,
770 "success": true,
771 "task": "<celery task id or None if done sync>"
771 "task": "<celery task id or None if done sync>"
772 }
772 }
773 error: null
773 error: null
774
774
775
775
776 Example error output:
776 Example error output:
777
777
778 .. code-block:: bash
778 .. code-block:: bash
779
779
780 id : <id_given_in_input>
780 id : <id_given_in_input>
781 result : null
781 result : null
782 error : {
782 error : {
783 'failed to create repository `<repo_name>`'
783 'failed to create repository `<repo_name>`'
784 }
784 }
785
785
786 """
786 """
787
787
788 owner = validate_set_owner_permissions(apiuser, owner)
788 owner = validate_set_owner_permissions(apiuser, owner)
789
789
790 description = Optional.extract(description)
790 description = Optional.extract(description)
791 copy_permissions = Optional.extract(copy_permissions)
791 copy_permissions = Optional.extract(copy_permissions)
792 clone_uri = Optional.extract(clone_uri)
792 clone_uri = Optional.extract(clone_uri)
793 push_uri = Optional.extract(push_uri)
793 push_uri = Optional.extract(push_uri)
794
794
795 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
795 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
796 if isinstance(private, Optional):
796 if isinstance(private, Optional):
797 private = defs.get('repo_private') or Optional.extract(private)
797 private = defs.get('repo_private') or Optional.extract(private)
798 if isinstance(repo_type, Optional):
798 if isinstance(repo_type, Optional):
799 repo_type = defs.get('repo_type')
799 repo_type = defs.get('repo_type')
800 if isinstance(enable_statistics, Optional):
800 if isinstance(enable_statistics, Optional):
801 enable_statistics = defs.get('repo_enable_statistics')
801 enable_statistics = defs.get('repo_enable_statistics')
802 if isinstance(enable_locking, Optional):
802 if isinstance(enable_locking, Optional):
803 enable_locking = defs.get('repo_enable_locking')
803 enable_locking = defs.get('repo_enable_locking')
804 if isinstance(enable_downloads, Optional):
804 if isinstance(enable_downloads, Optional):
805 enable_downloads = defs.get('repo_enable_downloads')
805 enable_downloads = defs.get('repo_enable_downloads')
806
806
807 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
807 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
808 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
808 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
809 ref_choices = list(set(ref_choices + [landing_ref]))
809 ref_choices = list(set(ref_choices + [landing_ref]))
810
810
811 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
811 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
812
812
813 schema = repo_schema.RepoSchema().bind(
813 schema = repo_schema.RepoSchema().bind(
814 repo_type_options=rhodecode.BACKENDS.keys(),
814 repo_type_options=rhodecode.BACKENDS.keys(),
815 repo_ref_options=ref_choices,
815 repo_ref_options=ref_choices,
816 repo_type=repo_type,
816 repo_type=repo_type,
817 # user caller
817 # user caller
818 user=apiuser)
818 user=apiuser)
819
819
820 try:
820 try:
821 schema_data = schema.deserialize(dict(
821 schema_data = schema.deserialize(dict(
822 repo_name=repo_name,
822 repo_name=repo_name,
823 repo_type=repo_type,
823 repo_type=repo_type,
824 repo_owner=owner.username,
824 repo_owner=owner.username,
825 repo_description=description,
825 repo_description=description,
826 repo_landing_commit_ref=landing_commit_ref,
826 repo_landing_commit_ref=landing_commit_ref,
827 repo_clone_uri=clone_uri,
827 repo_clone_uri=clone_uri,
828 repo_push_uri=push_uri,
828 repo_push_uri=push_uri,
829 repo_private=private,
829 repo_private=private,
830 repo_copy_permissions=copy_permissions,
830 repo_copy_permissions=copy_permissions,
831 repo_enable_statistics=enable_statistics,
831 repo_enable_statistics=enable_statistics,
832 repo_enable_downloads=enable_downloads,
832 repo_enable_downloads=enable_downloads,
833 repo_enable_locking=enable_locking))
833 repo_enable_locking=enable_locking))
834 except validation_schema.Invalid as err:
834 except validation_schema.Invalid as err:
835 raise JSONRPCValidationError(colander_exc=err)
835 raise JSONRPCValidationError(colander_exc=err)
836
836
837 try:
837 try:
838 data = {
838 data = {
839 'owner': owner,
839 'owner': owner,
840 'repo_name': schema_data['repo_group']['repo_name_without_group'],
840 'repo_name': schema_data['repo_group']['repo_name_without_group'],
841 'repo_name_full': schema_data['repo_name'],
841 'repo_name_full': schema_data['repo_name'],
842 'repo_group': schema_data['repo_group']['repo_group_id'],
842 'repo_group': schema_data['repo_group']['repo_group_id'],
843 'repo_type': schema_data['repo_type'],
843 'repo_type': schema_data['repo_type'],
844 'repo_description': schema_data['repo_description'],
844 'repo_description': schema_data['repo_description'],
845 'repo_private': schema_data['repo_private'],
845 'repo_private': schema_data['repo_private'],
846 'clone_uri': schema_data['repo_clone_uri'],
846 'clone_uri': schema_data['repo_clone_uri'],
847 'push_uri': schema_data['repo_push_uri'],
847 'push_uri': schema_data['repo_push_uri'],
848 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
848 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
849 'enable_statistics': schema_data['repo_enable_statistics'],
849 'enable_statistics': schema_data['repo_enable_statistics'],
850 'enable_locking': schema_data['repo_enable_locking'],
850 'enable_locking': schema_data['repo_enable_locking'],
851 'enable_downloads': schema_data['repo_enable_downloads'],
851 'enable_downloads': schema_data['repo_enable_downloads'],
852 'repo_copy_permissions': schema_data['repo_copy_permissions'],
852 'repo_copy_permissions': schema_data['repo_copy_permissions'],
853 }
853 }
854
854
855 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
855 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
856 task_id = get_task_id(task)
856 task_id = get_task_id(task)
857 # no commit, it's done in RepoModel, or async via celery
857 # no commit, it's done in RepoModel, or async via celery
858 return {
858 return {
859 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
859 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
860 'success': True, # cannot return the repo data here since fork
860 'success': True, # cannot return the repo data here since fork
861 # can be done async
861 # can be done async
862 'task': task_id
862 'task': task_id
863 }
863 }
864 except Exception:
864 except Exception:
865 log.exception(
865 log.exception(
866 u"Exception while trying to create the repository %s",
866 u"Exception while trying to create the repository %s",
867 schema_data['repo_name'])
867 schema_data['repo_name'])
868 raise JSONRPCError(
868 raise JSONRPCError(
869 'failed to create repository `%s`' % (schema_data['repo_name'],))
869 'failed to create repository `%s`' % (schema_data['repo_name'],))
870
870
871
871
872 @jsonrpc_method()
872 @jsonrpc_method()
873 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
873 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
874 description=Optional('')):
874 description=Optional('')):
875 """
875 """
876 Adds an extra field to a repository.
876 Adds an extra field to a repository.
877
877
878 This command can only be run using an |authtoken| with at least
878 This command can only be run using an |authtoken| with at least
879 write permissions to the |repo|.
879 write permissions to the |repo|.
880
880
881 :param apiuser: This is filled automatically from the |authtoken|.
881 :param apiuser: This is filled automatically from the |authtoken|.
882 :type apiuser: AuthUser
882 :type apiuser: AuthUser
883 :param repoid: Set the repository name or repository id.
883 :param repoid: Set the repository name or repository id.
884 :type repoid: str or int
884 :type repoid: str or int
885 :param key: Create a unique field key for this repository.
885 :param key: Create a unique field key for this repository.
886 :type key: str
886 :type key: str
887 :param label:
887 :param label:
888 :type label: Optional(str)
888 :type label: Optional(str)
889 :param description:
889 :param description:
890 :type description: Optional(str)
890 :type description: Optional(str)
891 """
891 """
892 repo = get_repo_or_error(repoid)
892 repo = get_repo_or_error(repoid)
893 if not has_superadmin_permission(apiuser):
893 if not has_superadmin_permission(apiuser):
894 _perms = ('repository.admin',)
894 _perms = ('repository.admin',)
895 validate_repo_permissions(apiuser, repoid, repo, _perms)
895 validate_repo_permissions(apiuser, repoid, repo, _perms)
896
896
897 label = Optional.extract(label) or key
897 label = Optional.extract(label) or key
898 description = Optional.extract(description)
898 description = Optional.extract(description)
899
899
900 field = RepositoryField.get_by_key_name(key, repo)
900 field = RepositoryField.get_by_key_name(key, repo)
901 if field:
901 if field:
902 raise JSONRPCError('Field with key '
902 raise JSONRPCError('Field with key '
903 '`%s` exists for repo `%s`' % (key, repoid))
903 '`%s` exists for repo `%s`' % (key, repoid))
904
904
905 try:
905 try:
906 RepoModel().add_repo_field(repo, key, field_label=label,
906 RepoModel().add_repo_field(repo, key, field_label=label,
907 field_desc=description)
907 field_desc=description)
908 Session().commit()
908 Session().commit()
909 return {
909 return {
910 'msg': "Added new repository field `%s`" % (key,),
910 'msg': "Added new repository field `%s`" % (key,),
911 'success': True,
911 'success': True,
912 }
912 }
913 except Exception:
913 except Exception:
914 log.exception("Exception occurred while trying to add field to repo")
914 log.exception("Exception occurred while trying to add field to repo")
915 raise JSONRPCError(
915 raise JSONRPCError(
916 'failed to create new field for repository `%s`' % (repoid,))
916 'failed to create new field for repository `%s`' % (repoid,))
917
917
918
918
919 @jsonrpc_method()
919 @jsonrpc_method()
920 def remove_field_from_repo(request, apiuser, repoid, key):
920 def remove_field_from_repo(request, apiuser, repoid, key):
921 """
921 """
922 Removes an extra field from a repository.
922 Removes an extra field from a repository.
923
923
924 This command can only be run using an |authtoken| with at least
924 This command can only be run using an |authtoken| with at least
925 write permissions to the |repo|.
925 write permissions to the |repo|.
926
926
927 :param apiuser: This is filled automatically from the |authtoken|.
927 :param apiuser: This is filled automatically from the |authtoken|.
928 :type apiuser: AuthUser
928 :type apiuser: AuthUser
929 :param repoid: Set the repository name or repository ID.
929 :param repoid: Set the repository name or repository ID.
930 :type repoid: str or int
930 :type repoid: str or int
931 :param key: Set the unique field key for this repository.
931 :param key: Set the unique field key for this repository.
932 :type key: str
932 :type key: str
933 """
933 """
934
934
935 repo = get_repo_or_error(repoid)
935 repo = get_repo_or_error(repoid)
936 if not has_superadmin_permission(apiuser):
936 if not has_superadmin_permission(apiuser):
937 _perms = ('repository.admin',)
937 _perms = ('repository.admin',)
938 validate_repo_permissions(apiuser, repoid, repo, _perms)
938 validate_repo_permissions(apiuser, repoid, repo, _perms)
939
939
940 field = RepositoryField.get_by_key_name(key, repo)
940 field = RepositoryField.get_by_key_name(key, repo)
941 if not field:
941 if not field:
942 raise JSONRPCError('Field with key `%s` does not '
942 raise JSONRPCError('Field with key `%s` does not '
943 'exists for repo `%s`' % (key, repoid))
943 'exists for repo `%s`' % (key, repoid))
944
944
945 try:
945 try:
946 RepoModel().delete_repo_field(repo, field_key=key)
946 RepoModel().delete_repo_field(repo, field_key=key)
947 Session().commit()
947 Session().commit()
948 return {
948 return {
949 'msg': "Deleted repository field `%s`" % (key,),
949 'msg': "Deleted repository field `%s`" % (key,),
950 'success': True,
950 'success': True,
951 }
951 }
952 except Exception:
952 except Exception:
953 log.exception(
953 log.exception(
954 "Exception occurred while trying to delete field from repo")
954 "Exception occurred while trying to delete field from repo")
955 raise JSONRPCError(
955 raise JSONRPCError(
956 'failed to delete field for repository `%s`' % (repoid,))
956 'failed to delete field for repository `%s`' % (repoid,))
957
957
958
958
959 @jsonrpc_method()
959 @jsonrpc_method()
960 def update_repo(
960 def update_repo(
961 request, apiuser, repoid, repo_name=Optional(None),
961 request, apiuser, repoid, repo_name=Optional(None),
962 owner=Optional(OAttr('apiuser')), description=Optional(''),
962 owner=Optional(OAttr('apiuser')), description=Optional(''),
963 private=Optional(False),
963 private=Optional(False),
964 clone_uri=Optional(None), push_uri=Optional(None),
964 clone_uri=Optional(None), push_uri=Optional(None),
965 landing_rev=Optional(None), fork_of=Optional(None),
965 landing_rev=Optional(None), fork_of=Optional(None),
966 enable_statistics=Optional(False),
966 enable_statistics=Optional(False),
967 enable_locking=Optional(False),
967 enable_locking=Optional(False),
968 enable_downloads=Optional(False), fields=Optional('')):
968 enable_downloads=Optional(False), fields=Optional('')):
969 """
969 """
970 Updates a repository with the given information.
970 Updates a repository with the given information.
971
971
972 This command can only be run using an |authtoken| with at least
972 This command can only be run using an |authtoken| with at least
973 admin permissions to the |repo|.
973 admin permissions to the |repo|.
974
974
975 * If the repository name contains "/", repository will be updated
975 * If the repository name contains "/", repository will be updated
976 accordingly with a repository group or nested repository groups
976 accordingly with a repository group or nested repository groups
977
977
978 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
978 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
979 called "repo-test" and place it inside group "foo/bar".
979 called "repo-test" and place it inside group "foo/bar".
980 You have to have permissions to access and write to the last repository
980 You have to have permissions to access and write to the last repository
981 group ("bar" in this example)
981 group ("bar" in this example)
982
982
983 :param apiuser: This is filled automatically from the |authtoken|.
983 :param apiuser: This is filled automatically from the |authtoken|.
984 :type apiuser: AuthUser
984 :type apiuser: AuthUser
985 :param repoid: repository name or repository ID.
985 :param repoid: repository name or repository ID.
986 :type repoid: str or int
986 :type repoid: str or int
987 :param repo_name: Update the |repo| name, including the
987 :param repo_name: Update the |repo| name, including the
988 repository group it's in.
988 repository group it's in.
989 :type repo_name: str
989 :type repo_name: str
990 :param owner: Set the |repo| owner.
990 :param owner: Set the |repo| owner.
991 :type owner: str
991 :type owner: str
992 :param fork_of: Set the |repo| as fork of another |repo|.
992 :param fork_of: Set the |repo| as fork of another |repo|.
993 :type fork_of: str
993 :type fork_of: str
994 :param description: Update the |repo| description.
994 :param description: Update the |repo| description.
995 :type description: str
995 :type description: str
996 :param private: Set the |repo| as private. (True | False)
996 :param private: Set the |repo| as private. (True | False)
997 :type private: bool
997 :type private: bool
998 :param clone_uri: Update the |repo| clone URI.
998 :param clone_uri: Update the |repo| clone URI.
999 :type clone_uri: str
999 :type clone_uri: str
1000 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1000 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1001 :type landing_rev: str
1001 :type landing_rev: str
1002 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1002 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1003 :type enable_statistics: bool
1003 :type enable_statistics: bool
1004 :param enable_locking: Enable |repo| locking.
1004 :param enable_locking: Enable |repo| locking.
1005 :type enable_locking: bool
1005 :type enable_locking: bool
1006 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1006 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1007 :type enable_downloads: bool
1007 :type enable_downloads: bool
1008 :param fields: Add extra fields to the |repo|. Use the following
1008 :param fields: Add extra fields to the |repo|. Use the following
1009 example format: ``field_key=field_val,field_key2=fieldval2``.
1009 example format: ``field_key=field_val,field_key2=fieldval2``.
1010 Escape ', ' with \,
1010 Escape ', ' with \,
1011 :type fields: str
1011 :type fields: str
1012 """
1012 """
1013
1013
1014 repo = get_repo_or_error(repoid)
1014 repo = get_repo_or_error(repoid)
1015
1015
1016 include_secrets = False
1016 include_secrets = False
1017 if not has_superadmin_permission(apiuser):
1017 if not has_superadmin_permission(apiuser):
1018 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1018 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1019 else:
1019 else:
1020 include_secrets = True
1020 include_secrets = True
1021
1021
1022 updates = dict(
1022 updates = dict(
1023 repo_name=repo_name
1023 repo_name=repo_name
1024 if not isinstance(repo_name, Optional) else repo.repo_name,
1024 if not isinstance(repo_name, Optional) else repo.repo_name,
1025
1025
1026 fork_id=fork_of
1026 fork_id=fork_of
1027 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1027 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1028
1028
1029 user=owner
1029 user=owner
1030 if not isinstance(owner, Optional) else repo.user.username,
1030 if not isinstance(owner, Optional) else repo.user.username,
1031
1031
1032 repo_description=description
1032 repo_description=description
1033 if not isinstance(description, Optional) else repo.description,
1033 if not isinstance(description, Optional) else repo.description,
1034
1034
1035 repo_private=private
1035 repo_private=private
1036 if not isinstance(private, Optional) else repo.private,
1036 if not isinstance(private, Optional) else repo.private,
1037
1037
1038 clone_uri=clone_uri
1038 clone_uri=clone_uri
1039 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1039 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1040
1040
1041 push_uri=push_uri
1041 push_uri=push_uri
1042 if not isinstance(push_uri, Optional) else repo.push_uri,
1042 if not isinstance(push_uri, Optional) else repo.push_uri,
1043
1043
1044 repo_landing_rev=landing_rev
1044 repo_landing_rev=landing_rev
1045 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1045 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1046
1046
1047 repo_enable_statistics=enable_statistics
1047 repo_enable_statistics=enable_statistics
1048 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1048 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1049
1049
1050 repo_enable_locking=enable_locking
1050 repo_enable_locking=enable_locking
1051 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1051 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1052
1052
1053 repo_enable_downloads=enable_downloads
1053 repo_enable_downloads=enable_downloads
1054 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1054 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1055
1055
1056 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1056 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1057 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1057 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1058 request.translate, repo=repo)
1058 request.translate, repo=repo)
1059 ref_choices = list(set(ref_choices + [landing_ref]))
1059 ref_choices = list(set(ref_choices + [landing_ref]))
1060
1060
1061 old_values = repo.get_api_data()
1061 old_values = repo.get_api_data()
1062 repo_type = repo.repo_type
1062 repo_type = repo.repo_type
1063 schema = repo_schema.RepoSchema().bind(
1063 schema = repo_schema.RepoSchema().bind(
1064 repo_type_options=rhodecode.BACKENDS.keys(),
1064 repo_type_options=rhodecode.BACKENDS.keys(),
1065 repo_ref_options=ref_choices,
1065 repo_ref_options=ref_choices,
1066 repo_type=repo_type,
1066 repo_type=repo_type,
1067 # user caller
1067 # user caller
1068 user=apiuser,
1068 user=apiuser,
1069 old_values=old_values)
1069 old_values=old_values)
1070 try:
1070 try:
1071 schema_data = schema.deserialize(dict(
1071 schema_data = schema.deserialize(dict(
1072 # we save old value, users cannot change type
1072 # we save old value, users cannot change type
1073 repo_type=repo_type,
1073 repo_type=repo_type,
1074
1074
1075 repo_name=updates['repo_name'],
1075 repo_name=updates['repo_name'],
1076 repo_owner=updates['user'],
1076 repo_owner=updates['user'],
1077 repo_description=updates['repo_description'],
1077 repo_description=updates['repo_description'],
1078 repo_clone_uri=updates['clone_uri'],
1078 repo_clone_uri=updates['clone_uri'],
1079 repo_push_uri=updates['push_uri'],
1079 repo_push_uri=updates['push_uri'],
1080 repo_fork_of=updates['fork_id'],
1080 repo_fork_of=updates['fork_id'],
1081 repo_private=updates['repo_private'],
1081 repo_private=updates['repo_private'],
1082 repo_landing_commit_ref=updates['repo_landing_rev'],
1082 repo_landing_commit_ref=updates['repo_landing_rev'],
1083 repo_enable_statistics=updates['repo_enable_statistics'],
1083 repo_enable_statistics=updates['repo_enable_statistics'],
1084 repo_enable_downloads=updates['repo_enable_downloads'],
1084 repo_enable_downloads=updates['repo_enable_downloads'],
1085 repo_enable_locking=updates['repo_enable_locking']))
1085 repo_enable_locking=updates['repo_enable_locking']))
1086 except validation_schema.Invalid as err:
1086 except validation_schema.Invalid as err:
1087 raise JSONRPCValidationError(colander_exc=err)
1087 raise JSONRPCValidationError(colander_exc=err)
1088
1088
1089 # save validated data back into the updates dict
1089 # save validated data back into the updates dict
1090 validated_updates = dict(
1090 validated_updates = dict(
1091 repo_name=schema_data['repo_group']['repo_name_without_group'],
1091 repo_name=schema_data['repo_group']['repo_name_without_group'],
1092 repo_group=schema_data['repo_group']['repo_group_id'],
1092 repo_group=schema_data['repo_group']['repo_group_id'],
1093
1093
1094 user=schema_data['repo_owner'],
1094 user=schema_data['repo_owner'],
1095 repo_description=schema_data['repo_description'],
1095 repo_description=schema_data['repo_description'],
1096 repo_private=schema_data['repo_private'],
1096 repo_private=schema_data['repo_private'],
1097 clone_uri=schema_data['repo_clone_uri'],
1097 clone_uri=schema_data['repo_clone_uri'],
1098 push_uri=schema_data['repo_push_uri'],
1098 push_uri=schema_data['repo_push_uri'],
1099 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1099 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1100 repo_enable_statistics=schema_data['repo_enable_statistics'],
1100 repo_enable_statistics=schema_data['repo_enable_statistics'],
1101 repo_enable_locking=schema_data['repo_enable_locking'],
1101 repo_enable_locking=schema_data['repo_enable_locking'],
1102 repo_enable_downloads=schema_data['repo_enable_downloads'],
1102 repo_enable_downloads=schema_data['repo_enable_downloads'],
1103 )
1103 )
1104
1104
1105 if schema_data['repo_fork_of']:
1105 if schema_data['repo_fork_of']:
1106 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1106 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1107 validated_updates['fork_id'] = fork_repo.repo_id
1107 validated_updates['fork_id'] = fork_repo.repo_id
1108
1108
1109 # extra fields
1109 # extra fields
1110 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1110 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1111 if fields:
1111 if fields:
1112 validated_updates.update(fields)
1112 validated_updates.update(fields)
1113
1113
1114 try:
1114 try:
1115 RepoModel().update(repo, **validated_updates)
1115 RepoModel().update(repo, **validated_updates)
1116 audit_logger.store_api(
1116 audit_logger.store_api(
1117 'repo.edit', action_data={'old_data': old_values},
1117 'repo.edit', action_data={'old_data': old_values},
1118 user=apiuser, repo=repo)
1118 user=apiuser, repo=repo)
1119 Session().commit()
1119 Session().commit()
1120 return {
1120 return {
1121 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1121 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1122 'repository': repo.get_api_data(include_secrets=include_secrets)
1122 'repository': repo.get_api_data(include_secrets=include_secrets)
1123 }
1123 }
1124 except Exception:
1124 except Exception:
1125 log.exception(
1125 log.exception(
1126 u"Exception while trying to update the repository %s",
1126 u"Exception while trying to update the repository %s",
1127 repoid)
1127 repoid)
1128 raise JSONRPCError('failed to update repo `%s`' % repoid)
1128 raise JSONRPCError('failed to update repo `%s`' % repoid)
1129
1129
1130
1130
1131 @jsonrpc_method()
1131 @jsonrpc_method()
1132 def fork_repo(request, apiuser, repoid, fork_name,
1132 def fork_repo(request, apiuser, repoid, fork_name,
1133 owner=Optional(OAttr('apiuser')),
1133 owner=Optional(OAttr('apiuser')),
1134 description=Optional(''),
1134 description=Optional(''),
1135 private=Optional(False),
1135 private=Optional(False),
1136 clone_uri=Optional(None),
1136 clone_uri=Optional(None),
1137 landing_rev=Optional(None),
1137 landing_rev=Optional(None),
1138 copy_permissions=Optional(False)):
1138 copy_permissions=Optional(False)):
1139 """
1139 """
1140 Creates a fork of the specified |repo|.
1140 Creates a fork of the specified |repo|.
1141
1141
1142 * If the fork_name contains "/", fork will be created inside
1142 * If the fork_name contains "/", fork will be created inside
1143 a repository group or nested repository groups
1143 a repository group or nested repository groups
1144
1144
1145 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1145 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1146 inside group "foo/bar". You have to have permissions to access and
1146 inside group "foo/bar". You have to have permissions to access and
1147 write to the last repository group ("bar" in this example)
1147 write to the last repository group ("bar" in this example)
1148
1148
1149 This command can only be run using an |authtoken| with minimum
1149 This command can only be run using an |authtoken| with minimum
1150 read permissions of the forked repo, create fork permissions for an user.
1150 read permissions of the forked repo, create fork permissions for an user.
1151
1151
1152 :param apiuser: This is filled automatically from the |authtoken|.
1152 :param apiuser: This is filled automatically from the |authtoken|.
1153 :type apiuser: AuthUser
1153 :type apiuser: AuthUser
1154 :param repoid: Set repository name or repository ID.
1154 :param repoid: Set repository name or repository ID.
1155 :type repoid: str or int
1155 :type repoid: str or int
1156 :param fork_name: Set the fork name, including it's repository group membership.
1156 :param fork_name: Set the fork name, including it's repository group membership.
1157 :type fork_name: str
1157 :type fork_name: str
1158 :param owner: Set the fork owner.
1158 :param owner: Set the fork owner.
1159 :type owner: str
1159 :type owner: str
1160 :param description: Set the fork description.
1160 :param description: Set the fork description.
1161 :type description: str
1161 :type description: str
1162 :param copy_permissions: Copy permissions from parent |repo|. The
1162 :param copy_permissions: Copy permissions from parent |repo|. The
1163 default is False.
1163 default is False.
1164 :type copy_permissions: bool
1164 :type copy_permissions: bool
1165 :param private: Make the fork private. The default is False.
1165 :param private: Make the fork private. The default is False.
1166 :type private: bool
1166 :type private: bool
1167 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1167 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1168
1168
1169 Example output:
1169 Example output:
1170
1170
1171 .. code-block:: bash
1171 .. code-block:: bash
1172
1172
1173 id : <id_for_response>
1173 id : <id_for_response>
1174 api_key : "<api_key>"
1174 api_key : "<api_key>"
1175 args: {
1175 args: {
1176 "repoid" : "<reponame or repo_id>",
1176 "repoid" : "<reponame or repo_id>",
1177 "fork_name": "<forkname>",
1177 "fork_name": "<forkname>",
1178 "owner": "<username or user_id = Optional(=apiuser)>",
1178 "owner": "<username or user_id = Optional(=apiuser)>",
1179 "description": "<description>",
1179 "description": "<description>",
1180 "copy_permissions": "<bool>",
1180 "copy_permissions": "<bool>",
1181 "private": "<bool>",
1181 "private": "<bool>",
1182 "landing_rev": "<landing_rev>"
1182 "landing_rev": "<landing_rev>"
1183 }
1183 }
1184
1184
1185 Example error output:
1185 Example error output:
1186
1186
1187 .. code-block:: bash
1187 .. code-block:: bash
1188
1188
1189 id : <id_given_in_input>
1189 id : <id_given_in_input>
1190 result: {
1190 result: {
1191 "msg": "Created fork of `<reponame>` as `<forkname>`",
1191 "msg": "Created fork of `<reponame>` as `<forkname>`",
1192 "success": true,
1192 "success": true,
1193 "task": "<celery task id or None if done sync>"
1193 "task": "<celery task id or None if done sync>"
1194 }
1194 }
1195 error: null
1195 error: null
1196
1196
1197 """
1197 """
1198
1198
1199 repo = get_repo_or_error(repoid)
1199 repo = get_repo_or_error(repoid)
1200 repo_name = repo.repo_name
1200 repo_name = repo.repo_name
1201
1201
1202 if not has_superadmin_permission(apiuser):
1202 if not has_superadmin_permission(apiuser):
1203 # check if we have at least read permission for
1203 # check if we have at least read permission for
1204 # this repo that we fork !
1204 # this repo that we fork !
1205 _perms = (
1205 _perms = (
1206 'repository.admin', 'repository.write', 'repository.read')
1206 'repository.admin', 'repository.write', 'repository.read')
1207 validate_repo_permissions(apiuser, repoid, repo, _perms)
1207 validate_repo_permissions(apiuser, repoid, repo, _perms)
1208
1208
1209 # check if the regular user has at least fork permissions as well
1209 # check if the regular user has at least fork permissions as well
1210 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1210 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1211 raise JSONRPCForbidden()
1211 raise JSONRPCForbidden()
1212
1212
1213 # check if user can set owner parameter
1213 # check if user can set owner parameter
1214 owner = validate_set_owner_permissions(apiuser, owner)
1214 owner = validate_set_owner_permissions(apiuser, owner)
1215
1215
1216 description = Optional.extract(description)
1216 description = Optional.extract(description)
1217 copy_permissions = Optional.extract(copy_permissions)
1217 copy_permissions = Optional.extract(copy_permissions)
1218 clone_uri = Optional.extract(clone_uri)
1218 clone_uri = Optional.extract(clone_uri)
1219
1219
1220 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1220 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1221 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1221 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1222 ref_choices = list(set(ref_choices + [landing_ref]))
1222 ref_choices = list(set(ref_choices + [landing_ref]))
1223 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1223 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1224
1224
1225 private = Optional.extract(private)
1225 private = Optional.extract(private)
1226
1226
1227 schema = repo_schema.RepoSchema().bind(
1227 schema = repo_schema.RepoSchema().bind(
1228 repo_type_options=rhodecode.BACKENDS.keys(),
1228 repo_type_options=rhodecode.BACKENDS.keys(),
1229 repo_ref_options=ref_choices,
1229 repo_ref_options=ref_choices,
1230 repo_type=repo.repo_type,
1230 repo_type=repo.repo_type,
1231 # user caller
1231 # user caller
1232 user=apiuser)
1232 user=apiuser)
1233
1233
1234 try:
1234 try:
1235 schema_data = schema.deserialize(dict(
1235 schema_data = schema.deserialize(dict(
1236 repo_name=fork_name,
1236 repo_name=fork_name,
1237 repo_type=repo.repo_type,
1237 repo_type=repo.repo_type,
1238 repo_owner=owner.username,
1238 repo_owner=owner.username,
1239 repo_description=description,
1239 repo_description=description,
1240 repo_landing_commit_ref=landing_commit_ref,
1240 repo_landing_commit_ref=landing_commit_ref,
1241 repo_clone_uri=clone_uri,
1241 repo_clone_uri=clone_uri,
1242 repo_private=private,
1242 repo_private=private,
1243 repo_copy_permissions=copy_permissions))
1243 repo_copy_permissions=copy_permissions))
1244 except validation_schema.Invalid as err:
1244 except validation_schema.Invalid as err:
1245 raise JSONRPCValidationError(colander_exc=err)
1245 raise JSONRPCValidationError(colander_exc=err)
1246
1246
1247 try:
1247 try:
1248 data = {
1248 data = {
1249 'fork_parent_id': repo.repo_id,
1249 'fork_parent_id': repo.repo_id,
1250
1250
1251 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1251 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1252 'repo_name_full': schema_data['repo_name'],
1252 'repo_name_full': schema_data['repo_name'],
1253 'repo_group': schema_data['repo_group']['repo_group_id'],
1253 'repo_group': schema_data['repo_group']['repo_group_id'],
1254 'repo_type': schema_data['repo_type'],
1254 'repo_type': schema_data['repo_type'],
1255 'description': schema_data['repo_description'],
1255 'description': schema_data['repo_description'],
1256 'private': schema_data['repo_private'],
1256 'private': schema_data['repo_private'],
1257 'copy_permissions': schema_data['repo_copy_permissions'],
1257 'copy_permissions': schema_data['repo_copy_permissions'],
1258 'landing_rev': schema_data['repo_landing_commit_ref'],
1258 'landing_rev': schema_data['repo_landing_commit_ref'],
1259 }
1259 }
1260
1260
1261 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1261 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1262 # no commit, it's done in RepoModel, or async via celery
1262 # no commit, it's done in RepoModel, or async via celery
1263 task_id = get_task_id(task)
1263 task_id = get_task_id(task)
1264
1264
1265 return {
1265 return {
1266 'msg': 'Created fork of `%s` as `%s`' % (
1266 'msg': 'Created fork of `%s` as `%s`' % (
1267 repo.repo_name, schema_data['repo_name']),
1267 repo.repo_name, schema_data['repo_name']),
1268 'success': True, # cannot return the repo data here since fork
1268 'success': True, # cannot return the repo data here since fork
1269 # can be done async
1269 # can be done async
1270 'task': task_id
1270 'task': task_id
1271 }
1271 }
1272 except Exception:
1272 except Exception:
1273 log.exception(
1273 log.exception(
1274 u"Exception while trying to create fork %s",
1274 u"Exception while trying to create fork %s",
1275 schema_data['repo_name'])
1275 schema_data['repo_name'])
1276 raise JSONRPCError(
1276 raise JSONRPCError(
1277 'failed to fork repository `%s` as `%s`' % (
1277 'failed to fork repository `%s` as `%s`' % (
1278 repo_name, schema_data['repo_name']))
1278 repo_name, schema_data['repo_name']))
1279
1279
1280
1280
1281 @jsonrpc_method()
1281 @jsonrpc_method()
1282 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1282 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1283 """
1283 """
1284 Deletes a repository.
1284 Deletes a repository.
1285
1285
1286 * When the `forks` parameter is set it's possible to detach or delete
1286 * When the `forks` parameter is set it's possible to detach or delete
1287 forks of deleted repository.
1287 forks of deleted repository.
1288
1288
1289 This command can only be run using an |authtoken| with admin
1289 This command can only be run using an |authtoken| with admin
1290 permissions on the |repo|.
1290 permissions on the |repo|.
1291
1291
1292 :param apiuser: This is filled automatically from the |authtoken|.
1292 :param apiuser: This is filled automatically from the |authtoken|.
1293 :type apiuser: AuthUser
1293 :type apiuser: AuthUser
1294 :param repoid: Set the repository name or repository ID.
1294 :param repoid: Set the repository name or repository ID.
1295 :type repoid: str or int
1295 :type repoid: str or int
1296 :param forks: Set to `detach` or `delete` forks from the |repo|.
1296 :param forks: Set to `detach` or `delete` forks from the |repo|.
1297 :type forks: Optional(str)
1297 :type forks: Optional(str)
1298
1298
1299 Example error output:
1299 Example error output:
1300
1300
1301 .. code-block:: bash
1301 .. code-block:: bash
1302
1302
1303 id : <id_given_in_input>
1303 id : <id_given_in_input>
1304 result: {
1304 result: {
1305 "msg": "Deleted repository `<reponame>`",
1305 "msg": "Deleted repository `<reponame>`",
1306 "success": true
1306 "success": true
1307 }
1307 }
1308 error: null
1308 error: null
1309 """
1309 """
1310
1310
1311 repo = get_repo_or_error(repoid)
1311 repo = get_repo_or_error(repoid)
1312 repo_name = repo.repo_name
1312 repo_name = repo.repo_name
1313 if not has_superadmin_permission(apiuser):
1313 if not has_superadmin_permission(apiuser):
1314 _perms = ('repository.admin',)
1314 _perms = ('repository.admin',)
1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1316
1316
1317 try:
1317 try:
1318 handle_forks = Optional.extract(forks)
1318 handle_forks = Optional.extract(forks)
1319 _forks_msg = ''
1319 _forks_msg = ''
1320 _forks = [f for f in repo.forks]
1320 _forks = [f for f in repo.forks]
1321 if handle_forks == 'detach':
1321 if handle_forks == 'detach':
1322 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1322 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1323 elif handle_forks == 'delete':
1323 elif handle_forks == 'delete':
1324 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1324 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1325 elif _forks:
1325 elif _forks:
1326 raise JSONRPCError(
1326 raise JSONRPCError(
1327 'Cannot delete `%s` it still contains attached forks' %
1327 'Cannot delete `%s` it still contains attached forks' %
1328 (repo.repo_name,)
1328 (repo.repo_name,)
1329 )
1329 )
1330 old_data = repo.get_api_data()
1330 old_data = repo.get_api_data()
1331 RepoModel().delete(repo, forks=forks)
1331 RepoModel().delete(repo, forks=forks)
1332
1332
1333 repo = audit_logger.RepoWrap(repo_id=None,
1333 repo = audit_logger.RepoWrap(repo_id=None,
1334 repo_name=repo.repo_name)
1334 repo_name=repo.repo_name)
1335
1335
1336 audit_logger.store_api(
1336 audit_logger.store_api(
1337 'repo.delete', action_data={'old_data': old_data},
1337 'repo.delete', action_data={'old_data': old_data},
1338 user=apiuser, repo=repo)
1338 user=apiuser, repo=repo)
1339
1339
1340 ScmModel().mark_for_invalidation(repo_name, delete=True)
1340 ScmModel().mark_for_invalidation(repo_name, delete=True)
1341 Session().commit()
1341 Session().commit()
1342 return {
1342 return {
1343 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1343 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1344 'success': True
1344 'success': True
1345 }
1345 }
1346 except Exception:
1346 except Exception:
1347 log.exception("Exception occurred while trying to delete repo")
1347 log.exception("Exception occurred while trying to delete repo")
1348 raise JSONRPCError(
1348 raise JSONRPCError(
1349 'failed to delete repository `%s`' % (repo_name,)
1349 'failed to delete repository `%s`' % (repo_name,)
1350 )
1350 )
1351
1351
1352
1352
1353 #TODO: marcink, change name ?
1353 #TODO: marcink, change name ?
1354 @jsonrpc_method()
1354 @jsonrpc_method()
1355 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1355 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1356 """
1356 """
1357 Invalidates the cache for the specified repository.
1357 Invalidates the cache for the specified repository.
1358
1358
1359 This command can only be run using an |authtoken| with admin rights to
1359 This command can only be run using an |authtoken| with admin rights to
1360 the specified repository.
1360 the specified repository.
1361
1361
1362 This command takes the following options:
1362 This command takes the following options:
1363
1363
1364 :param apiuser: This is filled automatically from |authtoken|.
1364 :param apiuser: This is filled automatically from |authtoken|.
1365 :type apiuser: AuthUser
1365 :type apiuser: AuthUser
1366 :param repoid: Sets the repository name or repository ID.
1366 :param repoid: Sets the repository name or repository ID.
1367 :type repoid: str or int
1367 :type repoid: str or int
1368 :param delete_keys: This deletes the invalidated keys instead of
1368 :param delete_keys: This deletes the invalidated keys instead of
1369 just flagging them.
1369 just flagging them.
1370 :type delete_keys: Optional(``True`` | ``False``)
1370 :type delete_keys: Optional(``True`` | ``False``)
1371
1371
1372 Example output:
1372 Example output:
1373
1373
1374 .. code-block:: bash
1374 .. code-block:: bash
1375
1375
1376 id : <id_given_in_input>
1376 id : <id_given_in_input>
1377 result : {
1377 result : {
1378 'msg': Cache for repository `<repository name>` was invalidated,
1378 'msg': Cache for repository `<repository name>` was invalidated,
1379 'repository': <repository name>
1379 'repository': <repository name>
1380 }
1380 }
1381 error : null
1381 error : null
1382
1382
1383 Example error output:
1383 Example error output:
1384
1384
1385 .. code-block:: bash
1385 .. code-block:: bash
1386
1386
1387 id : <id_given_in_input>
1387 id : <id_given_in_input>
1388 result : null
1388 result : null
1389 error : {
1389 error : {
1390 'Error occurred during cache invalidation action'
1390 'Error occurred during cache invalidation action'
1391 }
1391 }
1392
1392
1393 """
1393 """
1394
1394
1395 repo = get_repo_or_error(repoid)
1395 repo = get_repo_or_error(repoid)
1396 if not has_superadmin_permission(apiuser):
1396 if not has_superadmin_permission(apiuser):
1397 _perms = ('repository.admin', 'repository.write',)
1397 _perms = ('repository.admin', 'repository.write',)
1398 validate_repo_permissions(apiuser, repoid, repo, _perms)
1398 validate_repo_permissions(apiuser, repoid, repo, _perms)
1399
1399
1400 delete = Optional.extract(delete_keys)
1400 delete = Optional.extract(delete_keys)
1401 try:
1401 try:
1402 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1402 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1403 return {
1403 return {
1404 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1404 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1405 'repository': repo.repo_name
1405 'repository': repo.repo_name
1406 }
1406 }
1407 except Exception:
1407 except Exception:
1408 log.exception(
1408 log.exception(
1409 "Exception occurred while trying to invalidate repo cache")
1409 "Exception occurred while trying to invalidate repo cache")
1410 raise JSONRPCError(
1410 raise JSONRPCError(
1411 'Error occurred during cache invalidation action'
1411 'Error occurred during cache invalidation action'
1412 )
1412 )
1413
1413
1414
1414
1415 #TODO: marcink, change name ?
1415 #TODO: marcink, change name ?
1416 @jsonrpc_method()
1416 @jsonrpc_method()
1417 def lock(request, apiuser, repoid, locked=Optional(None),
1417 def lock(request, apiuser, repoid, locked=Optional(None),
1418 userid=Optional(OAttr('apiuser'))):
1418 userid=Optional(OAttr('apiuser'))):
1419 """
1419 """
1420 Sets the lock state of the specified |repo| by the given user.
1420 Sets the lock state of the specified |repo| by the given user.
1421 From more information, see :ref:`repo-locking`.
1421 From more information, see :ref:`repo-locking`.
1422
1422
1423 * If the ``userid`` option is not set, the repository is locked to the
1423 * If the ``userid`` option is not set, the repository is locked to the
1424 user who called the method.
1424 user who called the method.
1425 * If the ``locked`` parameter is not set, the current lock state of the
1425 * If the ``locked`` parameter is not set, the current lock state of the
1426 repository is displayed.
1426 repository is displayed.
1427
1427
1428 This command can only be run using an |authtoken| with admin rights to
1428 This command can only be run using an |authtoken| with admin rights to
1429 the specified repository.
1429 the specified repository.
1430
1430
1431 This command takes the following options:
1431 This command takes the following options:
1432
1432
1433 :param apiuser: This is filled automatically from the |authtoken|.
1433 :param apiuser: This is filled automatically from the |authtoken|.
1434 :type apiuser: AuthUser
1434 :type apiuser: AuthUser
1435 :param repoid: Sets the repository name or repository ID.
1435 :param repoid: Sets the repository name or repository ID.
1436 :type repoid: str or int
1436 :type repoid: str or int
1437 :param locked: Sets the lock state.
1437 :param locked: Sets the lock state.
1438 :type locked: Optional(``True`` | ``False``)
1438 :type locked: Optional(``True`` | ``False``)
1439 :param userid: Set the repository lock to this user.
1439 :param userid: Set the repository lock to this user.
1440 :type userid: Optional(str or int)
1440 :type userid: Optional(str or int)
1441
1441
1442 Example error output:
1442 Example error output:
1443
1443
1444 .. code-block:: bash
1444 .. code-block:: bash
1445
1445
1446 id : <id_given_in_input>
1446 id : <id_given_in_input>
1447 result : {
1447 result : {
1448 'repo': '<reponame>',
1448 'repo': '<reponame>',
1449 'locked': <bool: lock state>,
1449 'locked': <bool: lock state>,
1450 'locked_since': <int: lock timestamp>,
1450 'locked_since': <int: lock timestamp>,
1451 'locked_by': <username of person who made the lock>,
1451 'locked_by': <username of person who made the lock>,
1452 'lock_reason': <str: reason for locking>,
1452 'lock_reason': <str: reason for locking>,
1453 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1453 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1454 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1454 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1455 or
1455 or
1456 'msg': 'Repo `<repository name>` not locked.'
1456 'msg': 'Repo `<repository name>` not locked.'
1457 or
1457 or
1458 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1458 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1459 }
1459 }
1460 error : null
1460 error : null
1461
1461
1462 Example error output:
1462 Example error output:
1463
1463
1464 .. code-block:: bash
1464 .. code-block:: bash
1465
1465
1466 id : <id_given_in_input>
1466 id : <id_given_in_input>
1467 result : null
1467 result : null
1468 error : {
1468 error : {
1469 'Error occurred locking repository `<reponame>`'
1469 'Error occurred locking repository `<reponame>`'
1470 }
1470 }
1471 """
1471 """
1472
1472
1473 repo = get_repo_or_error(repoid)
1473 repo = get_repo_or_error(repoid)
1474 if not has_superadmin_permission(apiuser):
1474 if not has_superadmin_permission(apiuser):
1475 # check if we have at least write permission for this repo !
1475 # check if we have at least write permission for this repo !
1476 _perms = ('repository.admin', 'repository.write',)
1476 _perms = ('repository.admin', 'repository.write',)
1477 validate_repo_permissions(apiuser, repoid, repo, _perms)
1477 validate_repo_permissions(apiuser, repoid, repo, _perms)
1478
1478
1479 # make sure normal user does not pass someone else userid,
1479 # make sure normal user does not pass someone else userid,
1480 # he is not allowed to do that
1480 # he is not allowed to do that
1481 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1481 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1482 raise JSONRPCError('userid is not the same as your user')
1482 raise JSONRPCError('userid is not the same as your user')
1483
1483
1484 if isinstance(userid, Optional):
1484 if isinstance(userid, Optional):
1485 userid = apiuser.user_id
1485 userid = apiuser.user_id
1486
1486
1487 user = get_user_or_error(userid)
1487 user = get_user_or_error(userid)
1488
1488
1489 if isinstance(locked, Optional):
1489 if isinstance(locked, Optional):
1490 lockobj = repo.locked
1490 lockobj = repo.locked
1491
1491
1492 if lockobj[0] is None:
1492 if lockobj[0] is None:
1493 _d = {
1493 _d = {
1494 'repo': repo.repo_name,
1494 'repo': repo.repo_name,
1495 'locked': False,
1495 'locked': False,
1496 'locked_since': None,
1496 'locked_since': None,
1497 'locked_by': None,
1497 'locked_by': None,
1498 'lock_reason': None,
1498 'lock_reason': None,
1499 'lock_state_changed': False,
1499 'lock_state_changed': False,
1500 'msg': 'Repo `%s` not locked.' % repo.repo_name
1500 'msg': 'Repo `%s` not locked.' % repo.repo_name
1501 }
1501 }
1502 return _d
1502 return _d
1503 else:
1503 else:
1504 _user_id, _time, _reason = lockobj
1504 _user_id, _time, _reason = lockobj
1505 lock_user = get_user_or_error(userid)
1505 lock_user = get_user_or_error(userid)
1506 _d = {
1506 _d = {
1507 'repo': repo.repo_name,
1507 'repo': repo.repo_name,
1508 'locked': True,
1508 'locked': True,
1509 'locked_since': _time,
1509 'locked_since': _time,
1510 'locked_by': lock_user.username,
1510 'locked_by': lock_user.username,
1511 'lock_reason': _reason,
1511 'lock_reason': _reason,
1512 'lock_state_changed': False,
1512 'lock_state_changed': False,
1513 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1513 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1514 % (repo.repo_name, lock_user.username,
1514 % (repo.repo_name, lock_user.username,
1515 json.dumps(time_to_datetime(_time))))
1515 json.dumps(time_to_datetime(_time))))
1516 }
1516 }
1517 return _d
1517 return _d
1518
1518
1519 # force locked state through a flag
1519 # force locked state through a flag
1520 else:
1520 else:
1521 locked = str2bool(locked)
1521 locked = str2bool(locked)
1522 lock_reason = Repository.LOCK_API
1522 lock_reason = Repository.LOCK_API
1523 try:
1523 try:
1524 if locked:
1524 if locked:
1525 lock_time = time.time()
1525 lock_time = time.time()
1526 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1526 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1527 else:
1527 else:
1528 lock_time = None
1528 lock_time = None
1529 Repository.unlock(repo)
1529 Repository.unlock(repo)
1530 _d = {
1530 _d = {
1531 'repo': repo.repo_name,
1531 'repo': repo.repo_name,
1532 'locked': locked,
1532 'locked': locked,
1533 'locked_since': lock_time,
1533 'locked_since': lock_time,
1534 'locked_by': user.username,
1534 'locked_by': user.username,
1535 'lock_reason': lock_reason,
1535 'lock_reason': lock_reason,
1536 'lock_state_changed': True,
1536 'lock_state_changed': True,
1537 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1537 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1538 % (user.username, repo.repo_name, locked))
1538 % (user.username, repo.repo_name, locked))
1539 }
1539 }
1540 return _d
1540 return _d
1541 except Exception:
1541 except Exception:
1542 log.exception(
1542 log.exception(
1543 "Exception occurred while trying to lock repository")
1543 "Exception occurred while trying to lock repository")
1544 raise JSONRPCError(
1544 raise JSONRPCError(
1545 'Error occurred locking repository `%s`' % repo.repo_name
1545 'Error occurred locking repository `%s`' % repo.repo_name
1546 )
1546 )
1547
1547
1548
1548
1549 @jsonrpc_method()
1549 @jsonrpc_method()
1550 def comment_commit(
1550 def comment_commit(
1551 request, apiuser, repoid, commit_id, message, status=Optional(None),
1551 request, apiuser, repoid, commit_id, message, status=Optional(None),
1552 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1552 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1553 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
1553 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
1554 userid=Optional(OAttr('apiuser'))):
1554 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
1555 """
1555 """
1556 Set a commit comment, and optionally change the status of the commit.
1556 Set a commit comment, and optionally change the status of the commit.
1557
1557
1558 :param apiuser: This is filled automatically from the |authtoken|.
1558 :param apiuser: This is filled automatically from the |authtoken|.
1559 :type apiuser: AuthUser
1559 :type apiuser: AuthUser
1560 :param repoid: Set the repository name or repository ID.
1560 :param repoid: Set the repository name or repository ID.
1561 :type repoid: str or int
1561 :type repoid: str or int
1562 :param commit_id: Specify the commit_id for which to set a comment.
1562 :param commit_id: Specify the commit_id for which to set a comment.
1563 :type commit_id: str
1563 :type commit_id: str
1564 :param message: The comment text.
1564 :param message: The comment text.
1565 :type message: str
1565 :type message: str
1566 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1566 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1567 'approved', 'rejected', 'under_review'
1567 'approved', 'rejected', 'under_review'
1568 :type status: str
1568 :type status: str
1569 :param comment_type: Comment type, one of: 'note', 'todo'
1569 :param comment_type: Comment type, one of: 'note', 'todo'
1570 :type comment_type: Optional(str), default: 'note'
1570 :type comment_type: Optional(str), default: 'note'
1571 :param resolves_comment_id: id of comment which this one will resolve
1571 :param resolves_comment_id: id of comment which this one will resolve
1572 :type resolves_comment_id: Optional(int)
1572 :type resolves_comment_id: Optional(int)
1573 :param extra_recipients: list of user ids or usernames to add
1573 :param extra_recipients: list of user ids or usernames to add
1574 notifications for this comment. Acts like a CC for notification
1574 notifications for this comment. Acts like a CC for notification
1575 :type extra_recipients: Optional(list)
1575 :type extra_recipients: Optional(list)
1576 :param userid: Set the user name of the comment creator.
1576 :param userid: Set the user name of the comment creator.
1577 :type userid: Optional(str or int)
1577 :type userid: Optional(str or int)
1578 :param send_email: Define if this comment should also send email notification
1579 :type send_email: Optional(bool)
1578
1580
1579 Example error output:
1581 Example error output:
1580
1582
1581 .. code-block:: bash
1583 .. code-block:: bash
1582
1584
1583 {
1585 {
1584 "id" : <id_given_in_input>,
1586 "id" : <id_given_in_input>,
1585 "result" : {
1587 "result" : {
1586 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1588 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1587 "status_change": null or <status>,
1589 "status_change": null or <status>,
1588 "success": true
1590 "success": true
1589 },
1591 },
1590 "error" : null
1592 "error" : null
1591 }
1593 }
1592
1594
1593 """
1595 """
1594 repo = get_repo_or_error(repoid)
1596 repo = get_repo_or_error(repoid)
1595 if not has_superadmin_permission(apiuser):
1597 if not has_superadmin_permission(apiuser):
1596 _perms = ('repository.read', 'repository.write', 'repository.admin')
1598 _perms = ('repository.read', 'repository.write', 'repository.admin')
1597 validate_repo_permissions(apiuser, repoid, repo, _perms)
1599 validate_repo_permissions(apiuser, repoid, repo, _perms)
1598
1600
1599 try:
1601 try:
1600 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1602 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1601 except Exception as e:
1603 except Exception as e:
1602 log.exception('Failed to fetch commit')
1604 log.exception('Failed to fetch commit')
1603 raise JSONRPCError(safe_str(e))
1605 raise JSONRPCError(safe_str(e))
1604
1606
1605 if isinstance(userid, Optional):
1607 if isinstance(userid, Optional):
1606 userid = apiuser.user_id
1608 userid = apiuser.user_id
1607
1609
1608 user = get_user_or_error(userid)
1610 user = get_user_or_error(userid)
1609 status = Optional.extract(status)
1611 status = Optional.extract(status)
1610 comment_type = Optional.extract(comment_type)
1612 comment_type = Optional.extract(comment_type)
1611 resolves_comment_id = Optional.extract(resolves_comment_id)
1613 resolves_comment_id = Optional.extract(resolves_comment_id)
1612 extra_recipients = Optional.extract(extra_recipients)
1614 extra_recipients = Optional.extract(extra_recipients)
1615 send_email = Optional.extract(send_email, binary=True)
1613
1616
1614 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1617 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1615 if status and status not in allowed_statuses:
1618 if status and status not in allowed_statuses:
1616 raise JSONRPCError('Bad status, must be on '
1619 raise JSONRPCError('Bad status, must be on '
1617 'of %s got %s' % (allowed_statuses, status,))
1620 'of %s got %s' % (allowed_statuses, status,))
1618
1621
1619 if resolves_comment_id:
1622 if resolves_comment_id:
1620 comment = ChangesetComment.get(resolves_comment_id)
1623 comment = ChangesetComment.get(resolves_comment_id)
1621 if not comment:
1624 if not comment:
1622 raise JSONRPCError(
1625 raise JSONRPCError(
1623 'Invalid resolves_comment_id `%s` for this commit.'
1626 'Invalid resolves_comment_id `%s` for this commit.'
1624 % resolves_comment_id)
1627 % resolves_comment_id)
1625 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1628 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1626 raise JSONRPCError(
1629 raise JSONRPCError(
1627 'Comment `%s` is wrong type for setting status to resolved.'
1630 'Comment `%s` is wrong type for setting status to resolved.'
1628 % resolves_comment_id)
1631 % resolves_comment_id)
1629
1632
1630 try:
1633 try:
1631 rc_config = SettingsModel().get_all_settings()
1634 rc_config = SettingsModel().get_all_settings()
1632 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1635 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1633 status_change_label = ChangesetStatus.get_status_lbl(status)
1636 status_change_label = ChangesetStatus.get_status_lbl(status)
1634 comment = CommentsModel().create(
1637 comment = CommentsModel().create(
1635 message, repo, user, commit_id=commit_id,
1638 message, repo, user, commit_id=commit_id,
1636 status_change=status_change_label,
1639 status_change=status_change_label,
1637 status_change_type=status,
1640 status_change_type=status,
1638 renderer=renderer,
1641 renderer=renderer,
1639 comment_type=comment_type,
1642 comment_type=comment_type,
1640 resolves_comment_id=resolves_comment_id,
1643 resolves_comment_id=resolves_comment_id,
1641 auth_user=apiuser,
1644 auth_user=apiuser,
1642 extra_recipients=extra_recipients
1645 extra_recipients=extra_recipients,
1646 send_email=send_email
1643 )
1647 )
1644 if status:
1648 if status:
1645 # also do a status change
1649 # also do a status change
1646 try:
1650 try:
1647 ChangesetStatusModel().set_status(
1651 ChangesetStatusModel().set_status(
1648 repo, status, user, comment, revision=commit_id,
1652 repo, status, user, comment, revision=commit_id,
1649 dont_allow_on_closed_pull_request=True
1653 dont_allow_on_closed_pull_request=True
1650 )
1654 )
1651 except StatusChangeOnClosedPullRequestError:
1655 except StatusChangeOnClosedPullRequestError:
1652 log.exception(
1656 log.exception(
1653 "Exception occurred while trying to change repo commit status")
1657 "Exception occurred while trying to change repo commit status")
1654 msg = ('Changing status on a changeset associated with '
1658 msg = ('Changing status on a changeset associated with '
1655 'a closed pull request is not allowed')
1659 'a closed pull request is not allowed')
1656 raise JSONRPCError(msg)
1660 raise JSONRPCError(msg)
1657
1661
1658 Session().commit()
1662 Session().commit()
1659 return {
1663 return {
1660 'msg': (
1664 'msg': (
1661 'Commented on commit `%s` for repository `%s`' % (
1665 'Commented on commit `%s` for repository `%s`' % (
1662 comment.revision, repo.repo_name)),
1666 comment.revision, repo.repo_name)),
1663 'status_change': status,
1667 'status_change': status,
1664 'success': True,
1668 'success': True,
1665 }
1669 }
1666 except JSONRPCError:
1670 except JSONRPCError:
1667 # catch any inside errors, and re-raise them to prevent from
1671 # catch any inside errors, and re-raise them to prevent from
1668 # below global catch to silence them
1672 # below global catch to silence them
1669 raise
1673 raise
1670 except Exception:
1674 except Exception:
1671 log.exception("Exception occurred while trying to comment on commit")
1675 log.exception("Exception occurred while trying to comment on commit")
1672 raise JSONRPCError(
1676 raise JSONRPCError(
1673 'failed to set comment on repository `%s`' % (repo.repo_name,)
1677 'failed to set comment on repository `%s`' % (repo.repo_name,)
1674 )
1678 )
1675
1679
1676
1680
1677 @jsonrpc_method()
1681 @jsonrpc_method()
1678 def get_repo_comments(request, apiuser, repoid,
1682 def get_repo_comments(request, apiuser, repoid,
1679 commit_id=Optional(None), comment_type=Optional(None),
1683 commit_id=Optional(None), comment_type=Optional(None),
1680 userid=Optional(None)):
1684 userid=Optional(None)):
1681 """
1685 """
1682 Get all comments for a repository
1686 Get all comments for a repository
1683
1687
1684 :param apiuser: This is filled automatically from the |authtoken|.
1688 :param apiuser: This is filled automatically from the |authtoken|.
1685 :type apiuser: AuthUser
1689 :type apiuser: AuthUser
1686 :param repoid: Set the repository name or repository ID.
1690 :param repoid: Set the repository name or repository ID.
1687 :type repoid: str or int
1691 :type repoid: str or int
1688 :param commit_id: Optionally filter the comments by the commit_id
1692 :param commit_id: Optionally filter the comments by the commit_id
1689 :type commit_id: Optional(str), default: None
1693 :type commit_id: Optional(str), default: None
1690 :param comment_type: Optionally filter the comments by the comment_type
1694 :param comment_type: Optionally filter the comments by the comment_type
1691 one of: 'note', 'todo'
1695 one of: 'note', 'todo'
1692 :type comment_type: Optional(str), default: None
1696 :type comment_type: Optional(str), default: None
1693 :param userid: Optionally filter the comments by the author of comment
1697 :param userid: Optionally filter the comments by the author of comment
1694 :type userid: Optional(str or int), Default: None
1698 :type userid: Optional(str or int), Default: None
1695
1699
1696 Example error output:
1700 Example error output:
1697
1701
1698 .. code-block:: bash
1702 .. code-block:: bash
1699
1703
1700 {
1704 {
1701 "id" : <id_given_in_input>,
1705 "id" : <id_given_in_input>,
1702 "result" : [
1706 "result" : [
1703 {
1707 {
1704 "comment_author": <USER_DETAILS>,
1708 "comment_author": <USER_DETAILS>,
1705 "comment_created_on": "2017-02-01T14:38:16.309",
1709 "comment_created_on": "2017-02-01T14:38:16.309",
1706 "comment_f_path": "file.txt",
1710 "comment_f_path": "file.txt",
1707 "comment_id": 282,
1711 "comment_id": 282,
1708 "comment_lineno": "n1",
1712 "comment_lineno": "n1",
1709 "comment_resolved_by": null,
1713 "comment_resolved_by": null,
1710 "comment_status": [],
1714 "comment_status": [],
1711 "comment_text": "This file needs a header",
1715 "comment_text": "This file needs a header",
1712 "comment_type": "todo"
1716 "comment_type": "todo"
1713 }
1717 }
1714 ],
1718 ],
1715 "error" : null
1719 "error" : null
1716 }
1720 }
1717
1721
1718 """
1722 """
1719 repo = get_repo_or_error(repoid)
1723 repo = get_repo_or_error(repoid)
1720 if not has_superadmin_permission(apiuser):
1724 if not has_superadmin_permission(apiuser):
1721 _perms = ('repository.read', 'repository.write', 'repository.admin')
1725 _perms = ('repository.read', 'repository.write', 'repository.admin')
1722 validate_repo_permissions(apiuser, repoid, repo, _perms)
1726 validate_repo_permissions(apiuser, repoid, repo, _perms)
1723
1727
1724 commit_id = Optional.extract(commit_id)
1728 commit_id = Optional.extract(commit_id)
1725
1729
1726 userid = Optional.extract(userid)
1730 userid = Optional.extract(userid)
1727 if userid:
1731 if userid:
1728 user = get_user_or_error(userid)
1732 user = get_user_or_error(userid)
1729 else:
1733 else:
1730 user = None
1734 user = None
1731
1735
1732 comment_type = Optional.extract(comment_type)
1736 comment_type = Optional.extract(comment_type)
1733 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1737 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1734 raise JSONRPCError(
1738 raise JSONRPCError(
1735 'comment_type must be one of `{}` got {}'.format(
1739 'comment_type must be one of `{}` got {}'.format(
1736 ChangesetComment.COMMENT_TYPES, comment_type)
1740 ChangesetComment.COMMENT_TYPES, comment_type)
1737 )
1741 )
1738
1742
1739 comments = CommentsModel().get_repository_comments(
1743 comments = CommentsModel().get_repository_comments(
1740 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1744 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1741 return comments
1745 return comments
1742
1746
1743
1747
1744 @jsonrpc_method()
1748 @jsonrpc_method()
1745 def grant_user_permission(request, apiuser, repoid, userid, perm):
1749 def grant_user_permission(request, apiuser, repoid, userid, perm):
1746 """
1750 """
1747 Grant permissions for the specified user on the given repository,
1751 Grant permissions for the specified user on the given repository,
1748 or update existing permissions if found.
1752 or update existing permissions if found.
1749
1753
1750 This command can only be run using an |authtoken| with admin
1754 This command can only be run using an |authtoken| with admin
1751 permissions on the |repo|.
1755 permissions on the |repo|.
1752
1756
1753 :param apiuser: This is filled automatically from the |authtoken|.
1757 :param apiuser: This is filled automatically from the |authtoken|.
1754 :type apiuser: AuthUser
1758 :type apiuser: AuthUser
1755 :param repoid: Set the repository name or repository ID.
1759 :param repoid: Set the repository name or repository ID.
1756 :type repoid: str or int
1760 :type repoid: str or int
1757 :param userid: Set the user name.
1761 :param userid: Set the user name.
1758 :type userid: str
1762 :type userid: str
1759 :param perm: Set the user permissions, using the following format
1763 :param perm: Set the user permissions, using the following format
1760 ``(repository.(none|read|write|admin))``
1764 ``(repository.(none|read|write|admin))``
1761 :type perm: str
1765 :type perm: str
1762
1766
1763 Example output:
1767 Example output:
1764
1768
1765 .. code-block:: bash
1769 .. code-block:: bash
1766
1770
1767 id : <id_given_in_input>
1771 id : <id_given_in_input>
1768 result: {
1772 result: {
1769 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1773 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1770 "success": true
1774 "success": true
1771 }
1775 }
1772 error: null
1776 error: null
1773 """
1777 """
1774
1778
1775 repo = get_repo_or_error(repoid)
1779 repo = get_repo_or_error(repoid)
1776 user = get_user_or_error(userid)
1780 user = get_user_or_error(userid)
1777 perm = get_perm_or_error(perm)
1781 perm = get_perm_or_error(perm)
1778 if not has_superadmin_permission(apiuser):
1782 if not has_superadmin_permission(apiuser):
1779 _perms = ('repository.admin',)
1783 _perms = ('repository.admin',)
1780 validate_repo_permissions(apiuser, repoid, repo, _perms)
1784 validate_repo_permissions(apiuser, repoid, repo, _perms)
1781
1785
1782 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1786 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1783 try:
1787 try:
1784 changes = RepoModel().update_permissions(
1788 changes = RepoModel().update_permissions(
1785 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1789 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1786
1790
1787 action_data = {
1791 action_data = {
1788 'added': changes['added'],
1792 'added': changes['added'],
1789 'updated': changes['updated'],
1793 'updated': changes['updated'],
1790 'deleted': changes['deleted'],
1794 'deleted': changes['deleted'],
1791 }
1795 }
1792 audit_logger.store_api(
1796 audit_logger.store_api(
1793 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1797 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1794 Session().commit()
1798 Session().commit()
1795 PermissionModel().flush_user_permission_caches(changes)
1799 PermissionModel().flush_user_permission_caches(changes)
1796
1800
1797 return {
1801 return {
1798 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1802 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1799 perm.permission_name, user.username, repo.repo_name
1803 perm.permission_name, user.username, repo.repo_name
1800 ),
1804 ),
1801 'success': True
1805 'success': True
1802 }
1806 }
1803 except Exception:
1807 except Exception:
1804 log.exception("Exception occurred while trying edit permissions for repo")
1808 log.exception("Exception occurred while trying edit permissions for repo")
1805 raise JSONRPCError(
1809 raise JSONRPCError(
1806 'failed to edit permission for user: `%s` in repo: `%s`' % (
1810 'failed to edit permission for user: `%s` in repo: `%s`' % (
1807 userid, repoid
1811 userid, repoid
1808 )
1812 )
1809 )
1813 )
1810
1814
1811
1815
1812 @jsonrpc_method()
1816 @jsonrpc_method()
1813 def revoke_user_permission(request, apiuser, repoid, userid):
1817 def revoke_user_permission(request, apiuser, repoid, userid):
1814 """
1818 """
1815 Revoke permission for a user on the specified repository.
1819 Revoke permission for a user on the specified repository.
1816
1820
1817 This command can only be run using an |authtoken| with admin
1821 This command can only be run using an |authtoken| with admin
1818 permissions on the |repo|.
1822 permissions on the |repo|.
1819
1823
1820 :param apiuser: This is filled automatically from the |authtoken|.
1824 :param apiuser: This is filled automatically from the |authtoken|.
1821 :type apiuser: AuthUser
1825 :type apiuser: AuthUser
1822 :param repoid: Set the repository name or repository ID.
1826 :param repoid: Set the repository name or repository ID.
1823 :type repoid: str or int
1827 :type repoid: str or int
1824 :param userid: Set the user name of revoked user.
1828 :param userid: Set the user name of revoked user.
1825 :type userid: str or int
1829 :type userid: str or int
1826
1830
1827 Example error output:
1831 Example error output:
1828
1832
1829 .. code-block:: bash
1833 .. code-block:: bash
1830
1834
1831 id : <id_given_in_input>
1835 id : <id_given_in_input>
1832 result: {
1836 result: {
1833 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1837 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1834 "success": true
1838 "success": true
1835 }
1839 }
1836 error: null
1840 error: null
1837 """
1841 """
1838
1842
1839 repo = get_repo_or_error(repoid)
1843 repo = get_repo_or_error(repoid)
1840 user = get_user_or_error(userid)
1844 user = get_user_or_error(userid)
1841 if not has_superadmin_permission(apiuser):
1845 if not has_superadmin_permission(apiuser):
1842 _perms = ('repository.admin',)
1846 _perms = ('repository.admin',)
1843 validate_repo_permissions(apiuser, repoid, repo, _perms)
1847 validate_repo_permissions(apiuser, repoid, repo, _perms)
1844
1848
1845 perm_deletions = [[user.user_id, None, "user"]]
1849 perm_deletions = [[user.user_id, None, "user"]]
1846 try:
1850 try:
1847 changes = RepoModel().update_permissions(
1851 changes = RepoModel().update_permissions(
1848 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1852 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1849
1853
1850 action_data = {
1854 action_data = {
1851 'added': changes['added'],
1855 'added': changes['added'],
1852 'updated': changes['updated'],
1856 'updated': changes['updated'],
1853 'deleted': changes['deleted'],
1857 'deleted': changes['deleted'],
1854 }
1858 }
1855 audit_logger.store_api(
1859 audit_logger.store_api(
1856 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1860 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1857 Session().commit()
1861 Session().commit()
1858 PermissionModel().flush_user_permission_caches(changes)
1862 PermissionModel().flush_user_permission_caches(changes)
1859
1863
1860 return {
1864 return {
1861 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1865 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1862 user.username, repo.repo_name
1866 user.username, repo.repo_name
1863 ),
1867 ),
1864 'success': True
1868 'success': True
1865 }
1869 }
1866 except Exception:
1870 except Exception:
1867 log.exception("Exception occurred while trying revoke permissions to repo")
1871 log.exception("Exception occurred while trying revoke permissions to repo")
1868 raise JSONRPCError(
1872 raise JSONRPCError(
1869 'failed to edit permission for user: `%s` in repo: `%s`' % (
1873 'failed to edit permission for user: `%s` in repo: `%s`' % (
1870 userid, repoid
1874 userid, repoid
1871 )
1875 )
1872 )
1876 )
1873
1877
1874
1878
1875 @jsonrpc_method()
1879 @jsonrpc_method()
1876 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1880 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1877 """
1881 """
1878 Grant permission for a user group on the specified repository,
1882 Grant permission for a user group on the specified repository,
1879 or update existing permissions.
1883 or update existing permissions.
1880
1884
1881 This command can only be run using an |authtoken| with admin
1885 This command can only be run using an |authtoken| with admin
1882 permissions on the |repo|.
1886 permissions on the |repo|.
1883
1887
1884 :param apiuser: This is filled automatically from the |authtoken|.
1888 :param apiuser: This is filled automatically from the |authtoken|.
1885 :type apiuser: AuthUser
1889 :type apiuser: AuthUser
1886 :param repoid: Set the repository name or repository ID.
1890 :param repoid: Set the repository name or repository ID.
1887 :type repoid: str or int
1891 :type repoid: str or int
1888 :param usergroupid: Specify the ID of the user group.
1892 :param usergroupid: Specify the ID of the user group.
1889 :type usergroupid: str or int
1893 :type usergroupid: str or int
1890 :param perm: Set the user group permissions using the following
1894 :param perm: Set the user group permissions using the following
1891 format: (repository.(none|read|write|admin))
1895 format: (repository.(none|read|write|admin))
1892 :type perm: str
1896 :type perm: str
1893
1897
1894 Example output:
1898 Example output:
1895
1899
1896 .. code-block:: bash
1900 .. code-block:: bash
1897
1901
1898 id : <id_given_in_input>
1902 id : <id_given_in_input>
1899 result : {
1903 result : {
1900 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1904 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1901 "success": true
1905 "success": true
1902
1906
1903 }
1907 }
1904 error : null
1908 error : null
1905
1909
1906 Example error output:
1910 Example error output:
1907
1911
1908 .. code-block:: bash
1912 .. code-block:: bash
1909
1913
1910 id : <id_given_in_input>
1914 id : <id_given_in_input>
1911 result : null
1915 result : null
1912 error : {
1916 error : {
1913 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1917 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1914 }
1918 }
1915
1919
1916 """
1920 """
1917
1921
1918 repo = get_repo_or_error(repoid)
1922 repo = get_repo_or_error(repoid)
1919 perm = get_perm_or_error(perm)
1923 perm = get_perm_or_error(perm)
1920 if not has_superadmin_permission(apiuser):
1924 if not has_superadmin_permission(apiuser):
1921 _perms = ('repository.admin',)
1925 _perms = ('repository.admin',)
1922 validate_repo_permissions(apiuser, repoid, repo, _perms)
1926 validate_repo_permissions(apiuser, repoid, repo, _perms)
1923
1927
1924 user_group = get_user_group_or_error(usergroupid)
1928 user_group = get_user_group_or_error(usergroupid)
1925 if not has_superadmin_permission(apiuser):
1929 if not has_superadmin_permission(apiuser):
1926 # check if we have at least read permission for this user group !
1930 # check if we have at least read permission for this user group !
1927 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1931 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1928 if not HasUserGroupPermissionAnyApi(*_perms)(
1932 if not HasUserGroupPermissionAnyApi(*_perms)(
1929 user=apiuser, user_group_name=user_group.users_group_name):
1933 user=apiuser, user_group_name=user_group.users_group_name):
1930 raise JSONRPCError(
1934 raise JSONRPCError(
1931 'user group `%s` does not exist' % (usergroupid,))
1935 'user group `%s` does not exist' % (usergroupid,))
1932
1936
1933 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1937 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1934 try:
1938 try:
1935 changes = RepoModel().update_permissions(
1939 changes = RepoModel().update_permissions(
1936 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1940 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1937 action_data = {
1941 action_data = {
1938 'added': changes['added'],
1942 'added': changes['added'],
1939 'updated': changes['updated'],
1943 'updated': changes['updated'],
1940 'deleted': changes['deleted'],
1944 'deleted': changes['deleted'],
1941 }
1945 }
1942 audit_logger.store_api(
1946 audit_logger.store_api(
1943 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1947 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1944 Session().commit()
1948 Session().commit()
1945 PermissionModel().flush_user_permission_caches(changes)
1949 PermissionModel().flush_user_permission_caches(changes)
1946
1950
1947 return {
1951 return {
1948 'msg': 'Granted perm: `%s` for user group: `%s` in '
1952 'msg': 'Granted perm: `%s` for user group: `%s` in '
1949 'repo: `%s`' % (
1953 'repo: `%s`' % (
1950 perm.permission_name, user_group.users_group_name,
1954 perm.permission_name, user_group.users_group_name,
1951 repo.repo_name
1955 repo.repo_name
1952 ),
1956 ),
1953 'success': True
1957 'success': True
1954 }
1958 }
1955 except Exception:
1959 except Exception:
1956 log.exception(
1960 log.exception(
1957 "Exception occurred while trying change permission on repo")
1961 "Exception occurred while trying change permission on repo")
1958 raise JSONRPCError(
1962 raise JSONRPCError(
1959 'failed to edit permission for user group: `%s` in '
1963 'failed to edit permission for user group: `%s` in '
1960 'repo: `%s`' % (
1964 'repo: `%s`' % (
1961 usergroupid, repo.repo_name
1965 usergroupid, repo.repo_name
1962 )
1966 )
1963 )
1967 )
1964
1968
1965
1969
1966 @jsonrpc_method()
1970 @jsonrpc_method()
1967 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1971 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1968 """
1972 """
1969 Revoke the permissions of a user group on a given repository.
1973 Revoke the permissions of a user group on a given repository.
1970
1974
1971 This command can only be run using an |authtoken| with admin
1975 This command can only be run using an |authtoken| with admin
1972 permissions on the |repo|.
1976 permissions on the |repo|.
1973
1977
1974 :param apiuser: This is filled automatically from the |authtoken|.
1978 :param apiuser: This is filled automatically from the |authtoken|.
1975 :type apiuser: AuthUser
1979 :type apiuser: AuthUser
1976 :param repoid: Set the repository name or repository ID.
1980 :param repoid: Set the repository name or repository ID.
1977 :type repoid: str or int
1981 :type repoid: str or int
1978 :param usergroupid: Specify the user group ID.
1982 :param usergroupid: Specify the user group ID.
1979 :type usergroupid: str or int
1983 :type usergroupid: str or int
1980
1984
1981 Example output:
1985 Example output:
1982
1986
1983 .. code-block:: bash
1987 .. code-block:: bash
1984
1988
1985 id : <id_given_in_input>
1989 id : <id_given_in_input>
1986 result: {
1990 result: {
1987 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1991 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1988 "success": true
1992 "success": true
1989 }
1993 }
1990 error: null
1994 error: null
1991 """
1995 """
1992
1996
1993 repo = get_repo_or_error(repoid)
1997 repo = get_repo_or_error(repoid)
1994 if not has_superadmin_permission(apiuser):
1998 if not has_superadmin_permission(apiuser):
1995 _perms = ('repository.admin',)
1999 _perms = ('repository.admin',)
1996 validate_repo_permissions(apiuser, repoid, repo, _perms)
2000 validate_repo_permissions(apiuser, repoid, repo, _perms)
1997
2001
1998 user_group = get_user_group_or_error(usergroupid)
2002 user_group = get_user_group_or_error(usergroupid)
1999 if not has_superadmin_permission(apiuser):
2003 if not has_superadmin_permission(apiuser):
2000 # check if we have at least read permission for this user group !
2004 # check if we have at least read permission for this user group !
2001 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2005 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2002 if not HasUserGroupPermissionAnyApi(*_perms)(
2006 if not HasUserGroupPermissionAnyApi(*_perms)(
2003 user=apiuser, user_group_name=user_group.users_group_name):
2007 user=apiuser, user_group_name=user_group.users_group_name):
2004 raise JSONRPCError(
2008 raise JSONRPCError(
2005 'user group `%s` does not exist' % (usergroupid,))
2009 'user group `%s` does not exist' % (usergroupid,))
2006
2010
2007 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2011 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2008 try:
2012 try:
2009 changes = RepoModel().update_permissions(
2013 changes = RepoModel().update_permissions(
2010 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2014 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2011 action_data = {
2015 action_data = {
2012 'added': changes['added'],
2016 'added': changes['added'],
2013 'updated': changes['updated'],
2017 'updated': changes['updated'],
2014 'deleted': changes['deleted'],
2018 'deleted': changes['deleted'],
2015 }
2019 }
2016 audit_logger.store_api(
2020 audit_logger.store_api(
2017 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2021 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2018 Session().commit()
2022 Session().commit()
2019 PermissionModel().flush_user_permission_caches(changes)
2023 PermissionModel().flush_user_permission_caches(changes)
2020
2024
2021 return {
2025 return {
2022 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2026 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2023 user_group.users_group_name, repo.repo_name
2027 user_group.users_group_name, repo.repo_name
2024 ),
2028 ),
2025 'success': True
2029 'success': True
2026 }
2030 }
2027 except Exception:
2031 except Exception:
2028 log.exception("Exception occurred while trying revoke "
2032 log.exception("Exception occurred while trying revoke "
2029 "user group permission on repo")
2033 "user group permission on repo")
2030 raise JSONRPCError(
2034 raise JSONRPCError(
2031 'failed to edit permission for user group: `%s` in '
2035 'failed to edit permission for user group: `%s` in '
2032 'repo: `%s`' % (
2036 'repo: `%s`' % (
2033 user_group.users_group_name, repo.repo_name
2037 user_group.users_group_name, repo.repo_name
2034 )
2038 )
2035 )
2039 )
2036
2040
2037
2041
2038 @jsonrpc_method()
2042 @jsonrpc_method()
2039 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2043 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2040 """
2044 """
2041 Triggers a pull on the given repository from a remote location. You
2045 Triggers a pull on the given repository from a remote location. You
2042 can use this to keep remote repositories up-to-date.
2046 can use this to keep remote repositories up-to-date.
2043
2047
2044 This command can only be run using an |authtoken| with admin
2048 This command can only be run using an |authtoken| with admin
2045 rights to the specified repository. For more information,
2049 rights to the specified repository. For more information,
2046 see :ref:`config-token-ref`.
2050 see :ref:`config-token-ref`.
2047
2051
2048 This command takes the following options:
2052 This command takes the following options:
2049
2053
2050 :param apiuser: This is filled automatically from the |authtoken|.
2054 :param apiuser: This is filled automatically from the |authtoken|.
2051 :type apiuser: AuthUser
2055 :type apiuser: AuthUser
2052 :param repoid: The repository name or repository ID.
2056 :param repoid: The repository name or repository ID.
2053 :type repoid: str or int
2057 :type repoid: str or int
2054 :param remote_uri: Optional remote URI to pass in for pull
2058 :param remote_uri: Optional remote URI to pass in for pull
2055 :type remote_uri: str
2059 :type remote_uri: str
2056
2060
2057 Example output:
2061 Example output:
2058
2062
2059 .. code-block:: bash
2063 .. code-block:: bash
2060
2064
2061 id : <id_given_in_input>
2065 id : <id_given_in_input>
2062 result : {
2066 result : {
2063 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2067 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2064 "repository": "<repository name>"
2068 "repository": "<repository name>"
2065 }
2069 }
2066 error : null
2070 error : null
2067
2071
2068 Example error output:
2072 Example error output:
2069
2073
2070 .. code-block:: bash
2074 .. code-block:: bash
2071
2075
2072 id : <id_given_in_input>
2076 id : <id_given_in_input>
2073 result : null
2077 result : null
2074 error : {
2078 error : {
2075 "Unable to push changes from `<remote_url>`"
2079 "Unable to push changes from `<remote_url>`"
2076 }
2080 }
2077
2081
2078 """
2082 """
2079
2083
2080 repo = get_repo_or_error(repoid)
2084 repo = get_repo_or_error(repoid)
2081 remote_uri = Optional.extract(remote_uri)
2085 remote_uri = Optional.extract(remote_uri)
2082 remote_uri_display = remote_uri or repo.clone_uri_hidden
2086 remote_uri_display = remote_uri or repo.clone_uri_hidden
2083 if not has_superadmin_permission(apiuser):
2087 if not has_superadmin_permission(apiuser):
2084 _perms = ('repository.admin',)
2088 _perms = ('repository.admin',)
2085 validate_repo_permissions(apiuser, repoid, repo, _perms)
2089 validate_repo_permissions(apiuser, repoid, repo, _perms)
2086
2090
2087 try:
2091 try:
2088 ScmModel().pull_changes(
2092 ScmModel().pull_changes(
2089 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2093 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2090 return {
2094 return {
2091 'msg': 'Pulled from url `%s` on repo `%s`' % (
2095 'msg': 'Pulled from url `%s` on repo `%s`' % (
2092 remote_uri_display, repo.repo_name),
2096 remote_uri_display, repo.repo_name),
2093 'repository': repo.repo_name
2097 'repository': repo.repo_name
2094 }
2098 }
2095 except Exception:
2099 except Exception:
2096 log.exception("Exception occurred while trying to "
2100 log.exception("Exception occurred while trying to "
2097 "pull changes from remote location")
2101 "pull changes from remote location")
2098 raise JSONRPCError(
2102 raise JSONRPCError(
2099 'Unable to pull changes from `%s`' % remote_uri_display
2103 'Unable to pull changes from `%s`' % remote_uri_display
2100 )
2104 )
2101
2105
2102
2106
2103 @jsonrpc_method()
2107 @jsonrpc_method()
2104 def strip(request, apiuser, repoid, revision, branch):
2108 def strip(request, apiuser, repoid, revision, branch):
2105 """
2109 """
2106 Strips the given revision from the specified repository.
2110 Strips the given revision from the specified repository.
2107
2111
2108 * This will remove the revision and all of its decendants.
2112 * This will remove the revision and all of its decendants.
2109
2113
2110 This command can only be run using an |authtoken| with admin rights to
2114 This command can only be run using an |authtoken| with admin rights to
2111 the specified repository.
2115 the specified repository.
2112
2116
2113 This command takes the following options:
2117 This command takes the following options:
2114
2118
2115 :param apiuser: This is filled automatically from the |authtoken|.
2119 :param apiuser: This is filled automatically from the |authtoken|.
2116 :type apiuser: AuthUser
2120 :type apiuser: AuthUser
2117 :param repoid: The repository name or repository ID.
2121 :param repoid: The repository name or repository ID.
2118 :type repoid: str or int
2122 :type repoid: str or int
2119 :param revision: The revision you wish to strip.
2123 :param revision: The revision you wish to strip.
2120 :type revision: str
2124 :type revision: str
2121 :param branch: The branch from which to strip the revision.
2125 :param branch: The branch from which to strip the revision.
2122 :type branch: str
2126 :type branch: str
2123
2127
2124 Example output:
2128 Example output:
2125
2129
2126 .. code-block:: bash
2130 .. code-block:: bash
2127
2131
2128 id : <id_given_in_input>
2132 id : <id_given_in_input>
2129 result : {
2133 result : {
2130 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2134 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2131 "repository": "<repository name>"
2135 "repository": "<repository name>"
2132 }
2136 }
2133 error : null
2137 error : null
2134
2138
2135 Example error output:
2139 Example error output:
2136
2140
2137 .. code-block:: bash
2141 .. code-block:: bash
2138
2142
2139 id : <id_given_in_input>
2143 id : <id_given_in_input>
2140 result : null
2144 result : null
2141 error : {
2145 error : {
2142 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2146 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2143 }
2147 }
2144
2148
2145 """
2149 """
2146
2150
2147 repo = get_repo_or_error(repoid)
2151 repo = get_repo_or_error(repoid)
2148 if not has_superadmin_permission(apiuser):
2152 if not has_superadmin_permission(apiuser):
2149 _perms = ('repository.admin',)
2153 _perms = ('repository.admin',)
2150 validate_repo_permissions(apiuser, repoid, repo, _perms)
2154 validate_repo_permissions(apiuser, repoid, repo, _perms)
2151
2155
2152 try:
2156 try:
2153 ScmModel().strip(repo, revision, branch)
2157 ScmModel().strip(repo, revision, branch)
2154 audit_logger.store_api(
2158 audit_logger.store_api(
2155 'repo.commit.strip', action_data={'commit_id': revision},
2159 'repo.commit.strip', action_data={'commit_id': revision},
2156 repo=repo,
2160 repo=repo,
2157 user=apiuser, commit=True)
2161 user=apiuser, commit=True)
2158
2162
2159 return {
2163 return {
2160 'msg': 'Stripped commit %s from repo `%s`' % (
2164 'msg': 'Stripped commit %s from repo `%s`' % (
2161 revision, repo.repo_name),
2165 revision, repo.repo_name),
2162 'repository': repo.repo_name
2166 'repository': repo.repo_name
2163 }
2167 }
2164 except Exception:
2168 except Exception:
2165 log.exception("Exception while trying to strip")
2169 log.exception("Exception while trying to strip")
2166 raise JSONRPCError(
2170 raise JSONRPCError(
2167 'Unable to strip commit %s from repo `%s`' % (
2171 'Unable to strip commit %s from repo `%s`' % (
2168 revision, repo.repo_name)
2172 revision, repo.repo_name)
2169 )
2173 )
2170
2174
2171
2175
2172 @jsonrpc_method()
2176 @jsonrpc_method()
2173 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2177 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2174 """
2178 """
2175 Returns all settings for a repository. If key is given it only returns the
2179 Returns all settings for a repository. If key is given it only returns the
2176 setting identified by the key or null.
2180 setting identified by the key or null.
2177
2181
2178 :param apiuser: This is filled automatically from the |authtoken|.
2182 :param apiuser: This is filled automatically from the |authtoken|.
2179 :type apiuser: AuthUser
2183 :type apiuser: AuthUser
2180 :param repoid: The repository name or repository id.
2184 :param repoid: The repository name or repository id.
2181 :type repoid: str or int
2185 :type repoid: str or int
2182 :param key: Key of the setting to return.
2186 :param key: Key of the setting to return.
2183 :type: key: Optional(str)
2187 :type: key: Optional(str)
2184
2188
2185 Example output:
2189 Example output:
2186
2190
2187 .. code-block:: bash
2191 .. code-block:: bash
2188
2192
2189 {
2193 {
2190 "error": null,
2194 "error": null,
2191 "id": 237,
2195 "id": 237,
2192 "result": {
2196 "result": {
2193 "extensions_largefiles": true,
2197 "extensions_largefiles": true,
2194 "extensions_evolve": true,
2198 "extensions_evolve": true,
2195 "hooks_changegroup_push_logger": true,
2199 "hooks_changegroup_push_logger": true,
2196 "hooks_changegroup_repo_size": false,
2200 "hooks_changegroup_repo_size": false,
2197 "hooks_outgoing_pull_logger": true,
2201 "hooks_outgoing_pull_logger": true,
2198 "phases_publish": "True",
2202 "phases_publish": "True",
2199 "rhodecode_hg_use_rebase_for_merging": true,
2203 "rhodecode_hg_use_rebase_for_merging": true,
2200 "rhodecode_pr_merge_enabled": true,
2204 "rhodecode_pr_merge_enabled": true,
2201 "rhodecode_use_outdated_comments": true
2205 "rhodecode_use_outdated_comments": true
2202 }
2206 }
2203 }
2207 }
2204 """
2208 """
2205
2209
2206 # Restrict access to this api method to admins only.
2210 # Restrict access to this api method to admins only.
2207 if not has_superadmin_permission(apiuser):
2211 if not has_superadmin_permission(apiuser):
2208 raise JSONRPCForbidden()
2212 raise JSONRPCForbidden()
2209
2213
2210 try:
2214 try:
2211 repo = get_repo_or_error(repoid)
2215 repo = get_repo_or_error(repoid)
2212 settings_model = VcsSettingsModel(repo=repo)
2216 settings_model = VcsSettingsModel(repo=repo)
2213 settings = settings_model.get_global_settings()
2217 settings = settings_model.get_global_settings()
2214 settings.update(settings_model.get_repo_settings())
2218 settings.update(settings_model.get_repo_settings())
2215
2219
2216 # If only a single setting is requested fetch it from all settings.
2220 # If only a single setting is requested fetch it from all settings.
2217 key = Optional.extract(key)
2221 key = Optional.extract(key)
2218 if key is not None:
2222 if key is not None:
2219 settings = settings.get(key, None)
2223 settings = settings.get(key, None)
2220 except Exception:
2224 except Exception:
2221 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2225 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2222 log.exception(msg)
2226 log.exception(msg)
2223 raise JSONRPCError(msg)
2227 raise JSONRPCError(msg)
2224
2228
2225 return settings
2229 return settings
2226
2230
2227
2231
2228 @jsonrpc_method()
2232 @jsonrpc_method()
2229 def set_repo_settings(request, apiuser, repoid, settings):
2233 def set_repo_settings(request, apiuser, repoid, settings):
2230 """
2234 """
2231 Update repository settings. Returns true on success.
2235 Update repository settings. Returns true on success.
2232
2236
2233 :param apiuser: This is filled automatically from the |authtoken|.
2237 :param apiuser: This is filled automatically from the |authtoken|.
2234 :type apiuser: AuthUser
2238 :type apiuser: AuthUser
2235 :param repoid: The repository name or repository id.
2239 :param repoid: The repository name or repository id.
2236 :type repoid: str or int
2240 :type repoid: str or int
2237 :param settings: The new settings for the repository.
2241 :param settings: The new settings for the repository.
2238 :type: settings: dict
2242 :type: settings: dict
2239
2243
2240 Example output:
2244 Example output:
2241
2245
2242 .. code-block:: bash
2246 .. code-block:: bash
2243
2247
2244 {
2248 {
2245 "error": null,
2249 "error": null,
2246 "id": 237,
2250 "id": 237,
2247 "result": true
2251 "result": true
2248 }
2252 }
2249 """
2253 """
2250 # Restrict access to this api method to admins only.
2254 # Restrict access to this api method to admins only.
2251 if not has_superadmin_permission(apiuser):
2255 if not has_superadmin_permission(apiuser):
2252 raise JSONRPCForbidden()
2256 raise JSONRPCForbidden()
2253
2257
2254 if type(settings) is not dict:
2258 if type(settings) is not dict:
2255 raise JSONRPCError('Settings have to be a JSON Object.')
2259 raise JSONRPCError('Settings have to be a JSON Object.')
2256
2260
2257 try:
2261 try:
2258 settings_model = VcsSettingsModel(repo=repoid)
2262 settings_model = VcsSettingsModel(repo=repoid)
2259
2263
2260 # Merge global, repo and incoming settings.
2264 # Merge global, repo and incoming settings.
2261 new_settings = settings_model.get_global_settings()
2265 new_settings = settings_model.get_global_settings()
2262 new_settings.update(settings_model.get_repo_settings())
2266 new_settings.update(settings_model.get_repo_settings())
2263 new_settings.update(settings)
2267 new_settings.update(settings)
2264
2268
2265 # Update the settings.
2269 # Update the settings.
2266 inherit_global_settings = new_settings.get(
2270 inherit_global_settings = new_settings.get(
2267 'inherit_global_settings', False)
2271 'inherit_global_settings', False)
2268 settings_model.create_or_update_repo_settings(
2272 settings_model.create_or_update_repo_settings(
2269 new_settings, inherit_global_settings=inherit_global_settings)
2273 new_settings, inherit_global_settings=inherit_global_settings)
2270 Session().commit()
2274 Session().commit()
2271 except Exception:
2275 except Exception:
2272 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2276 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2273 log.exception(msg)
2277 log.exception(msg)
2274 raise JSONRPCError(msg)
2278 raise JSONRPCError(msg)
2275
2279
2276 # Indicate success.
2280 # Indicate success.
2277 return True
2281 return True
2278
2282
2279
2283
2280 @jsonrpc_method()
2284 @jsonrpc_method()
2281 def maintenance(request, apiuser, repoid):
2285 def maintenance(request, apiuser, repoid):
2282 """
2286 """
2283 Triggers a maintenance on the given repository.
2287 Triggers a maintenance on the given repository.
2284
2288
2285 This command can only be run using an |authtoken| with admin
2289 This command can only be run using an |authtoken| with admin
2286 rights to the specified repository. For more information,
2290 rights to the specified repository. For more information,
2287 see :ref:`config-token-ref`.
2291 see :ref:`config-token-ref`.
2288
2292
2289 This command takes the following options:
2293 This command takes the following options:
2290
2294
2291 :param apiuser: This is filled automatically from the |authtoken|.
2295 :param apiuser: This is filled automatically from the |authtoken|.
2292 :type apiuser: AuthUser
2296 :type apiuser: AuthUser
2293 :param repoid: The repository name or repository ID.
2297 :param repoid: The repository name or repository ID.
2294 :type repoid: str or int
2298 :type repoid: str or int
2295
2299
2296 Example output:
2300 Example output:
2297
2301
2298 .. code-block:: bash
2302 .. code-block:: bash
2299
2303
2300 id : <id_given_in_input>
2304 id : <id_given_in_input>
2301 result : {
2305 result : {
2302 "msg": "executed maintenance command",
2306 "msg": "executed maintenance command",
2303 "executed_actions": [
2307 "executed_actions": [
2304 <action_message>, <action_message2>...
2308 <action_message>, <action_message2>...
2305 ],
2309 ],
2306 "repository": "<repository name>"
2310 "repository": "<repository name>"
2307 }
2311 }
2308 error : null
2312 error : null
2309
2313
2310 Example error output:
2314 Example error output:
2311
2315
2312 .. code-block:: bash
2316 .. code-block:: bash
2313
2317
2314 id : <id_given_in_input>
2318 id : <id_given_in_input>
2315 result : null
2319 result : null
2316 error : {
2320 error : {
2317 "Unable to execute maintenance on `<reponame>`"
2321 "Unable to execute maintenance on `<reponame>`"
2318 }
2322 }
2319
2323
2320 """
2324 """
2321
2325
2322 repo = get_repo_or_error(repoid)
2326 repo = get_repo_or_error(repoid)
2323 if not has_superadmin_permission(apiuser):
2327 if not has_superadmin_permission(apiuser):
2324 _perms = ('repository.admin',)
2328 _perms = ('repository.admin',)
2325 validate_repo_permissions(apiuser, repoid, repo, _perms)
2329 validate_repo_permissions(apiuser, repoid, repo, _perms)
2326
2330
2327 try:
2331 try:
2328 maintenance = repo_maintenance.RepoMaintenance()
2332 maintenance = repo_maintenance.RepoMaintenance()
2329 executed_actions = maintenance.execute(repo)
2333 executed_actions = maintenance.execute(repo)
2330
2334
2331 return {
2335 return {
2332 'msg': 'executed maintenance command',
2336 'msg': 'executed maintenance command',
2333 'executed_actions': executed_actions,
2337 'executed_actions': executed_actions,
2334 'repository': repo.repo_name
2338 'repository': repo.repo_name
2335 }
2339 }
2336 except Exception:
2340 except Exception:
2337 log.exception("Exception occurred while trying to run maintenance")
2341 log.exception("Exception occurred while trying to run maintenance")
2338 raise JSONRPCError(
2342 raise JSONRPCError(
2339 'Unable to execute maintenance on `%s`' % repo.repo_name)
2343 'Unable to execute maintenance on `%s`' % repo.repo_name)
General Comments 0
You need to be logged in to leave comments. Login now