##// END OF EJS Templates
exceptions: use python3 compatible exception handling
marcink -
r3104:4de59e0f default
parent child Browse files
Show More
@@ -1,39 +1,42 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 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 class JSONRPCBaseError(Exception):
23 pass
23 def __init__(self, message='', *args):
24 self.message = message
25 super(JSONRPCBaseError, self).__init__(message, *args)
24 26
25 27
26 28 class JSONRPCError(JSONRPCBaseError):
27 29 pass
28 30
29 31
30 32 class JSONRPCValidationError(JSONRPCBaseError):
31 33
32 34 def __init__(self, *args, **kwargs):
33 35 self.colander_exception = kwargs.pop('colander_exc')
34 super(JSONRPCValidationError, self).__init__(*args, **kwargs)
36 super(JSONRPCValidationError, self).__init__(
37 message=self.colander_exception, *args)
35 38
36 39
37 40 class JSONRPCForbidden(JSONRPCBaseError):
38 41 pass
39 42
@@ -1,2065 +1,2065 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 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
33 33 from rhodecode.lib import repo_maintenance
34 34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 35 from rhodecode.lib.celerylib.utils import get_task_id
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str
37 37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
39 39 from rhodecode.model.changeset_status import ChangesetStatusModel
40 40 from rhodecode.model.comment import CommentsModel
41 41 from rhodecode.model.db import (
42 42 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
43 43 ChangesetComment)
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.scm import ScmModel, RepoList
46 46 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
47 47 from rhodecode.model import validation_schema
48 48 from rhodecode.model.validation_schema.schemas import repo_schema
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 @jsonrpc_method()
54 54 def get_repo(request, apiuser, repoid, cache=Optional(True)):
55 55 """
56 56 Gets an existing repository by its name or repository_id.
57 57
58 58 The members section so the output returns users groups or users
59 59 associated with that repository.
60 60
61 61 This command can only be run using an |authtoken| with admin rights,
62 62 or users with at least read rights to the |repo|.
63 63
64 64 :param apiuser: This is filled automatically from the |authtoken|.
65 65 :type apiuser: AuthUser
66 66 :param repoid: The repository name or repository id.
67 67 :type repoid: str or int
68 68 :param cache: use the cached value for last changeset
69 69 :type: cache: Optional(bool)
70 70
71 71 Example output:
72 72
73 73 .. code-block:: bash
74 74
75 75 {
76 76 "error": null,
77 77 "id": <repo_id>,
78 78 "result": {
79 79 "clone_uri": null,
80 80 "created_on": "timestamp",
81 81 "description": "repo description",
82 82 "enable_downloads": false,
83 83 "enable_locking": false,
84 84 "enable_statistics": false,
85 85 "followers": [
86 86 {
87 87 "active": true,
88 88 "admin": false,
89 89 "api_key": "****************************************",
90 90 "api_keys": [
91 91 "****************************************"
92 92 ],
93 93 "email": "user@example.com",
94 94 "emails": [
95 95 "user@example.com"
96 96 ],
97 97 "extern_name": "rhodecode",
98 98 "extern_type": "rhodecode",
99 99 "firstname": "username",
100 100 "ip_addresses": [],
101 101 "language": null,
102 102 "last_login": "2015-09-16T17:16:35.854",
103 103 "lastname": "surname",
104 104 "user_id": <user_id>,
105 105 "username": "name"
106 106 }
107 107 ],
108 108 "fork_of": "parent-repo",
109 109 "landing_rev": [
110 110 "rev",
111 111 "tip"
112 112 ],
113 113 "last_changeset": {
114 114 "author": "User <user@example.com>",
115 115 "branch": "default",
116 116 "date": "timestamp",
117 117 "message": "last commit message",
118 118 "parents": [
119 119 {
120 120 "raw_id": "commit-id"
121 121 }
122 122 ],
123 123 "raw_id": "commit-id",
124 124 "revision": <revision number>,
125 125 "short_id": "short id"
126 126 },
127 127 "lock_reason": null,
128 128 "locked_by": null,
129 129 "locked_date": null,
130 130 "owner": "owner-name",
131 131 "permissions": [
132 132 {
133 133 "name": "super-admin-name",
134 134 "origin": "super-admin",
135 135 "permission": "repository.admin",
136 136 "type": "user"
137 137 },
138 138 {
139 139 "name": "owner-name",
140 140 "origin": "owner",
141 141 "permission": "repository.admin",
142 142 "type": "user"
143 143 },
144 144 {
145 145 "name": "user-group-name",
146 146 "origin": "permission",
147 147 "permission": "repository.write",
148 148 "type": "user_group"
149 149 }
150 150 ],
151 151 "private": true,
152 152 "repo_id": 676,
153 153 "repo_name": "user-group/repo-name",
154 154 "repo_type": "hg"
155 155 }
156 156 }
157 157 """
158 158
159 159 repo = get_repo_or_error(repoid)
160 160 cache = Optional.extract(cache)
161 161
162 162 include_secrets = False
163 163 if has_superadmin_permission(apiuser):
164 164 include_secrets = True
165 165 else:
166 166 # check if we have at least read permission for this repo !
167 167 _perms = (
168 168 'repository.admin', 'repository.write', 'repository.read',)
169 169 validate_repo_permissions(apiuser, repoid, repo, _perms)
170 170
171 171 permissions = []
172 172 for _user in repo.permissions():
173 173 user_data = {
174 174 'name': _user.username,
175 175 'permission': _user.permission,
176 176 'origin': get_origin(_user),
177 177 'type': "user",
178 178 }
179 179 permissions.append(user_data)
180 180
181 181 for _user_group in repo.permission_user_groups():
182 182 user_group_data = {
183 183 'name': _user_group.users_group_name,
184 184 'permission': _user_group.permission,
185 185 'origin': get_origin(_user_group),
186 186 'type': "user_group",
187 187 }
188 188 permissions.append(user_group_data)
189 189
190 190 following_users = [
191 191 user.user.get_api_data(include_secrets=include_secrets)
192 192 for user in repo.followers]
193 193
194 194 if not cache:
195 195 repo.update_commit_cache()
196 196 data = repo.get_api_data(include_secrets=include_secrets)
197 197 data['permissions'] = permissions
198 198 data['followers'] = following_users
199 199 return data
200 200
201 201
202 202 @jsonrpc_method()
203 203 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
204 204 """
205 205 Lists all existing repositories.
206 206
207 207 This command can only be run using an |authtoken| with admin rights,
208 208 or users with at least read rights to |repos|.
209 209
210 210 :param apiuser: This is filled automatically from the |authtoken|.
211 211 :type apiuser: AuthUser
212 212 :param root: specify root repository group to fetch repositories.
213 213 filters the returned repositories to be members of given root group.
214 214 :type root: Optional(None)
215 215 :param traverse: traverse given root into subrepositories. With this flag
216 216 set to False, it will only return top-level repositories from `root`.
217 217 if root is empty it will return just top-level repositories.
218 218 :type traverse: Optional(True)
219 219
220 220
221 221 Example output:
222 222
223 223 .. code-block:: bash
224 224
225 225 id : <id_given_in_input>
226 226 result: [
227 227 {
228 228 "repo_id" : "<repo_id>",
229 229 "repo_name" : "<reponame>"
230 230 "repo_type" : "<repo_type>",
231 231 "clone_uri" : "<clone_uri>",
232 232 "private": : "<bool>",
233 233 "created_on" : "<datetimecreated>",
234 234 "description" : "<description>",
235 235 "landing_rev": "<landing_rev>",
236 236 "owner": "<repo_owner>",
237 237 "fork_of": "<name_of_fork_parent>",
238 238 "enable_downloads": "<bool>",
239 239 "enable_locking": "<bool>",
240 240 "enable_statistics": "<bool>",
241 241 },
242 242 ...
243 243 ]
244 244 error: null
245 245 """
246 246
247 247 include_secrets = has_superadmin_permission(apiuser)
248 248 _perms = ('repository.read', 'repository.write', 'repository.admin',)
249 249 extras = {'user': apiuser}
250 250
251 251 root = Optional.extract(root)
252 252 traverse = Optional.extract(traverse, binary=True)
253 253
254 254 if root:
255 255 # verify parent existance, if it's empty return an error
256 256 parent = RepoGroup.get_by_group_name(root)
257 257 if not parent:
258 258 raise JSONRPCError(
259 259 'Root repository group `{}` does not exist'.format(root))
260 260
261 261 if traverse:
262 262 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
263 263 else:
264 264 repos = RepoModel().get_repos_for_root(root=parent)
265 265 else:
266 266 if traverse:
267 267 repos = RepoModel().get_all()
268 268 else:
269 269 # return just top-level
270 270 repos = RepoModel().get_repos_for_root(root=None)
271 271
272 272 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
273 273 return [repo.get_api_data(include_secrets=include_secrets)
274 274 for repo in repo_list]
275 275
276 276
277 277 @jsonrpc_method()
278 278 def get_repo_changeset(request, apiuser, repoid, revision,
279 279 details=Optional('basic')):
280 280 """
281 281 Returns information about a changeset.
282 282
283 283 Additionally parameters define the amount of details returned by
284 284 this function.
285 285
286 286 This command can only be run using an |authtoken| with admin rights,
287 287 or users with at least read rights to the |repo|.
288 288
289 289 :param apiuser: This is filled automatically from the |authtoken|.
290 290 :type apiuser: AuthUser
291 291 :param repoid: The repository name or repository id
292 292 :type repoid: str or int
293 293 :param revision: revision for which listing should be done
294 294 :type revision: str
295 295 :param details: details can be 'basic|extended|full' full gives diff
296 296 info details like the diff itself, and number of changed files etc.
297 297 :type details: Optional(str)
298 298
299 299 """
300 300 repo = get_repo_or_error(repoid)
301 301 if not has_superadmin_permission(apiuser):
302 302 _perms = (
303 303 'repository.admin', 'repository.write', 'repository.read',)
304 304 validate_repo_permissions(apiuser, repoid, repo, _perms)
305 305
306 306 changes_details = Optional.extract(details)
307 307 _changes_details_types = ['basic', 'extended', 'full']
308 308 if changes_details not in _changes_details_types:
309 309 raise JSONRPCError(
310 310 'ret_type must be one of %s' % (
311 311 ','.join(_changes_details_types)))
312 312
313 313 pre_load = ['author', 'branch', 'date', 'message', 'parents',
314 314 'status', '_commit', '_file_paths']
315 315
316 316 try:
317 317 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
318 318 except TypeError as e:
319 raise JSONRPCError(e.message)
319 raise JSONRPCError(safe_str(e))
320 320 _cs_json = cs.__json__()
321 321 _cs_json['diff'] = build_commit_data(cs, changes_details)
322 322 if changes_details == 'full':
323 323 _cs_json['refs'] = cs._get_refs()
324 324 return _cs_json
325 325
326 326
327 327 @jsonrpc_method()
328 328 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
329 329 details=Optional('basic')):
330 330 """
331 331 Returns a set of commits limited by the number starting
332 332 from the `start_rev` option.
333 333
334 334 Additional parameters define the amount of details returned by this
335 335 function.
336 336
337 337 This command can only be run using an |authtoken| with admin rights,
338 338 or users with at least read rights to |repos|.
339 339
340 340 :param apiuser: This is filled automatically from the |authtoken|.
341 341 :type apiuser: AuthUser
342 342 :param repoid: The repository name or repository ID.
343 343 :type repoid: str or int
344 344 :param start_rev: The starting revision from where to get changesets.
345 345 :type start_rev: str
346 346 :param limit: Limit the number of commits to this amount
347 347 :type limit: str or int
348 348 :param details: Set the level of detail returned. Valid option are:
349 349 ``basic``, ``extended`` and ``full``.
350 350 :type details: Optional(str)
351 351
352 352 .. note::
353 353
354 354 Setting the parameter `details` to the value ``full`` is extensive
355 355 and returns details like the diff itself, and the number
356 356 of changed files.
357 357
358 358 """
359 359 repo = get_repo_or_error(repoid)
360 360 if not has_superadmin_permission(apiuser):
361 361 _perms = (
362 362 'repository.admin', 'repository.write', 'repository.read',)
363 363 validate_repo_permissions(apiuser, repoid, repo, _perms)
364 364
365 365 changes_details = Optional.extract(details)
366 366 _changes_details_types = ['basic', 'extended', 'full']
367 367 if changes_details not in _changes_details_types:
368 368 raise JSONRPCError(
369 369 'ret_type must be one of %s' % (
370 370 ','.join(_changes_details_types)))
371 371
372 372 limit = int(limit)
373 373 pre_load = ['author', 'branch', 'date', 'message', 'parents',
374 374 'status', '_commit', '_file_paths']
375 375
376 376 vcs_repo = repo.scm_instance()
377 377 # SVN needs a special case to distinguish its index and commit id
378 378 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
379 379 start_rev = vcs_repo.commit_ids[0]
380 380
381 381 try:
382 382 commits = vcs_repo.get_commits(
383 383 start_id=start_rev, pre_load=pre_load)
384 384 except TypeError as e:
385 raise JSONRPCError(e.message)
385 raise JSONRPCError(safe_str(e))
386 386 except Exception:
387 387 log.exception('Fetching of commits failed')
388 388 raise JSONRPCError('Error occurred during commit fetching')
389 389
390 390 ret = []
391 391 for cnt, commit in enumerate(commits):
392 392 if cnt >= limit != -1:
393 393 break
394 394 _cs_json = commit.__json__()
395 395 _cs_json['diff'] = build_commit_data(commit, changes_details)
396 396 if changes_details == 'full':
397 397 _cs_json['refs'] = {
398 398 'branches': [commit.branch],
399 399 'bookmarks': getattr(commit, 'bookmarks', []),
400 400 'tags': commit.tags
401 401 }
402 402 ret.append(_cs_json)
403 403 return ret
404 404
405 405
406 406 @jsonrpc_method()
407 407 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
408 408 ret_type=Optional('all'), details=Optional('basic'),
409 409 max_file_bytes=Optional(None)):
410 410 """
411 411 Returns a list of nodes and children in a flat list for a given
412 412 path at given revision.
413 413
414 414 It's possible to specify ret_type to show only `files` or `dirs`.
415 415
416 416 This command can only be run using an |authtoken| with admin rights,
417 417 or users with at least read rights to |repos|.
418 418
419 419 :param apiuser: This is filled automatically from the |authtoken|.
420 420 :type apiuser: AuthUser
421 421 :param repoid: The repository name or repository ID.
422 422 :type repoid: str or int
423 423 :param revision: The revision for which listing should be done.
424 424 :type revision: str
425 425 :param root_path: The path from which to start displaying.
426 426 :type root_path: str
427 427 :param ret_type: Set the return type. Valid options are
428 428 ``all`` (default), ``files`` and ``dirs``.
429 429 :type ret_type: Optional(str)
430 430 :param details: Returns extended information about nodes, such as
431 431 md5, binary, and or content. The valid options are ``basic`` and
432 432 ``full``.
433 433 :type details: Optional(str)
434 434 :param max_file_bytes: Only return file content under this file size bytes
435 435 :type details: Optional(int)
436 436
437 437 Example output:
438 438
439 439 .. code-block:: bash
440 440
441 441 id : <id_given_in_input>
442 442 result: [
443 443 {
444 444 "name" : "<name>"
445 445 "type" : "<type>",
446 446 "binary": "<true|false>" (only in extended mode)
447 447 "md5" : "<md5 of file content>" (only in extended mode)
448 448 },
449 449 ...
450 450 ]
451 451 error: null
452 452 """
453 453
454 454 repo = get_repo_or_error(repoid)
455 455 if not has_superadmin_permission(apiuser):
456 456 _perms = (
457 457 'repository.admin', 'repository.write', 'repository.read',)
458 458 validate_repo_permissions(apiuser, repoid, repo, _perms)
459 459
460 460 ret_type = Optional.extract(ret_type)
461 461 details = Optional.extract(details)
462 462 _extended_types = ['basic', 'full']
463 463 if details not in _extended_types:
464 464 raise JSONRPCError(
465 465 'ret_type must be one of %s' % (','.join(_extended_types)))
466 466 extended_info = False
467 467 content = False
468 468 if details == 'basic':
469 469 extended_info = True
470 470
471 471 if details == 'full':
472 472 extended_info = content = True
473 473
474 474 _map = {}
475 475 try:
476 476 # check if repo is not empty by any chance, skip quicker if it is.
477 477 _scm = repo.scm_instance()
478 478 if _scm.is_empty():
479 479 return []
480 480
481 481 _d, _f = ScmModel().get_nodes(
482 482 repo, revision, root_path, flat=False,
483 483 extended_info=extended_info, content=content,
484 484 max_file_bytes=max_file_bytes)
485 485 _map = {
486 486 'all': _d + _f,
487 487 'files': _f,
488 488 'dirs': _d,
489 489 }
490 490 return _map[ret_type]
491 491 except KeyError:
492 492 raise JSONRPCError(
493 493 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
494 494 except Exception:
495 495 log.exception("Exception occurred while trying to get repo nodes")
496 496 raise JSONRPCError(
497 497 'failed to get repo: `%s` nodes' % repo.repo_name
498 498 )
499 499
500 500
501 501 @jsonrpc_method()
502 502 def get_repo_refs(request, apiuser, repoid):
503 503 """
504 504 Returns a dictionary of current references. It returns
505 505 bookmarks, branches, closed_branches, and tags for given repository
506 506
507 507 It's possible to specify ret_type to show only `files` or `dirs`.
508 508
509 509 This command can only be run using an |authtoken| with admin rights,
510 510 or users with at least read rights to |repos|.
511 511
512 512 :param apiuser: This is filled automatically from the |authtoken|.
513 513 :type apiuser: AuthUser
514 514 :param repoid: The repository name or repository ID.
515 515 :type repoid: str or int
516 516
517 517 Example output:
518 518
519 519 .. code-block:: bash
520 520
521 521 id : <id_given_in_input>
522 522 "result": {
523 523 "bookmarks": {
524 524 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
525 525 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
526 526 },
527 527 "branches": {
528 528 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
529 529 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
530 530 },
531 531 "branches_closed": {},
532 532 "tags": {
533 533 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
534 534 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
535 535 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
536 536 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
537 537 }
538 538 }
539 539 error: null
540 540 """
541 541
542 542 repo = get_repo_or_error(repoid)
543 543 if not has_superadmin_permission(apiuser):
544 544 _perms = ('repository.admin', 'repository.write', 'repository.read',)
545 545 validate_repo_permissions(apiuser, repoid, repo, _perms)
546 546
547 547 try:
548 548 # check if repo is not empty by any chance, skip quicker if it is.
549 549 vcs_instance = repo.scm_instance()
550 550 refs = vcs_instance.refs()
551 551 return refs
552 552 except Exception:
553 553 log.exception("Exception occurred while trying to get repo refs")
554 554 raise JSONRPCError(
555 555 'failed to get repo: `%s` references' % repo.repo_name
556 556 )
557 557
558 558
559 559 @jsonrpc_method()
560 560 def create_repo(
561 561 request, apiuser, repo_name, repo_type,
562 562 owner=Optional(OAttr('apiuser')),
563 563 description=Optional(''),
564 564 private=Optional(False),
565 565 clone_uri=Optional(None),
566 566 push_uri=Optional(None),
567 567 landing_rev=Optional('rev:tip'),
568 568 enable_statistics=Optional(False),
569 569 enable_locking=Optional(False),
570 570 enable_downloads=Optional(False),
571 571 copy_permissions=Optional(False)):
572 572 """
573 573 Creates a repository.
574 574
575 575 * If the repository name contains "/", repository will be created inside
576 576 a repository group or nested repository groups
577 577
578 578 For example "foo/bar/repo1" will create |repo| called "repo1" inside
579 579 group "foo/bar". You have to have permissions to access and write to
580 580 the last repository group ("bar" in this example)
581 581
582 582 This command can only be run using an |authtoken| with at least
583 583 permissions to create repositories, or write permissions to
584 584 parent repository groups.
585 585
586 586 :param apiuser: This is filled automatically from the |authtoken|.
587 587 :type apiuser: AuthUser
588 588 :param repo_name: Set the repository name.
589 589 :type repo_name: str
590 590 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
591 591 :type repo_type: str
592 592 :param owner: user_id or username
593 593 :type owner: Optional(str)
594 594 :param description: Set the repository description.
595 595 :type description: Optional(str)
596 596 :param private: set repository as private
597 597 :type private: bool
598 598 :param clone_uri: set clone_uri
599 599 :type clone_uri: str
600 600 :param push_uri: set push_uri
601 601 :type push_uri: str
602 602 :param landing_rev: <rev_type>:<rev>
603 603 :type landing_rev: str
604 604 :param enable_locking:
605 605 :type enable_locking: bool
606 606 :param enable_downloads:
607 607 :type enable_downloads: bool
608 608 :param enable_statistics:
609 609 :type enable_statistics: bool
610 610 :param copy_permissions: Copy permission from group in which the
611 611 repository is being created.
612 612 :type copy_permissions: bool
613 613
614 614
615 615 Example output:
616 616
617 617 .. code-block:: bash
618 618
619 619 id : <id_given_in_input>
620 620 result: {
621 621 "msg": "Created new repository `<reponame>`",
622 622 "success": true,
623 623 "task": "<celery task id or None if done sync>"
624 624 }
625 625 error: null
626 626
627 627
628 628 Example error output:
629 629
630 630 .. code-block:: bash
631 631
632 632 id : <id_given_in_input>
633 633 result : null
634 634 error : {
635 635 'failed to create repository `<repo_name>`'
636 636 }
637 637
638 638 """
639 639
640 640 owner = validate_set_owner_permissions(apiuser, owner)
641 641
642 642 description = Optional.extract(description)
643 643 copy_permissions = Optional.extract(copy_permissions)
644 644 clone_uri = Optional.extract(clone_uri)
645 645 push_uri = Optional.extract(push_uri)
646 646 landing_commit_ref = Optional.extract(landing_rev)
647 647
648 648 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
649 649 if isinstance(private, Optional):
650 650 private = defs.get('repo_private') or Optional.extract(private)
651 651 if isinstance(repo_type, Optional):
652 652 repo_type = defs.get('repo_type')
653 653 if isinstance(enable_statistics, Optional):
654 654 enable_statistics = defs.get('repo_enable_statistics')
655 655 if isinstance(enable_locking, Optional):
656 656 enable_locking = defs.get('repo_enable_locking')
657 657 if isinstance(enable_downloads, Optional):
658 658 enable_downloads = defs.get('repo_enable_downloads')
659 659
660 660 schema = repo_schema.RepoSchema().bind(
661 661 repo_type_options=rhodecode.BACKENDS.keys(),
662 662 repo_type=repo_type,
663 663 # user caller
664 664 user=apiuser)
665 665
666 666 try:
667 667 schema_data = schema.deserialize(dict(
668 668 repo_name=repo_name,
669 669 repo_type=repo_type,
670 670 repo_owner=owner.username,
671 671 repo_description=description,
672 672 repo_landing_commit_ref=landing_commit_ref,
673 673 repo_clone_uri=clone_uri,
674 674 repo_push_uri=push_uri,
675 675 repo_private=private,
676 676 repo_copy_permissions=copy_permissions,
677 677 repo_enable_statistics=enable_statistics,
678 678 repo_enable_downloads=enable_downloads,
679 679 repo_enable_locking=enable_locking))
680 680 except validation_schema.Invalid as err:
681 681 raise JSONRPCValidationError(colander_exc=err)
682 682
683 683 try:
684 684 data = {
685 685 'owner': owner,
686 686 'repo_name': schema_data['repo_group']['repo_name_without_group'],
687 687 'repo_name_full': schema_data['repo_name'],
688 688 'repo_group': schema_data['repo_group']['repo_group_id'],
689 689 'repo_type': schema_data['repo_type'],
690 690 'repo_description': schema_data['repo_description'],
691 691 'repo_private': schema_data['repo_private'],
692 692 'clone_uri': schema_data['repo_clone_uri'],
693 693 'push_uri': schema_data['repo_push_uri'],
694 694 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
695 695 'enable_statistics': schema_data['repo_enable_statistics'],
696 696 'enable_locking': schema_data['repo_enable_locking'],
697 697 'enable_downloads': schema_data['repo_enable_downloads'],
698 698 'repo_copy_permissions': schema_data['repo_copy_permissions'],
699 699 }
700 700
701 701 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
702 702 task_id = get_task_id(task)
703 703 # no commit, it's done in RepoModel, or async via celery
704 704 return {
705 705 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
706 706 'success': True, # cannot return the repo data here since fork
707 707 # can be done async
708 708 'task': task_id
709 709 }
710 710 except Exception:
711 711 log.exception(
712 712 u"Exception while trying to create the repository %s",
713 713 schema_data['repo_name'])
714 714 raise JSONRPCError(
715 715 'failed to create repository `%s`' % (schema_data['repo_name'],))
716 716
717 717
718 718 @jsonrpc_method()
719 719 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
720 720 description=Optional('')):
721 721 """
722 722 Adds an extra field to a repository.
723 723
724 724 This command can only be run using an |authtoken| with at least
725 725 write permissions to the |repo|.
726 726
727 727 :param apiuser: This is filled automatically from the |authtoken|.
728 728 :type apiuser: AuthUser
729 729 :param repoid: Set the repository name or repository id.
730 730 :type repoid: str or int
731 731 :param key: Create a unique field key for this repository.
732 732 :type key: str
733 733 :param label:
734 734 :type label: Optional(str)
735 735 :param description:
736 736 :type description: Optional(str)
737 737 """
738 738 repo = get_repo_or_error(repoid)
739 739 if not has_superadmin_permission(apiuser):
740 740 _perms = ('repository.admin',)
741 741 validate_repo_permissions(apiuser, repoid, repo, _perms)
742 742
743 743 label = Optional.extract(label) or key
744 744 description = Optional.extract(description)
745 745
746 746 field = RepositoryField.get_by_key_name(key, repo)
747 747 if field:
748 748 raise JSONRPCError('Field with key '
749 749 '`%s` exists for repo `%s`' % (key, repoid))
750 750
751 751 try:
752 752 RepoModel().add_repo_field(repo, key, field_label=label,
753 753 field_desc=description)
754 754 Session().commit()
755 755 return {
756 756 'msg': "Added new repository field `%s`" % (key,),
757 757 'success': True,
758 758 }
759 759 except Exception:
760 760 log.exception("Exception occurred while trying to add field to repo")
761 761 raise JSONRPCError(
762 762 'failed to create new field for repository `%s`' % (repoid,))
763 763
764 764
765 765 @jsonrpc_method()
766 766 def remove_field_from_repo(request, apiuser, repoid, key):
767 767 """
768 768 Removes an extra field from a repository.
769 769
770 770 This command can only be run using an |authtoken| with at least
771 771 write permissions to the |repo|.
772 772
773 773 :param apiuser: This is filled automatically from the |authtoken|.
774 774 :type apiuser: AuthUser
775 775 :param repoid: Set the repository name or repository ID.
776 776 :type repoid: str or int
777 777 :param key: Set the unique field key for this repository.
778 778 :type key: str
779 779 """
780 780
781 781 repo = get_repo_or_error(repoid)
782 782 if not has_superadmin_permission(apiuser):
783 783 _perms = ('repository.admin',)
784 784 validate_repo_permissions(apiuser, repoid, repo, _perms)
785 785
786 786 field = RepositoryField.get_by_key_name(key, repo)
787 787 if not field:
788 788 raise JSONRPCError('Field with key `%s` does not '
789 789 'exists for repo `%s`' % (key, repoid))
790 790
791 791 try:
792 792 RepoModel().delete_repo_field(repo, field_key=key)
793 793 Session().commit()
794 794 return {
795 795 'msg': "Deleted repository field `%s`" % (key,),
796 796 'success': True,
797 797 }
798 798 except Exception:
799 799 log.exception(
800 800 "Exception occurred while trying to delete field from repo")
801 801 raise JSONRPCError(
802 802 'failed to delete field for repository `%s`' % (repoid,))
803 803
804 804
805 805 @jsonrpc_method()
806 806 def update_repo(
807 807 request, apiuser, repoid, repo_name=Optional(None),
808 808 owner=Optional(OAttr('apiuser')), description=Optional(''),
809 809 private=Optional(False),
810 810 clone_uri=Optional(None), push_uri=Optional(None),
811 811 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
812 812 enable_statistics=Optional(False),
813 813 enable_locking=Optional(False),
814 814 enable_downloads=Optional(False), fields=Optional('')):
815 815 """
816 816 Updates a repository with the given information.
817 817
818 818 This command can only be run using an |authtoken| with at least
819 819 admin permissions to the |repo|.
820 820
821 821 * If the repository name contains "/", repository will be updated
822 822 accordingly with a repository group or nested repository groups
823 823
824 824 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
825 825 called "repo-test" and place it inside group "foo/bar".
826 826 You have to have permissions to access and write to the last repository
827 827 group ("bar" in this example)
828 828
829 829 :param apiuser: This is filled automatically from the |authtoken|.
830 830 :type apiuser: AuthUser
831 831 :param repoid: repository name or repository ID.
832 832 :type repoid: str or int
833 833 :param repo_name: Update the |repo| name, including the
834 834 repository group it's in.
835 835 :type repo_name: str
836 836 :param owner: Set the |repo| owner.
837 837 :type owner: str
838 838 :param fork_of: Set the |repo| as fork of another |repo|.
839 839 :type fork_of: str
840 840 :param description: Update the |repo| description.
841 841 :type description: str
842 842 :param private: Set the |repo| as private. (True | False)
843 843 :type private: bool
844 844 :param clone_uri: Update the |repo| clone URI.
845 845 :type clone_uri: str
846 846 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
847 847 :type landing_rev: str
848 848 :param enable_statistics: Enable statistics on the |repo|, (True | False).
849 849 :type enable_statistics: bool
850 850 :param enable_locking: Enable |repo| locking.
851 851 :type enable_locking: bool
852 852 :param enable_downloads: Enable downloads from the |repo|, (True | False).
853 853 :type enable_downloads: bool
854 854 :param fields: Add extra fields to the |repo|. Use the following
855 855 example format: ``field_key=field_val,field_key2=fieldval2``.
856 856 Escape ', ' with \,
857 857 :type fields: str
858 858 """
859 859
860 860 repo = get_repo_or_error(repoid)
861 861
862 862 include_secrets = False
863 863 if not has_superadmin_permission(apiuser):
864 864 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
865 865 else:
866 866 include_secrets = True
867 867
868 868 updates = dict(
869 869 repo_name=repo_name
870 870 if not isinstance(repo_name, Optional) else repo.repo_name,
871 871
872 872 fork_id=fork_of
873 873 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
874 874
875 875 user=owner
876 876 if not isinstance(owner, Optional) else repo.user.username,
877 877
878 878 repo_description=description
879 879 if not isinstance(description, Optional) else repo.description,
880 880
881 881 repo_private=private
882 882 if not isinstance(private, Optional) else repo.private,
883 883
884 884 clone_uri=clone_uri
885 885 if not isinstance(clone_uri, Optional) else repo.clone_uri,
886 886
887 887 push_uri=push_uri
888 888 if not isinstance(push_uri, Optional) else repo.push_uri,
889 889
890 890 repo_landing_rev=landing_rev
891 891 if not isinstance(landing_rev, Optional) else repo._landing_revision,
892 892
893 893 repo_enable_statistics=enable_statistics
894 894 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
895 895
896 896 repo_enable_locking=enable_locking
897 897 if not isinstance(enable_locking, Optional) else repo.enable_locking,
898 898
899 899 repo_enable_downloads=enable_downloads
900 900 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
901 901
902 902 ref_choices, _labels = ScmModel().get_repo_landing_revs(
903 903 request.translate, repo=repo)
904 904
905 905 old_values = repo.get_api_data()
906 906 repo_type = repo.repo_type
907 907 schema = repo_schema.RepoSchema().bind(
908 908 repo_type_options=rhodecode.BACKENDS.keys(),
909 909 repo_ref_options=ref_choices,
910 910 repo_type=repo_type,
911 911 # user caller
912 912 user=apiuser,
913 913 old_values=old_values)
914 914 try:
915 915 schema_data = schema.deserialize(dict(
916 916 # we save old value, users cannot change type
917 917 repo_type=repo_type,
918 918
919 919 repo_name=updates['repo_name'],
920 920 repo_owner=updates['user'],
921 921 repo_description=updates['repo_description'],
922 922 repo_clone_uri=updates['clone_uri'],
923 923 repo_push_uri=updates['push_uri'],
924 924 repo_fork_of=updates['fork_id'],
925 925 repo_private=updates['repo_private'],
926 926 repo_landing_commit_ref=updates['repo_landing_rev'],
927 927 repo_enable_statistics=updates['repo_enable_statistics'],
928 928 repo_enable_downloads=updates['repo_enable_downloads'],
929 929 repo_enable_locking=updates['repo_enable_locking']))
930 930 except validation_schema.Invalid as err:
931 931 raise JSONRPCValidationError(colander_exc=err)
932 932
933 933 # save validated data back into the updates dict
934 934 validated_updates = dict(
935 935 repo_name=schema_data['repo_group']['repo_name_without_group'],
936 936 repo_group=schema_data['repo_group']['repo_group_id'],
937 937
938 938 user=schema_data['repo_owner'],
939 939 repo_description=schema_data['repo_description'],
940 940 repo_private=schema_data['repo_private'],
941 941 clone_uri=schema_data['repo_clone_uri'],
942 942 push_uri=schema_data['repo_push_uri'],
943 943 repo_landing_rev=schema_data['repo_landing_commit_ref'],
944 944 repo_enable_statistics=schema_data['repo_enable_statistics'],
945 945 repo_enable_locking=schema_data['repo_enable_locking'],
946 946 repo_enable_downloads=schema_data['repo_enable_downloads'],
947 947 )
948 948
949 949 if schema_data['repo_fork_of']:
950 950 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
951 951 validated_updates['fork_id'] = fork_repo.repo_id
952 952
953 953 # extra fields
954 954 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
955 955 if fields:
956 956 validated_updates.update(fields)
957 957
958 958 try:
959 959 RepoModel().update(repo, **validated_updates)
960 960 audit_logger.store_api(
961 961 'repo.edit', action_data={'old_data': old_values},
962 962 user=apiuser, repo=repo)
963 963 Session().commit()
964 964 return {
965 965 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
966 966 'repository': repo.get_api_data(include_secrets=include_secrets)
967 967 }
968 968 except Exception:
969 969 log.exception(
970 970 u"Exception while trying to update the repository %s",
971 971 repoid)
972 972 raise JSONRPCError('failed to update repo `%s`' % repoid)
973 973
974 974
975 975 @jsonrpc_method()
976 976 def fork_repo(request, apiuser, repoid, fork_name,
977 977 owner=Optional(OAttr('apiuser')),
978 978 description=Optional(''),
979 979 private=Optional(False),
980 980 clone_uri=Optional(None),
981 981 landing_rev=Optional('rev:tip'),
982 982 copy_permissions=Optional(False)):
983 983 """
984 984 Creates a fork of the specified |repo|.
985 985
986 986 * If the fork_name contains "/", fork will be created inside
987 987 a repository group or nested repository groups
988 988
989 989 For example "foo/bar/fork-repo" will create fork called "fork-repo"
990 990 inside group "foo/bar". You have to have permissions to access and
991 991 write to the last repository group ("bar" in this example)
992 992
993 993 This command can only be run using an |authtoken| with minimum
994 994 read permissions of the forked repo, create fork permissions for an user.
995 995
996 996 :param apiuser: This is filled automatically from the |authtoken|.
997 997 :type apiuser: AuthUser
998 998 :param repoid: Set repository name or repository ID.
999 999 :type repoid: str or int
1000 1000 :param fork_name: Set the fork name, including it's repository group membership.
1001 1001 :type fork_name: str
1002 1002 :param owner: Set the fork owner.
1003 1003 :type owner: str
1004 1004 :param description: Set the fork description.
1005 1005 :type description: str
1006 1006 :param copy_permissions: Copy permissions from parent |repo|. The
1007 1007 default is False.
1008 1008 :type copy_permissions: bool
1009 1009 :param private: Make the fork private. The default is False.
1010 1010 :type private: bool
1011 1011 :param landing_rev: Set the landing revision. The default is tip.
1012 1012
1013 1013 Example output:
1014 1014
1015 1015 .. code-block:: bash
1016 1016
1017 1017 id : <id_for_response>
1018 1018 api_key : "<api_key>"
1019 1019 args: {
1020 1020 "repoid" : "<reponame or repo_id>",
1021 1021 "fork_name": "<forkname>",
1022 1022 "owner": "<username or user_id = Optional(=apiuser)>",
1023 1023 "description": "<description>",
1024 1024 "copy_permissions": "<bool>",
1025 1025 "private": "<bool>",
1026 1026 "landing_rev": "<landing_rev>"
1027 1027 }
1028 1028
1029 1029 Example error output:
1030 1030
1031 1031 .. code-block:: bash
1032 1032
1033 1033 id : <id_given_in_input>
1034 1034 result: {
1035 1035 "msg": "Created fork of `<reponame>` as `<forkname>`",
1036 1036 "success": true,
1037 1037 "task": "<celery task id or None if done sync>"
1038 1038 }
1039 1039 error: null
1040 1040
1041 1041 """
1042 1042
1043 1043 repo = get_repo_or_error(repoid)
1044 1044 repo_name = repo.repo_name
1045 1045
1046 1046 if not has_superadmin_permission(apiuser):
1047 1047 # check if we have at least read permission for
1048 1048 # this repo that we fork !
1049 1049 _perms = (
1050 1050 'repository.admin', 'repository.write', 'repository.read')
1051 1051 validate_repo_permissions(apiuser, repoid, repo, _perms)
1052 1052
1053 1053 # check if the regular user has at least fork permissions as well
1054 1054 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1055 1055 raise JSONRPCForbidden()
1056 1056
1057 1057 # check if user can set owner parameter
1058 1058 owner = validate_set_owner_permissions(apiuser, owner)
1059 1059
1060 1060 description = Optional.extract(description)
1061 1061 copy_permissions = Optional.extract(copy_permissions)
1062 1062 clone_uri = Optional.extract(clone_uri)
1063 1063 landing_commit_ref = Optional.extract(landing_rev)
1064 1064 private = Optional.extract(private)
1065 1065
1066 1066 schema = repo_schema.RepoSchema().bind(
1067 1067 repo_type_options=rhodecode.BACKENDS.keys(),
1068 1068 repo_type=repo.repo_type,
1069 1069 # user caller
1070 1070 user=apiuser)
1071 1071
1072 1072 try:
1073 1073 schema_data = schema.deserialize(dict(
1074 1074 repo_name=fork_name,
1075 1075 repo_type=repo.repo_type,
1076 1076 repo_owner=owner.username,
1077 1077 repo_description=description,
1078 1078 repo_landing_commit_ref=landing_commit_ref,
1079 1079 repo_clone_uri=clone_uri,
1080 1080 repo_private=private,
1081 1081 repo_copy_permissions=copy_permissions))
1082 1082 except validation_schema.Invalid as err:
1083 1083 raise JSONRPCValidationError(colander_exc=err)
1084 1084
1085 1085 try:
1086 1086 data = {
1087 1087 'fork_parent_id': repo.repo_id,
1088 1088
1089 1089 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1090 1090 'repo_name_full': schema_data['repo_name'],
1091 1091 'repo_group': schema_data['repo_group']['repo_group_id'],
1092 1092 'repo_type': schema_data['repo_type'],
1093 1093 'description': schema_data['repo_description'],
1094 1094 'private': schema_data['repo_private'],
1095 1095 'copy_permissions': schema_data['repo_copy_permissions'],
1096 1096 'landing_rev': schema_data['repo_landing_commit_ref'],
1097 1097 }
1098 1098
1099 1099 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1100 1100 # no commit, it's done in RepoModel, or async via celery
1101 1101 task_id = get_task_id(task)
1102 1102
1103 1103 return {
1104 1104 'msg': 'Created fork of `%s` as `%s`' % (
1105 1105 repo.repo_name, schema_data['repo_name']),
1106 1106 'success': True, # cannot return the repo data here since fork
1107 1107 # can be done async
1108 1108 'task': task_id
1109 1109 }
1110 1110 except Exception:
1111 1111 log.exception(
1112 1112 u"Exception while trying to create fork %s",
1113 1113 schema_data['repo_name'])
1114 1114 raise JSONRPCError(
1115 1115 'failed to fork repository `%s` as `%s`' % (
1116 1116 repo_name, schema_data['repo_name']))
1117 1117
1118 1118
1119 1119 @jsonrpc_method()
1120 1120 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1121 1121 """
1122 1122 Deletes a repository.
1123 1123
1124 1124 * When the `forks` parameter is set it's possible to detach or delete
1125 1125 forks of deleted repository.
1126 1126
1127 1127 This command can only be run using an |authtoken| with admin
1128 1128 permissions on the |repo|.
1129 1129
1130 1130 :param apiuser: This is filled automatically from the |authtoken|.
1131 1131 :type apiuser: AuthUser
1132 1132 :param repoid: Set the repository name or repository ID.
1133 1133 :type repoid: str or int
1134 1134 :param forks: Set to `detach` or `delete` forks from the |repo|.
1135 1135 :type forks: Optional(str)
1136 1136
1137 1137 Example error output:
1138 1138
1139 1139 .. code-block:: bash
1140 1140
1141 1141 id : <id_given_in_input>
1142 1142 result: {
1143 1143 "msg": "Deleted repository `<reponame>`",
1144 1144 "success": true
1145 1145 }
1146 1146 error: null
1147 1147 """
1148 1148
1149 1149 repo = get_repo_or_error(repoid)
1150 1150 repo_name = repo.repo_name
1151 1151 if not has_superadmin_permission(apiuser):
1152 1152 _perms = ('repository.admin',)
1153 1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
1154 1154
1155 1155 try:
1156 1156 handle_forks = Optional.extract(forks)
1157 1157 _forks_msg = ''
1158 1158 _forks = [f for f in repo.forks]
1159 1159 if handle_forks == 'detach':
1160 1160 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1161 1161 elif handle_forks == 'delete':
1162 1162 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1163 1163 elif _forks:
1164 1164 raise JSONRPCError(
1165 1165 'Cannot delete `%s` it still contains attached forks' %
1166 1166 (repo.repo_name,)
1167 1167 )
1168 1168 old_data = repo.get_api_data()
1169 1169 RepoModel().delete(repo, forks=forks)
1170 1170
1171 1171 repo = audit_logger.RepoWrap(repo_id=None,
1172 1172 repo_name=repo.repo_name)
1173 1173
1174 1174 audit_logger.store_api(
1175 1175 'repo.delete', action_data={'old_data': old_data},
1176 1176 user=apiuser, repo=repo)
1177 1177
1178 1178 ScmModel().mark_for_invalidation(repo_name, delete=True)
1179 1179 Session().commit()
1180 1180 return {
1181 1181 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1182 1182 'success': True
1183 1183 }
1184 1184 except Exception:
1185 1185 log.exception("Exception occurred while trying to delete repo")
1186 1186 raise JSONRPCError(
1187 1187 'failed to delete repository `%s`' % (repo_name,)
1188 1188 )
1189 1189
1190 1190
1191 1191 #TODO: marcink, change name ?
1192 1192 @jsonrpc_method()
1193 1193 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1194 1194 """
1195 1195 Invalidates the cache for the specified repository.
1196 1196
1197 1197 This command can only be run using an |authtoken| with admin rights to
1198 1198 the specified repository.
1199 1199
1200 1200 This command takes the following options:
1201 1201
1202 1202 :param apiuser: This is filled automatically from |authtoken|.
1203 1203 :type apiuser: AuthUser
1204 1204 :param repoid: Sets the repository name or repository ID.
1205 1205 :type repoid: str or int
1206 1206 :param delete_keys: This deletes the invalidated keys instead of
1207 1207 just flagging them.
1208 1208 :type delete_keys: Optional(``True`` | ``False``)
1209 1209
1210 1210 Example output:
1211 1211
1212 1212 .. code-block:: bash
1213 1213
1214 1214 id : <id_given_in_input>
1215 1215 result : {
1216 1216 'msg': Cache for repository `<repository name>` was invalidated,
1217 1217 'repository': <repository name>
1218 1218 }
1219 1219 error : null
1220 1220
1221 1221 Example error output:
1222 1222
1223 1223 .. code-block:: bash
1224 1224
1225 1225 id : <id_given_in_input>
1226 1226 result : null
1227 1227 error : {
1228 1228 'Error occurred during cache invalidation action'
1229 1229 }
1230 1230
1231 1231 """
1232 1232
1233 1233 repo = get_repo_or_error(repoid)
1234 1234 if not has_superadmin_permission(apiuser):
1235 1235 _perms = ('repository.admin', 'repository.write',)
1236 1236 validate_repo_permissions(apiuser, repoid, repo, _perms)
1237 1237
1238 1238 delete = Optional.extract(delete_keys)
1239 1239 try:
1240 1240 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1241 1241 return {
1242 1242 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1243 1243 'repository': repo.repo_name
1244 1244 }
1245 1245 except Exception:
1246 1246 log.exception(
1247 1247 "Exception occurred while trying to invalidate repo cache")
1248 1248 raise JSONRPCError(
1249 1249 'Error occurred during cache invalidation action'
1250 1250 )
1251 1251
1252 1252
1253 1253 #TODO: marcink, change name ?
1254 1254 @jsonrpc_method()
1255 1255 def lock(request, apiuser, repoid, locked=Optional(None),
1256 1256 userid=Optional(OAttr('apiuser'))):
1257 1257 """
1258 1258 Sets the lock state of the specified |repo| by the given user.
1259 1259 From more information, see :ref:`repo-locking`.
1260 1260
1261 1261 * If the ``userid`` option is not set, the repository is locked to the
1262 1262 user who called the method.
1263 1263 * If the ``locked`` parameter is not set, the current lock state of the
1264 1264 repository is displayed.
1265 1265
1266 1266 This command can only be run using an |authtoken| with admin rights to
1267 1267 the specified repository.
1268 1268
1269 1269 This command takes the following options:
1270 1270
1271 1271 :param apiuser: This is filled automatically from the |authtoken|.
1272 1272 :type apiuser: AuthUser
1273 1273 :param repoid: Sets the repository name or repository ID.
1274 1274 :type repoid: str or int
1275 1275 :param locked: Sets the lock state.
1276 1276 :type locked: Optional(``True`` | ``False``)
1277 1277 :param userid: Set the repository lock to this user.
1278 1278 :type userid: Optional(str or int)
1279 1279
1280 1280 Example error output:
1281 1281
1282 1282 .. code-block:: bash
1283 1283
1284 1284 id : <id_given_in_input>
1285 1285 result : {
1286 1286 'repo': '<reponame>',
1287 1287 'locked': <bool: lock state>,
1288 1288 'locked_since': <int: lock timestamp>,
1289 1289 'locked_by': <username of person who made the lock>,
1290 1290 'lock_reason': <str: reason for locking>,
1291 1291 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1292 1292 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1293 1293 or
1294 1294 'msg': 'Repo `<repository name>` not locked.'
1295 1295 or
1296 1296 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1297 1297 }
1298 1298 error : null
1299 1299
1300 1300 Example error output:
1301 1301
1302 1302 .. code-block:: bash
1303 1303
1304 1304 id : <id_given_in_input>
1305 1305 result : null
1306 1306 error : {
1307 1307 'Error occurred locking repository `<reponame>`'
1308 1308 }
1309 1309 """
1310 1310
1311 1311 repo = get_repo_or_error(repoid)
1312 1312 if not has_superadmin_permission(apiuser):
1313 1313 # check if we have at least write permission for this repo !
1314 1314 _perms = ('repository.admin', 'repository.write',)
1315 1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1316 1316
1317 1317 # make sure normal user does not pass someone else userid,
1318 1318 # he is not allowed to do that
1319 1319 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1320 1320 raise JSONRPCError('userid is not the same as your user')
1321 1321
1322 1322 if isinstance(userid, Optional):
1323 1323 userid = apiuser.user_id
1324 1324
1325 1325 user = get_user_or_error(userid)
1326 1326
1327 1327 if isinstance(locked, Optional):
1328 1328 lockobj = repo.locked
1329 1329
1330 1330 if lockobj[0] is None:
1331 1331 _d = {
1332 1332 'repo': repo.repo_name,
1333 1333 'locked': False,
1334 1334 'locked_since': None,
1335 1335 'locked_by': None,
1336 1336 'lock_reason': None,
1337 1337 'lock_state_changed': False,
1338 1338 'msg': 'Repo `%s` not locked.' % repo.repo_name
1339 1339 }
1340 1340 return _d
1341 1341 else:
1342 1342 _user_id, _time, _reason = lockobj
1343 1343 lock_user = get_user_or_error(userid)
1344 1344 _d = {
1345 1345 'repo': repo.repo_name,
1346 1346 'locked': True,
1347 1347 'locked_since': _time,
1348 1348 'locked_by': lock_user.username,
1349 1349 'lock_reason': _reason,
1350 1350 'lock_state_changed': False,
1351 1351 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1352 1352 % (repo.repo_name, lock_user.username,
1353 1353 json.dumps(time_to_datetime(_time))))
1354 1354 }
1355 1355 return _d
1356 1356
1357 1357 # force locked state through a flag
1358 1358 else:
1359 1359 locked = str2bool(locked)
1360 1360 lock_reason = Repository.LOCK_API
1361 1361 try:
1362 1362 if locked:
1363 1363 lock_time = time.time()
1364 1364 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1365 1365 else:
1366 1366 lock_time = None
1367 1367 Repository.unlock(repo)
1368 1368 _d = {
1369 1369 'repo': repo.repo_name,
1370 1370 'locked': locked,
1371 1371 'locked_since': lock_time,
1372 1372 'locked_by': user.username,
1373 1373 'lock_reason': lock_reason,
1374 1374 'lock_state_changed': True,
1375 1375 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1376 1376 % (user.username, repo.repo_name, locked))
1377 1377 }
1378 1378 return _d
1379 1379 except Exception:
1380 1380 log.exception(
1381 1381 "Exception occurred while trying to lock repository")
1382 1382 raise JSONRPCError(
1383 1383 'Error occurred locking repository `%s`' % repo.repo_name
1384 1384 )
1385 1385
1386 1386
1387 1387 @jsonrpc_method()
1388 1388 def comment_commit(
1389 1389 request, apiuser, repoid, commit_id, message, status=Optional(None),
1390 1390 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1391 1391 resolves_comment_id=Optional(None),
1392 1392 userid=Optional(OAttr('apiuser'))):
1393 1393 """
1394 1394 Set a commit comment, and optionally change the status of the commit.
1395 1395
1396 1396 :param apiuser: This is filled automatically from the |authtoken|.
1397 1397 :type apiuser: AuthUser
1398 1398 :param repoid: Set the repository name or repository ID.
1399 1399 :type repoid: str or int
1400 1400 :param commit_id: Specify the commit_id for which to set a comment.
1401 1401 :type commit_id: str
1402 1402 :param message: The comment text.
1403 1403 :type message: str
1404 1404 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1405 1405 'approved', 'rejected', 'under_review'
1406 1406 :type status: str
1407 1407 :param comment_type: Comment type, one of: 'note', 'todo'
1408 1408 :type comment_type: Optional(str), default: 'note'
1409 1409 :param userid: Set the user name of the comment creator.
1410 1410 :type userid: Optional(str or int)
1411 1411
1412 1412 Example error output:
1413 1413
1414 1414 .. code-block:: bash
1415 1415
1416 1416 {
1417 1417 "id" : <id_given_in_input>,
1418 1418 "result" : {
1419 1419 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1420 1420 "status_change": null or <status>,
1421 1421 "success": true
1422 1422 },
1423 1423 "error" : null
1424 1424 }
1425 1425
1426 1426 """
1427 1427 repo = get_repo_or_error(repoid)
1428 1428 if not has_superadmin_permission(apiuser):
1429 1429 _perms = ('repository.read', 'repository.write', 'repository.admin')
1430 1430 validate_repo_permissions(apiuser, repoid, repo, _perms)
1431 1431
1432 1432 try:
1433 1433 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1434 1434 except Exception as e:
1435 1435 log.exception('Failed to fetch commit')
1436 raise JSONRPCError(e.message)
1436 raise JSONRPCError(safe_str(e))
1437 1437
1438 1438 if isinstance(userid, Optional):
1439 1439 userid = apiuser.user_id
1440 1440
1441 1441 user = get_user_or_error(userid)
1442 1442 status = Optional.extract(status)
1443 1443 comment_type = Optional.extract(comment_type)
1444 1444 resolves_comment_id = Optional.extract(resolves_comment_id)
1445 1445
1446 1446 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1447 1447 if status and status not in allowed_statuses:
1448 1448 raise JSONRPCError('Bad status, must be on '
1449 1449 'of %s got %s' % (allowed_statuses, status,))
1450 1450
1451 1451 if resolves_comment_id:
1452 1452 comment = ChangesetComment.get(resolves_comment_id)
1453 1453 if not comment:
1454 1454 raise JSONRPCError(
1455 1455 'Invalid resolves_comment_id `%s` for this commit.'
1456 1456 % resolves_comment_id)
1457 1457 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1458 1458 raise JSONRPCError(
1459 1459 'Comment `%s` is wrong type for setting status to resolved.'
1460 1460 % resolves_comment_id)
1461 1461
1462 1462 try:
1463 1463 rc_config = SettingsModel().get_all_settings()
1464 1464 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1465 1465 status_change_label = ChangesetStatus.get_status_lbl(status)
1466 1466 comment = CommentsModel().create(
1467 1467 message, repo, user, commit_id=commit_id,
1468 1468 status_change=status_change_label,
1469 1469 status_change_type=status,
1470 1470 renderer=renderer,
1471 1471 comment_type=comment_type,
1472 1472 resolves_comment_id=resolves_comment_id,
1473 1473 auth_user=apiuser
1474 1474 )
1475 1475 if status:
1476 1476 # also do a status change
1477 1477 try:
1478 1478 ChangesetStatusModel().set_status(
1479 1479 repo, status, user, comment, revision=commit_id,
1480 1480 dont_allow_on_closed_pull_request=True
1481 1481 )
1482 1482 except StatusChangeOnClosedPullRequestError:
1483 1483 log.exception(
1484 1484 "Exception occurred while trying to change repo commit status")
1485 1485 msg = ('Changing status on a changeset associated with '
1486 1486 'a closed pull request is not allowed')
1487 1487 raise JSONRPCError(msg)
1488 1488
1489 1489 Session().commit()
1490 1490 return {
1491 1491 'msg': (
1492 1492 'Commented on commit `%s` for repository `%s`' % (
1493 1493 comment.revision, repo.repo_name)),
1494 1494 'status_change': status,
1495 1495 'success': True,
1496 1496 }
1497 1497 except JSONRPCError:
1498 1498 # catch any inside errors, and re-raise them to prevent from
1499 1499 # below global catch to silence them
1500 1500 raise
1501 1501 except Exception:
1502 1502 log.exception("Exception occurred while trying to comment on commit")
1503 1503 raise JSONRPCError(
1504 1504 'failed to set comment on repository `%s`' % (repo.repo_name,)
1505 1505 )
1506 1506
1507 1507
1508 1508 @jsonrpc_method()
1509 1509 def grant_user_permission(request, apiuser, repoid, userid, perm):
1510 1510 """
1511 1511 Grant permissions for the specified user on the given repository,
1512 1512 or update existing permissions if found.
1513 1513
1514 1514 This command can only be run using an |authtoken| with admin
1515 1515 permissions on the |repo|.
1516 1516
1517 1517 :param apiuser: This is filled automatically from the |authtoken|.
1518 1518 :type apiuser: AuthUser
1519 1519 :param repoid: Set the repository name or repository ID.
1520 1520 :type repoid: str or int
1521 1521 :param userid: Set the user name.
1522 1522 :type userid: str
1523 1523 :param perm: Set the user permissions, using the following format
1524 1524 ``(repository.(none|read|write|admin))``
1525 1525 :type perm: str
1526 1526
1527 1527 Example output:
1528 1528
1529 1529 .. code-block:: bash
1530 1530
1531 1531 id : <id_given_in_input>
1532 1532 result: {
1533 1533 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1534 1534 "success": true
1535 1535 }
1536 1536 error: null
1537 1537 """
1538 1538
1539 1539 repo = get_repo_or_error(repoid)
1540 1540 user = get_user_or_error(userid)
1541 1541 perm = get_perm_or_error(perm)
1542 1542 if not has_superadmin_permission(apiuser):
1543 1543 _perms = ('repository.admin',)
1544 1544 validate_repo_permissions(apiuser, repoid, repo, _perms)
1545 1545
1546 1546 try:
1547 1547
1548 1548 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1549 1549
1550 1550 Session().commit()
1551 1551 return {
1552 1552 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1553 1553 perm.permission_name, user.username, repo.repo_name
1554 1554 ),
1555 1555 'success': True
1556 1556 }
1557 1557 except Exception:
1558 1558 log.exception(
1559 1559 "Exception occurred while trying edit permissions for repo")
1560 1560 raise JSONRPCError(
1561 1561 'failed to edit permission for user: `%s` in repo: `%s`' % (
1562 1562 userid, repoid
1563 1563 )
1564 1564 )
1565 1565
1566 1566
1567 1567 @jsonrpc_method()
1568 1568 def revoke_user_permission(request, apiuser, repoid, userid):
1569 1569 """
1570 1570 Revoke permission for a user on the specified repository.
1571 1571
1572 1572 This command can only be run using an |authtoken| with admin
1573 1573 permissions on the |repo|.
1574 1574
1575 1575 :param apiuser: This is filled automatically from the |authtoken|.
1576 1576 :type apiuser: AuthUser
1577 1577 :param repoid: Set the repository name or repository ID.
1578 1578 :type repoid: str or int
1579 1579 :param userid: Set the user name of revoked user.
1580 1580 :type userid: str or int
1581 1581
1582 1582 Example error output:
1583 1583
1584 1584 .. code-block:: bash
1585 1585
1586 1586 id : <id_given_in_input>
1587 1587 result: {
1588 1588 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1589 1589 "success": true
1590 1590 }
1591 1591 error: null
1592 1592 """
1593 1593
1594 1594 repo = get_repo_or_error(repoid)
1595 1595 user = get_user_or_error(userid)
1596 1596 if not has_superadmin_permission(apiuser):
1597 1597 _perms = ('repository.admin',)
1598 1598 validate_repo_permissions(apiuser, repoid, repo, _perms)
1599 1599
1600 1600 try:
1601 1601 RepoModel().revoke_user_permission(repo=repo, user=user)
1602 1602 Session().commit()
1603 1603 return {
1604 1604 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1605 1605 user.username, repo.repo_name
1606 1606 ),
1607 1607 'success': True
1608 1608 }
1609 1609 except Exception:
1610 1610 log.exception(
1611 1611 "Exception occurred while trying revoke permissions to repo")
1612 1612 raise JSONRPCError(
1613 1613 'failed to edit permission for user: `%s` in repo: `%s`' % (
1614 1614 userid, repoid
1615 1615 )
1616 1616 )
1617 1617
1618 1618
1619 1619 @jsonrpc_method()
1620 1620 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1621 1621 """
1622 1622 Grant permission for a user group on the specified repository,
1623 1623 or update existing permissions.
1624 1624
1625 1625 This command can only be run using an |authtoken| with admin
1626 1626 permissions on the |repo|.
1627 1627
1628 1628 :param apiuser: This is filled automatically from the |authtoken|.
1629 1629 :type apiuser: AuthUser
1630 1630 :param repoid: Set the repository name or repository ID.
1631 1631 :type repoid: str or int
1632 1632 :param usergroupid: Specify the ID of the user group.
1633 1633 :type usergroupid: str or int
1634 1634 :param perm: Set the user group permissions using the following
1635 1635 format: (repository.(none|read|write|admin))
1636 1636 :type perm: str
1637 1637
1638 1638 Example output:
1639 1639
1640 1640 .. code-block:: bash
1641 1641
1642 1642 id : <id_given_in_input>
1643 1643 result : {
1644 1644 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1645 1645 "success": true
1646 1646
1647 1647 }
1648 1648 error : null
1649 1649
1650 1650 Example error output:
1651 1651
1652 1652 .. code-block:: bash
1653 1653
1654 1654 id : <id_given_in_input>
1655 1655 result : null
1656 1656 error : {
1657 1657 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1658 1658 }
1659 1659
1660 1660 """
1661 1661
1662 1662 repo = get_repo_or_error(repoid)
1663 1663 perm = get_perm_or_error(perm)
1664 1664 if not has_superadmin_permission(apiuser):
1665 1665 _perms = ('repository.admin',)
1666 1666 validate_repo_permissions(apiuser, repoid, repo, _perms)
1667 1667
1668 1668 user_group = get_user_group_or_error(usergroupid)
1669 1669 if not has_superadmin_permission(apiuser):
1670 1670 # check if we have at least read permission for this user group !
1671 1671 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1672 1672 if not HasUserGroupPermissionAnyApi(*_perms)(
1673 1673 user=apiuser, user_group_name=user_group.users_group_name):
1674 1674 raise JSONRPCError(
1675 1675 'user group `%s` does not exist' % (usergroupid,))
1676 1676
1677 1677 try:
1678 1678 RepoModel().grant_user_group_permission(
1679 1679 repo=repo, group_name=user_group, perm=perm)
1680 1680
1681 1681 Session().commit()
1682 1682 return {
1683 1683 'msg': 'Granted perm: `%s` for user group: `%s` in '
1684 1684 'repo: `%s`' % (
1685 1685 perm.permission_name, user_group.users_group_name,
1686 1686 repo.repo_name
1687 1687 ),
1688 1688 'success': True
1689 1689 }
1690 1690 except Exception:
1691 1691 log.exception(
1692 1692 "Exception occurred while trying change permission on repo")
1693 1693 raise JSONRPCError(
1694 1694 'failed to edit permission for user group: `%s` in '
1695 1695 'repo: `%s`' % (
1696 1696 usergroupid, repo.repo_name
1697 1697 )
1698 1698 )
1699 1699
1700 1700
1701 1701 @jsonrpc_method()
1702 1702 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1703 1703 """
1704 1704 Revoke the permissions of a user group on a given repository.
1705 1705
1706 1706 This command can only be run using an |authtoken| with admin
1707 1707 permissions on the |repo|.
1708 1708
1709 1709 :param apiuser: This is filled automatically from the |authtoken|.
1710 1710 :type apiuser: AuthUser
1711 1711 :param repoid: Set the repository name or repository ID.
1712 1712 :type repoid: str or int
1713 1713 :param usergroupid: Specify the user group ID.
1714 1714 :type usergroupid: str or int
1715 1715
1716 1716 Example output:
1717 1717
1718 1718 .. code-block:: bash
1719 1719
1720 1720 id : <id_given_in_input>
1721 1721 result: {
1722 1722 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1723 1723 "success": true
1724 1724 }
1725 1725 error: null
1726 1726 """
1727 1727
1728 1728 repo = get_repo_or_error(repoid)
1729 1729 if not has_superadmin_permission(apiuser):
1730 1730 _perms = ('repository.admin',)
1731 1731 validate_repo_permissions(apiuser, repoid, repo, _perms)
1732 1732
1733 1733 user_group = get_user_group_or_error(usergroupid)
1734 1734 if not has_superadmin_permission(apiuser):
1735 1735 # check if we have at least read permission for this user group !
1736 1736 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1737 1737 if not HasUserGroupPermissionAnyApi(*_perms)(
1738 1738 user=apiuser, user_group_name=user_group.users_group_name):
1739 1739 raise JSONRPCError(
1740 1740 'user group `%s` does not exist' % (usergroupid,))
1741 1741
1742 1742 try:
1743 1743 RepoModel().revoke_user_group_permission(
1744 1744 repo=repo, group_name=user_group)
1745 1745
1746 1746 Session().commit()
1747 1747 return {
1748 1748 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1749 1749 user_group.users_group_name, repo.repo_name
1750 1750 ),
1751 1751 'success': True
1752 1752 }
1753 1753 except Exception:
1754 1754 log.exception("Exception occurred while trying revoke "
1755 1755 "user group permission on repo")
1756 1756 raise JSONRPCError(
1757 1757 'failed to edit permission for user group: `%s` in '
1758 1758 'repo: `%s`' % (
1759 1759 user_group.users_group_name, repo.repo_name
1760 1760 )
1761 1761 )
1762 1762
1763 1763
1764 1764 @jsonrpc_method()
1765 1765 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
1766 1766 """
1767 1767 Triggers a pull on the given repository from a remote location. You
1768 1768 can use this to keep remote repositories up-to-date.
1769 1769
1770 1770 This command can only be run using an |authtoken| with admin
1771 1771 rights to the specified repository. For more information,
1772 1772 see :ref:`config-token-ref`.
1773 1773
1774 1774 This command takes the following options:
1775 1775
1776 1776 :param apiuser: This is filled automatically from the |authtoken|.
1777 1777 :type apiuser: AuthUser
1778 1778 :param repoid: The repository name or repository ID.
1779 1779 :type repoid: str or int
1780 1780 :param remote_uri: Optional remote URI to pass in for pull
1781 1781 :type remote_uri: str
1782 1782
1783 1783 Example output:
1784 1784
1785 1785 .. code-block:: bash
1786 1786
1787 1787 id : <id_given_in_input>
1788 1788 result : {
1789 1789 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
1790 1790 "repository": "<repository name>"
1791 1791 }
1792 1792 error : null
1793 1793
1794 1794 Example error output:
1795 1795
1796 1796 .. code-block:: bash
1797 1797
1798 1798 id : <id_given_in_input>
1799 1799 result : null
1800 1800 error : {
1801 1801 "Unable to push changes from `<remote_url>`"
1802 1802 }
1803 1803
1804 1804 """
1805 1805
1806 1806 repo = get_repo_or_error(repoid)
1807 1807 remote_uri = Optional.extract(remote_uri)
1808 1808 remote_uri_display = remote_uri or repo.clone_uri_hidden
1809 1809 if not has_superadmin_permission(apiuser):
1810 1810 _perms = ('repository.admin',)
1811 1811 validate_repo_permissions(apiuser, repoid, repo, _perms)
1812 1812
1813 1813 try:
1814 1814 ScmModel().pull_changes(
1815 1815 repo.repo_name, apiuser.username, remote_uri=remote_uri)
1816 1816 return {
1817 1817 'msg': 'Pulled from url `%s` on repo `%s`' % (
1818 1818 remote_uri_display, repo.repo_name),
1819 1819 'repository': repo.repo_name
1820 1820 }
1821 1821 except Exception:
1822 1822 log.exception("Exception occurred while trying to "
1823 1823 "pull changes from remote location")
1824 1824 raise JSONRPCError(
1825 1825 'Unable to pull changes from `%s`' % remote_uri_display
1826 1826 )
1827 1827
1828 1828
1829 1829 @jsonrpc_method()
1830 1830 def strip(request, apiuser, repoid, revision, branch):
1831 1831 """
1832 1832 Strips the given revision from the specified repository.
1833 1833
1834 1834 * This will remove the revision and all of its decendants.
1835 1835
1836 1836 This command can only be run using an |authtoken| with admin rights to
1837 1837 the specified repository.
1838 1838
1839 1839 This command takes the following options:
1840 1840
1841 1841 :param apiuser: This is filled automatically from the |authtoken|.
1842 1842 :type apiuser: AuthUser
1843 1843 :param repoid: The repository name or repository ID.
1844 1844 :type repoid: str or int
1845 1845 :param revision: The revision you wish to strip.
1846 1846 :type revision: str
1847 1847 :param branch: The branch from which to strip the revision.
1848 1848 :type branch: str
1849 1849
1850 1850 Example output:
1851 1851
1852 1852 .. code-block:: bash
1853 1853
1854 1854 id : <id_given_in_input>
1855 1855 result : {
1856 1856 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1857 1857 "repository": "<repository name>"
1858 1858 }
1859 1859 error : null
1860 1860
1861 1861 Example error output:
1862 1862
1863 1863 .. code-block:: bash
1864 1864
1865 1865 id : <id_given_in_input>
1866 1866 result : null
1867 1867 error : {
1868 1868 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1869 1869 }
1870 1870
1871 1871 """
1872 1872
1873 1873 repo = get_repo_or_error(repoid)
1874 1874 if not has_superadmin_permission(apiuser):
1875 1875 _perms = ('repository.admin',)
1876 1876 validate_repo_permissions(apiuser, repoid, repo, _perms)
1877 1877
1878 1878 try:
1879 1879 ScmModel().strip(repo, revision, branch)
1880 1880 audit_logger.store_api(
1881 1881 'repo.commit.strip', action_data={'commit_id': revision},
1882 1882 repo=repo,
1883 1883 user=apiuser, commit=True)
1884 1884
1885 1885 return {
1886 1886 'msg': 'Stripped commit %s from repo `%s`' % (
1887 1887 revision, repo.repo_name),
1888 1888 'repository': repo.repo_name
1889 1889 }
1890 1890 except Exception:
1891 1891 log.exception("Exception while trying to strip")
1892 1892 raise JSONRPCError(
1893 1893 'Unable to strip commit %s from repo `%s`' % (
1894 1894 revision, repo.repo_name)
1895 1895 )
1896 1896
1897 1897
1898 1898 @jsonrpc_method()
1899 1899 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1900 1900 """
1901 1901 Returns all settings for a repository. If key is given it only returns the
1902 1902 setting identified by the key or null.
1903 1903
1904 1904 :param apiuser: This is filled automatically from the |authtoken|.
1905 1905 :type apiuser: AuthUser
1906 1906 :param repoid: The repository name or repository id.
1907 1907 :type repoid: str or int
1908 1908 :param key: Key of the setting to return.
1909 1909 :type: key: Optional(str)
1910 1910
1911 1911 Example output:
1912 1912
1913 1913 .. code-block:: bash
1914 1914
1915 1915 {
1916 1916 "error": null,
1917 1917 "id": 237,
1918 1918 "result": {
1919 1919 "extensions_largefiles": true,
1920 1920 "extensions_evolve": true,
1921 1921 "hooks_changegroup_push_logger": true,
1922 1922 "hooks_changegroup_repo_size": false,
1923 1923 "hooks_outgoing_pull_logger": true,
1924 1924 "phases_publish": "True",
1925 1925 "rhodecode_hg_use_rebase_for_merging": true,
1926 1926 "rhodecode_pr_merge_enabled": true,
1927 1927 "rhodecode_use_outdated_comments": true
1928 1928 }
1929 1929 }
1930 1930 """
1931 1931
1932 1932 # Restrict access to this api method to admins only.
1933 1933 if not has_superadmin_permission(apiuser):
1934 1934 raise JSONRPCForbidden()
1935 1935
1936 1936 try:
1937 1937 repo = get_repo_or_error(repoid)
1938 1938 settings_model = VcsSettingsModel(repo=repo)
1939 1939 settings = settings_model.get_global_settings()
1940 1940 settings.update(settings_model.get_repo_settings())
1941 1941
1942 1942 # If only a single setting is requested fetch it from all settings.
1943 1943 key = Optional.extract(key)
1944 1944 if key is not None:
1945 1945 settings = settings.get(key, None)
1946 1946 except Exception:
1947 1947 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1948 1948 log.exception(msg)
1949 1949 raise JSONRPCError(msg)
1950 1950
1951 1951 return settings
1952 1952
1953 1953
1954 1954 @jsonrpc_method()
1955 1955 def set_repo_settings(request, apiuser, repoid, settings):
1956 1956 """
1957 1957 Update repository settings. Returns true on success.
1958 1958
1959 1959 :param apiuser: This is filled automatically from the |authtoken|.
1960 1960 :type apiuser: AuthUser
1961 1961 :param repoid: The repository name or repository id.
1962 1962 :type repoid: str or int
1963 1963 :param settings: The new settings for the repository.
1964 1964 :type: settings: dict
1965 1965
1966 1966 Example output:
1967 1967
1968 1968 .. code-block:: bash
1969 1969
1970 1970 {
1971 1971 "error": null,
1972 1972 "id": 237,
1973 1973 "result": true
1974 1974 }
1975 1975 """
1976 1976 # Restrict access to this api method to admins only.
1977 1977 if not has_superadmin_permission(apiuser):
1978 1978 raise JSONRPCForbidden()
1979 1979
1980 1980 if type(settings) is not dict:
1981 1981 raise JSONRPCError('Settings have to be a JSON Object.')
1982 1982
1983 1983 try:
1984 1984 settings_model = VcsSettingsModel(repo=repoid)
1985 1985
1986 1986 # Merge global, repo and incoming settings.
1987 1987 new_settings = settings_model.get_global_settings()
1988 1988 new_settings.update(settings_model.get_repo_settings())
1989 1989 new_settings.update(settings)
1990 1990
1991 1991 # Update the settings.
1992 1992 inherit_global_settings = new_settings.get(
1993 1993 'inherit_global_settings', False)
1994 1994 settings_model.create_or_update_repo_settings(
1995 1995 new_settings, inherit_global_settings=inherit_global_settings)
1996 1996 Session().commit()
1997 1997 except Exception:
1998 1998 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1999 1999 log.exception(msg)
2000 2000 raise JSONRPCError(msg)
2001 2001
2002 2002 # Indicate success.
2003 2003 return True
2004 2004
2005 2005
2006 2006 @jsonrpc_method()
2007 2007 def maintenance(request, apiuser, repoid):
2008 2008 """
2009 2009 Triggers a maintenance on the given repository.
2010 2010
2011 2011 This command can only be run using an |authtoken| with admin
2012 2012 rights to the specified repository. For more information,
2013 2013 see :ref:`config-token-ref`.
2014 2014
2015 2015 This command takes the following options:
2016 2016
2017 2017 :param apiuser: This is filled automatically from the |authtoken|.
2018 2018 :type apiuser: AuthUser
2019 2019 :param repoid: The repository name or repository ID.
2020 2020 :type repoid: str or int
2021 2021
2022 2022 Example output:
2023 2023
2024 2024 .. code-block:: bash
2025 2025
2026 2026 id : <id_given_in_input>
2027 2027 result : {
2028 2028 "msg": "executed maintenance command",
2029 2029 "executed_actions": [
2030 2030 <action_message>, <action_message2>...
2031 2031 ],
2032 2032 "repository": "<repository name>"
2033 2033 }
2034 2034 error : null
2035 2035
2036 2036 Example error output:
2037 2037
2038 2038 .. code-block:: bash
2039 2039
2040 2040 id : <id_given_in_input>
2041 2041 result : null
2042 2042 error : {
2043 2043 "Unable to execute maintenance on `<reponame>`"
2044 2044 }
2045 2045
2046 2046 """
2047 2047
2048 2048 repo = get_repo_or_error(repoid)
2049 2049 if not has_superadmin_permission(apiuser):
2050 2050 _perms = ('repository.admin',)
2051 2051 validate_repo_permissions(apiuser, repoid, repo, _perms)
2052 2052
2053 2053 try:
2054 2054 maintenance = repo_maintenance.RepoMaintenance()
2055 2055 executed_actions = maintenance.execute(repo)
2056 2056
2057 2057 return {
2058 2058 'msg': 'executed maintenance command',
2059 2059 'executed_actions': executed_actions,
2060 2060 'repository': repo.repo_name
2061 2061 }
2062 2062 except Exception:
2063 2063 log.exception("Exception occurred while trying to run maintenance")
2064 2064 raise JSONRPCError(
2065 2065 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,678 +1,679 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 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 time
22 22 import logging
23 23 import operator
24 24
25 25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
26 26
27 27 from rhodecode.lib import helpers as h, diffs
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 from rhodecode.lib.utils2 import (
29 StrictAttributeDict, safe_int, datetime_to_time, safe_unicode)
29 30 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 31 from rhodecode.model import repo
31 32 from rhodecode.model import repo_group
32 33 from rhodecode.model import user_group
33 34 from rhodecode.model import user
34 35 from rhodecode.model.db import User
35 36 from rhodecode.model.scm import ScmModel
36 37 from rhodecode.model.settings import VcsSettingsModel
37 38
38 39 log = logging.getLogger(__name__)
39 40
40 41
41 42 ADMIN_PREFIX = '/_admin'
42 43 STATIC_FILE_PREFIX = '/_static'
43 44
44 45 URL_NAME_REQUIREMENTS = {
45 46 # group name can have a slash in them, but they must not end with a slash
46 47 'group_name': r'.*?[^/]',
47 48 'repo_group_name': r'.*?[^/]',
48 49 # repo names can have a slash in them, but they must not end with a slash
49 50 'repo_name': r'.*?[^/]',
50 51 # file path eats up everything at the end
51 52 'f_path': r'.*',
52 53 # reference types
53 54 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
54 55 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
55 56 }
56 57
57 58
58 59 def add_route_with_slash(config,name, pattern, **kw):
59 60 config.add_route(name, pattern, **kw)
60 61 if not pattern.endswith('/'):
61 62 config.add_route(name + '_slash', pattern + '/', **kw)
62 63
63 64
64 65 def add_route_requirements(route_path, requirements=None):
65 66 """
66 67 Adds regex requirements to pyramid routes using a mapping dict
67 68 e.g::
68 69 add_route_requirements('{repo_name}/settings')
69 70 """
70 71 requirements = requirements or URL_NAME_REQUIREMENTS
71 72 for key, regex in requirements.items():
72 73 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
73 74 return route_path
74 75
75 76
76 77 def get_format_ref_id(repo):
77 78 """Returns a `repo` specific reference formatter function"""
78 79 if h.is_svn(repo):
79 80 return _format_ref_id_svn
80 81 else:
81 82 return _format_ref_id
82 83
83 84
84 85 def _format_ref_id(name, raw_id):
85 86 """Default formatting of a given reference `name`"""
86 87 return name
87 88
88 89
89 90 def _format_ref_id_svn(name, raw_id):
90 91 """Special way of formatting a reference for Subversion including path"""
91 92 return '%s@%s' % (name, raw_id)
92 93
93 94
94 95 class TemplateArgs(StrictAttributeDict):
95 96 pass
96 97
97 98
98 99 class BaseAppView(object):
99 100
100 101 def __init__(self, context, request):
101 102 self.request = request
102 103 self.context = context
103 104 self.session = request.session
104 105 if not hasattr(request, 'user'):
105 106 # NOTE(marcink): edge case, we ended up in matched route
106 107 # but probably of web-app context, e.g API CALL/VCS CALL
107 108 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
108 109 log.warning('Unable to process request `%s` in this scope', request)
109 110 raise HTTPBadRequest()
110 111
111 112 self._rhodecode_user = request.user # auth user
112 113 self._rhodecode_db_user = self._rhodecode_user.get_instance()
113 114 self._maybe_needs_password_change(
114 115 request.matched_route.name, self._rhodecode_db_user)
115 116
116 117 def _maybe_needs_password_change(self, view_name, user_obj):
117 118 log.debug('Checking if user %s needs password change on view %s',
118 119 user_obj, view_name)
119 120 skip_user_views = [
120 121 'logout', 'login',
121 122 'my_account_password', 'my_account_password_update'
122 123 ]
123 124
124 125 if not user_obj:
125 126 return
126 127
127 128 if user_obj.username == User.DEFAULT_USER:
128 129 return
129 130
130 131 now = time.time()
131 132 should_change = user_obj.user_data.get('force_password_change')
132 133 change_after = safe_int(should_change) or 0
133 134 if should_change and now > change_after:
134 135 log.debug('User %s requires password change', user_obj)
135 136 h.flash('You are required to change your password', 'warning',
136 137 ignore_duplicate=True)
137 138
138 139 if view_name not in skip_user_views:
139 140 raise HTTPFound(
140 141 self.request.route_path('my_account_password'))
141 142
142 143 def _log_creation_exception(self, e, repo_name):
143 144 _ = self.request.translate
144 145 reason = None
145 146 if len(e.args) == 2:
146 147 reason = e.args[1]
147 148
148 149 if reason == 'INVALID_CERTIFICATE':
149 150 log.exception(
150 151 'Exception creating a repository: invalid certificate')
151 152 msg = (_('Error creating repository %s: invalid certificate')
152 153 % repo_name)
153 154 else:
154 155 log.exception("Exception creating a repository")
155 156 msg = (_('Error creating repository %s')
156 157 % repo_name)
157 158 return msg
158 159
159 160 def _get_local_tmpl_context(self, include_app_defaults=True):
160 161 c = TemplateArgs()
161 162 c.auth_user = self.request.user
162 163 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
163 164 c.rhodecode_user = self.request.user
164 165
165 166 if include_app_defaults:
166 167 from rhodecode.lib.base import attach_context_attributes
167 168 attach_context_attributes(c, self.request, self.request.user.user_id)
168 169
169 170 return c
170 171
171 172 def _get_template_context(self, tmpl_args, **kwargs):
172 173
173 174 local_tmpl_args = {
174 175 'defaults': {},
175 176 'errors': {},
176 177 'c': tmpl_args
177 178 }
178 179 local_tmpl_args.update(kwargs)
179 180 return local_tmpl_args
180 181
181 182 def load_default_context(self):
182 183 """
183 184 example:
184 185
185 186 def load_default_context(self):
186 187 c = self._get_local_tmpl_context()
187 188 c.custom_var = 'foobar'
188 189
189 190 return c
190 191 """
191 192 raise NotImplementedError('Needs implementation in view class')
192 193
193 194
194 195 class RepoAppView(BaseAppView):
195 196
196 197 def __init__(self, context, request):
197 198 super(RepoAppView, self).__init__(context, request)
198 199 self.db_repo = request.db_repo
199 200 self.db_repo_name = self.db_repo.repo_name
200 201 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
201 202
202 203 def _handle_missing_requirements(self, error):
203 204 log.error(
204 205 'Requirements are missing for repository %s: %s',
205 self.db_repo_name, error.message)
206 self.db_repo_name, safe_unicode(error))
206 207
207 208 def _get_local_tmpl_context(self, include_app_defaults=True):
208 209 _ = self.request.translate
209 210 c = super(RepoAppView, self)._get_local_tmpl_context(
210 211 include_app_defaults=include_app_defaults)
211 212
212 213 # register common vars for this type of view
213 214 c.rhodecode_db_repo = self.db_repo
214 215 c.repo_name = self.db_repo_name
215 216 c.repository_pull_requests = self.db_repo_pull_requests
216 217 self.path_filter = PathFilter(None)
217 218
218 219 c.repository_requirements_missing = {}
219 220 try:
220 221 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
221 222 if self.rhodecode_vcs_repo:
222 223 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
223 224 c.auth_user.username)
224 225 self.path_filter = PathFilter(path_perms)
225 226 except RepositoryRequirementError as e:
226 227 c.repository_requirements_missing = {'error': str(e)}
227 228 self._handle_missing_requirements(e)
228 229 self.rhodecode_vcs_repo = None
229 230
230 231 c.path_filter = self.path_filter # used by atom_feed_entry.mako
231 232
232 233 if self.rhodecode_vcs_repo is None:
233 234 # unable to fetch this repo as vcs instance, report back to user
234 235 h.flash(_(
235 236 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
236 237 "Please check if it exist, or is not damaged.") %
237 238 {'repo_name': c.repo_name},
238 239 category='error', ignore_duplicate=True)
239 240 if c.repository_requirements_missing:
240 241 route = self.request.matched_route.name
241 242 if route.startswith(('edit_repo', 'repo_summary')):
242 243 # allow summary and edit repo on missing requirements
243 244 return c
244 245
245 246 raise HTTPFound(
246 247 h.route_path('repo_summary', repo_name=self.db_repo_name))
247 248
248 249 else: # redirect if we don't show missing requirements
249 250 raise HTTPFound(h.route_path('home'))
250 251
251 252 return c
252 253
253 254 def _get_f_path_unchecked(self, matchdict, default=None):
254 255 """
255 256 Should only be used by redirects, everything else should call _get_f_path
256 257 """
257 258 f_path = matchdict.get('f_path')
258 259 if f_path:
259 260 # fix for multiple initial slashes that causes errors for GIT
260 261 return f_path.lstrip('/')
261 262
262 263 return default
263 264
264 265 def _get_f_path(self, matchdict, default=None):
265 266 f_path_match = self._get_f_path_unchecked(matchdict, default)
266 267 return self.path_filter.assert_path_permissions(f_path_match)
267 268
268 269 def _get_general_setting(self, target_repo, settings_key, default=False):
269 270 settings_model = VcsSettingsModel(repo=target_repo)
270 271 settings = settings_model.get_general_settings()
271 272 return settings.get(settings_key, default)
272 273
273 274
274 275 class PathFilter(object):
275 276
276 277 # Expects and instance of BasePathPermissionChecker or None
277 278 def __init__(self, permission_checker):
278 279 self.permission_checker = permission_checker
279 280
280 281 def assert_path_permissions(self, path):
281 282 if path and self.permission_checker and not self.permission_checker.has_access(path):
282 283 raise HTTPForbidden()
283 284 return path
284 285
285 286 def filter_patchset(self, patchset):
286 287 if not self.permission_checker or not patchset:
287 288 return patchset, False
288 289 had_filtered = False
289 290 filtered_patchset = []
290 291 for patch in patchset:
291 292 filename = patch.get('filename', None)
292 293 if not filename or self.permission_checker.has_access(filename):
293 294 filtered_patchset.append(patch)
294 295 else:
295 296 had_filtered = True
296 297 if had_filtered:
297 298 if isinstance(patchset, diffs.LimitedDiffContainer):
298 299 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
299 300 return filtered_patchset, True
300 301 else:
301 302 return patchset, False
302 303
303 304 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
304 305 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
305 306 result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref)
306 307 result.has_hidden_changes = has_hidden_changes
307 308 return result
308 309
309 310 def get_raw_patch(self, diff_processor):
310 311 if self.permission_checker is None:
311 312 return diff_processor.as_raw()
312 313 elif self.permission_checker.has_full_access:
313 314 return diff_processor.as_raw()
314 315 else:
315 316 return '# Repository has user-specific filters, raw patch generation is disabled.'
316 317
317 318 @property
318 319 def is_enabled(self):
319 320 return self.permission_checker is not None
320 321
321 322
322 323 class RepoGroupAppView(BaseAppView):
323 324 def __init__(self, context, request):
324 325 super(RepoGroupAppView, self).__init__(context, request)
325 326 self.db_repo_group = request.db_repo_group
326 327 self.db_repo_group_name = self.db_repo_group.group_name
327 328
328 329 def _revoke_perms_on_yourself(self, form_result):
329 330 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
330 331 form_result['perm_updates'])
331 332 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
332 333 form_result['perm_additions'])
333 334 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
334 335 form_result['perm_deletions'])
335 336 admin_perm = 'group.admin'
336 337 if _updates and _updates[0][1] != admin_perm or \
337 338 _additions and _additions[0][1] != admin_perm or \
338 339 _deletions and _deletions[0][1] != admin_perm:
339 340 return True
340 341 return False
341 342
342 343
343 344 class UserGroupAppView(BaseAppView):
344 345 def __init__(self, context, request):
345 346 super(UserGroupAppView, self).__init__(context, request)
346 347 self.db_user_group = request.db_user_group
347 348 self.db_user_group_name = self.db_user_group.users_group_name
348 349
349 350
350 351 class UserAppView(BaseAppView):
351 352 def __init__(self, context, request):
352 353 super(UserAppView, self).__init__(context, request)
353 354 self.db_user = request.db_user
354 355 self.db_user_id = self.db_user.user_id
355 356
356 357 _ = self.request.translate
357 358 if not request.db_user_supports_default:
358 359 if self.db_user.username == User.DEFAULT_USER:
359 360 h.flash(_("Editing user `{}` is disabled.".format(
360 361 User.DEFAULT_USER)), category='warning')
361 362 raise HTTPFound(h.route_path('users'))
362 363
363 364
364 365 class DataGridAppView(object):
365 366 """
366 367 Common class to have re-usable grid rendering components
367 368 """
368 369
369 370 def _extract_ordering(self, request, column_map=None):
370 371 column_map = column_map or {}
371 372 column_index = safe_int(request.GET.get('order[0][column]'))
372 373 order_dir = request.GET.get(
373 374 'order[0][dir]', 'desc')
374 375 order_by = request.GET.get(
375 376 'columns[%s][data][sort]' % column_index, 'name_raw')
376 377
377 378 # translate datatable to DB columns
378 379 order_by = column_map.get(order_by) or order_by
379 380
380 381 search_q = request.GET.get('search[value]')
381 382 return search_q, order_by, order_dir
382 383
383 384 def _extract_chunk(self, request):
384 385 start = safe_int(request.GET.get('start'), 0)
385 386 length = safe_int(request.GET.get('length'), 25)
386 387 draw = safe_int(request.GET.get('draw'))
387 388 return draw, start, length
388 389
389 390 def _get_order_col(self, order_by, model):
390 391 if isinstance(order_by, basestring):
391 392 try:
392 393 return operator.attrgetter(order_by)(model)
393 394 except AttributeError:
394 395 return None
395 396 else:
396 397 return order_by
397 398
398 399
399 400 class BaseReferencesView(RepoAppView):
400 401 """
401 402 Base for reference view for branches, tags and bookmarks.
402 403 """
403 404 def load_default_context(self):
404 405 c = self._get_local_tmpl_context()
405 406
406 407
407 408 return c
408 409
409 410 def load_refs_context(self, ref_items, partials_template):
410 411 _render = self.request.get_partial_renderer(partials_template)
411 412 pre_load = ["author", "date", "message"]
412 413
413 414 is_svn = h.is_svn(self.rhodecode_vcs_repo)
414 415 is_hg = h.is_hg(self.rhodecode_vcs_repo)
415 416
416 417 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
417 418
418 419 closed_refs = {}
419 420 if is_hg:
420 421 closed_refs = self.rhodecode_vcs_repo.branches_closed
421 422
422 423 data = []
423 424 for ref_name, commit_id in ref_items:
424 425 commit = self.rhodecode_vcs_repo.get_commit(
425 426 commit_id=commit_id, pre_load=pre_load)
426 427 closed = ref_name in closed_refs
427 428
428 429 # TODO: johbo: Unify generation of reference links
429 430 use_commit_id = '/' in ref_name or is_svn
430 431
431 432 if use_commit_id:
432 433 files_url = h.route_path(
433 434 'repo_files',
434 435 repo_name=self.db_repo_name,
435 436 f_path=ref_name if is_svn else '',
436 437 commit_id=commit_id)
437 438
438 439 else:
439 440 files_url = h.route_path(
440 441 'repo_files',
441 442 repo_name=self.db_repo_name,
442 443 f_path=ref_name if is_svn else '',
443 444 commit_id=ref_name,
444 445 _query=dict(at=ref_name))
445 446
446 447 data.append({
447 448 "name": _render('name', ref_name, files_url, closed),
448 449 "name_raw": ref_name,
449 450 "date": _render('date', commit.date),
450 451 "date_raw": datetime_to_time(commit.date),
451 452 "author": _render('author', commit.author),
452 453 "commit": _render(
453 454 'commit', commit.message, commit.raw_id, commit.idx),
454 455 "commit_raw": commit.idx,
455 456 "compare": _render(
456 457 'compare', format_ref_id(ref_name, commit.raw_id)),
457 458 })
458 459
459 460 return data
460 461
461 462
462 463 class RepoRoutePredicate(object):
463 464 def __init__(self, val, config):
464 465 self.val = val
465 466
466 467 def text(self):
467 468 return 'repo_route = %s' % self.val
468 469
469 470 phash = text
470 471
471 472 def __call__(self, info, request):
472 473 if hasattr(request, 'vcs_call'):
473 474 # skip vcs calls
474 475 return
475 476
476 477 repo_name = info['match']['repo_name']
477 478 repo_model = repo.RepoModel()
478 479
479 480 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
480 481
481 482 def redirect_if_creating(route_info, db_repo):
482 483 skip_views = ['edit_repo_advanced_delete']
483 484 route = route_info['route']
484 485 # we should skip delete view so we can actually "remove" repositories
485 486 # if they get stuck in creating state.
486 487 if route.name in skip_views:
487 488 return
488 489
489 490 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
490 491 repo_creating_url = request.route_path(
491 492 'repo_creating', repo_name=db_repo.repo_name)
492 493 raise HTTPFound(repo_creating_url)
493 494
494 495 if by_name_match:
495 496 # register this as request object we can re-use later
496 497 request.db_repo = by_name_match
497 498 redirect_if_creating(info, by_name_match)
498 499 return True
499 500
500 501 by_id_match = repo_model.get_repo_by_id(repo_name)
501 502 if by_id_match:
502 503 request.db_repo = by_id_match
503 504 redirect_if_creating(info, by_id_match)
504 505 return True
505 506
506 507 return False
507 508
508 509
509 510 class RepoForbidArchivedRoutePredicate(object):
510 511 def __init__(self, val, config):
511 512 self.val = val
512 513
513 514 def text(self):
514 515 return 'repo_forbid_archived = %s' % self.val
515 516
516 517 phash = text
517 518
518 519 def __call__(self, info, request):
519 520 _ = request.translate
520 521 rhodecode_db_repo = request.db_repo
521 522
522 523 log.debug(
523 524 '%s checking if archived flag for repo for %s',
524 525 self.__class__.__name__, rhodecode_db_repo.repo_name)
525 526
526 527 if rhodecode_db_repo.archived:
527 528 log.warning('Current view is not supported for archived repo:%s',
528 529 rhodecode_db_repo.repo_name)
529 530
530 531 h.flash(
531 532 h.literal(_('Action not supported for archived repository.')),
532 533 category='warning')
533 534 summary_url = request.route_path(
534 535 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
535 536 raise HTTPFound(summary_url)
536 537 return True
537 538
538 539
539 540 class RepoTypeRoutePredicate(object):
540 541 def __init__(self, val, config):
541 542 self.val = val or ['hg', 'git', 'svn']
542 543
543 544 def text(self):
544 545 return 'repo_accepted_type = %s' % self.val
545 546
546 547 phash = text
547 548
548 549 def __call__(self, info, request):
549 550 if hasattr(request, 'vcs_call'):
550 551 # skip vcs calls
551 552 return
552 553
553 554 rhodecode_db_repo = request.db_repo
554 555
555 556 log.debug(
556 557 '%s checking repo type for %s in %s',
557 558 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
558 559
559 560 if rhodecode_db_repo.repo_type in self.val:
560 561 return True
561 562 else:
562 563 log.warning('Current view is not supported for repo type:%s',
563 564 rhodecode_db_repo.repo_type)
564 565 return False
565 566
566 567
567 568 class RepoGroupRoutePredicate(object):
568 569 def __init__(self, val, config):
569 570 self.val = val
570 571
571 572 def text(self):
572 573 return 'repo_group_route = %s' % self.val
573 574
574 575 phash = text
575 576
576 577 def __call__(self, info, request):
577 578 if hasattr(request, 'vcs_call'):
578 579 # skip vcs calls
579 580 return
580 581
581 582 repo_group_name = info['match']['repo_group_name']
582 583 repo_group_model = repo_group.RepoGroupModel()
583 584 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
584 585
585 586 if by_name_match:
586 587 # register this as request object we can re-use later
587 588 request.db_repo_group = by_name_match
588 589 return True
589 590
590 591 return False
591 592
592 593
593 594 class UserGroupRoutePredicate(object):
594 595 def __init__(self, val, config):
595 596 self.val = val
596 597
597 598 def text(self):
598 599 return 'user_group_route = %s' % self.val
599 600
600 601 phash = text
601 602
602 603 def __call__(self, info, request):
603 604 if hasattr(request, 'vcs_call'):
604 605 # skip vcs calls
605 606 return
606 607
607 608 user_group_id = info['match']['user_group_id']
608 609 user_group_model = user_group.UserGroup()
609 610 by_id_match = user_group_model.get(user_group_id, cache=False)
610 611
611 612 if by_id_match:
612 613 # register this as request object we can re-use later
613 614 request.db_user_group = by_id_match
614 615 return True
615 616
616 617 return False
617 618
618 619
619 620 class UserRoutePredicateBase(object):
620 621 supports_default = None
621 622
622 623 def __init__(self, val, config):
623 624 self.val = val
624 625
625 626 def text(self):
626 627 raise NotImplementedError()
627 628
628 629 def __call__(self, info, request):
629 630 if hasattr(request, 'vcs_call'):
630 631 # skip vcs calls
631 632 return
632 633
633 634 user_id = info['match']['user_id']
634 635 user_model = user.User()
635 636 by_id_match = user_model.get(user_id, cache=False)
636 637
637 638 if by_id_match:
638 639 # register this as request object we can re-use later
639 640 request.db_user = by_id_match
640 641 request.db_user_supports_default = self.supports_default
641 642 return True
642 643
643 644 return False
644 645
645 646
646 647 class UserRoutePredicate(UserRoutePredicateBase):
647 648 supports_default = False
648 649
649 650 def text(self):
650 651 return 'user_route = %s' % self.val
651 652
652 653 phash = text
653 654
654 655
655 656 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
656 657 supports_default = True
657 658
658 659 def text(self):
659 660 return 'user_with_default_route = %s' % self.val
660 661
661 662 phash = text
662 663
663 664
664 665 def includeme(config):
665 666 config.add_route_predicate(
666 667 'repo_route', RepoRoutePredicate)
667 668 config.add_route_predicate(
668 669 'repo_accepted_types', RepoTypeRoutePredicate)
669 670 config.add_route_predicate(
670 671 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
671 672 config.add_route_predicate(
672 673 'repo_group_route', RepoGroupRoutePredicate)
673 674 config.add_route_predicate(
674 675 'user_group_route', UserGroupRoutePredicate)
675 676 config.add_route_predicate(
676 677 'user_route_with_default', UserRouteWithDefaultPredicate)
677 678 config.add_route_predicate(
678 679 'user_route', UserRoutePredicate)
@@ -1,54 +1,54 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 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 pytest
22 22
23 23
24 24 class TestSSHWrapper(object):
25 25
26 26 def test_serve_raises_an_exception_when_vcs_is_not_recognized(self, ssh_wrapper):
27 27 with pytest.raises(Exception) as exc_info:
28 28 ssh_wrapper.serve(
29 29 vcs='microsoft-tfs', repo='test-repo', mode=None, user='test',
30 30 permissions={}, branch_permissions={})
31 assert exc_info.value.message == 'Unrecognised VCS: microsoft-tfs'
31 assert str(exc_info.value) == 'Unrecognised VCS: microsoft-tfs'
32 32
33 33 def test_parse_config(self, ssh_wrapper):
34 34 config = ssh_wrapper.parse_config(ssh_wrapper.ini_path)
35 35 assert config
36 36
37 37 def test_get_connection_info(self, ssh_wrapper):
38 38 conn_info = ssh_wrapper.get_connection_info()
39 39 assert {'client_ip': '127.0.0.1',
40 40 'client_port': '22',
41 41 'server_ip': '10.0.0.1',
42 42 'server_port': '443'} == conn_info
43 43
44 44 @pytest.mark.parametrize('command, vcs', [
45 45 ('xxx', None),
46 46 ('svnserve -t', 'svn'),
47 47 ('hg -R repo serve --stdio', 'hg'),
48 48 ('git-receive-pack \'repo.git\'', 'git'),
49 49
50 50 ])
51 51 def test_get_repo_details(self, ssh_wrapper, command, vcs):
52 52 ssh_wrapper.command = command
53 53 vcs_type, repo_name, mode = ssh_wrapper.get_repo_details(mode='auto')
54 54 assert vcs_type == vcs
General Comments 0
You need to be logged in to leave comments. Login now