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