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