##// END OF EJS Templates
cache: bump file-tree caches to next iteration
marcink -
r4036:6ca2afaa default
parent child Browse files
Show More
@@ -1,2332 +1,2332 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import time
23 23
24 24 import rhodecode
25 25 from rhodecode.api import (
26 26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 27 from rhodecode.api.utils import (
28 28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 31 validate_set_owner_permissions)
32 32 from rhodecode.lib import audit_logger, rc_cache
33 33 from rhodecode.lib import repo_maintenance
34 34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 35 from rhodecode.lib.celerylib.utils import get_task_id
36 36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int
37 37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
39 39 from rhodecode.lib.vcs import RepositoryError
40 40 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
41 41 from rhodecode.model.changeset_status import ChangesetStatusModel
42 42 from rhodecode.model.comment import CommentsModel
43 43 from rhodecode.model.db import (
44 44 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
45 45 ChangesetComment)
46 46 from rhodecode.model.permission import PermissionModel
47 47 from rhodecode.model.repo import RepoModel
48 48 from rhodecode.model.scm import ScmModel, RepoList
49 49 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
50 50 from rhodecode.model import validation_schema
51 51 from rhodecode.model.validation_schema.schemas import repo_schema
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 @jsonrpc_method()
57 57 def get_repo(request, apiuser, repoid, cache=Optional(True)):
58 58 """
59 59 Gets an existing repository by its name or repository_id.
60 60
61 61 The members section so the output returns users groups or users
62 62 associated with that repository.
63 63
64 64 This command can only be run using an |authtoken| with admin rights,
65 65 or users with at least read rights to the |repo|.
66 66
67 67 :param apiuser: This is filled automatically from the |authtoken|.
68 68 :type apiuser: AuthUser
69 69 :param repoid: The repository name or repository id.
70 70 :type repoid: str or int
71 71 :param cache: use the cached value for last changeset
72 72 :type: cache: Optional(bool)
73 73
74 74 Example output:
75 75
76 76 .. code-block:: bash
77 77
78 78 {
79 79 "error": null,
80 80 "id": <repo_id>,
81 81 "result": {
82 82 "clone_uri": null,
83 83 "created_on": "timestamp",
84 84 "description": "repo description",
85 85 "enable_downloads": false,
86 86 "enable_locking": false,
87 87 "enable_statistics": false,
88 88 "followers": [
89 89 {
90 90 "active": true,
91 91 "admin": false,
92 92 "api_key": "****************************************",
93 93 "api_keys": [
94 94 "****************************************"
95 95 ],
96 96 "email": "user@example.com",
97 97 "emails": [
98 98 "user@example.com"
99 99 ],
100 100 "extern_name": "rhodecode",
101 101 "extern_type": "rhodecode",
102 102 "firstname": "username",
103 103 "ip_addresses": [],
104 104 "language": null,
105 105 "last_login": "2015-09-16T17:16:35.854",
106 106 "lastname": "surname",
107 107 "user_id": <user_id>,
108 108 "username": "name"
109 109 }
110 110 ],
111 111 "fork_of": "parent-repo",
112 112 "landing_rev": [
113 113 "rev",
114 114 "tip"
115 115 ],
116 116 "last_changeset": {
117 117 "author": "User <user@example.com>",
118 118 "branch": "default",
119 119 "date": "timestamp",
120 120 "message": "last commit message",
121 121 "parents": [
122 122 {
123 123 "raw_id": "commit-id"
124 124 }
125 125 ],
126 126 "raw_id": "commit-id",
127 127 "revision": <revision number>,
128 128 "short_id": "short id"
129 129 },
130 130 "lock_reason": null,
131 131 "locked_by": null,
132 132 "locked_date": null,
133 133 "owner": "owner-name",
134 134 "permissions": [
135 135 {
136 136 "name": "super-admin-name",
137 137 "origin": "super-admin",
138 138 "permission": "repository.admin",
139 139 "type": "user"
140 140 },
141 141 {
142 142 "name": "owner-name",
143 143 "origin": "owner",
144 144 "permission": "repository.admin",
145 145 "type": "user"
146 146 },
147 147 {
148 148 "name": "user-group-name",
149 149 "origin": "permission",
150 150 "permission": "repository.write",
151 151 "type": "user_group"
152 152 }
153 153 ],
154 154 "private": true,
155 155 "repo_id": 676,
156 156 "repo_name": "user-group/repo-name",
157 157 "repo_type": "hg"
158 158 }
159 159 }
160 160 """
161 161
162 162 repo = get_repo_or_error(repoid)
163 163 cache = Optional.extract(cache)
164 164
165 165 include_secrets = False
166 166 if has_superadmin_permission(apiuser):
167 167 include_secrets = True
168 168 else:
169 169 # check if we have at least read permission for this repo !
170 170 _perms = (
171 171 'repository.admin', 'repository.write', 'repository.read',)
172 172 validate_repo_permissions(apiuser, repoid, repo, _perms)
173 173
174 174 permissions = []
175 175 for _user in repo.permissions():
176 176 user_data = {
177 177 'name': _user.username,
178 178 'permission': _user.permission,
179 179 'origin': get_origin(_user),
180 180 'type': "user",
181 181 }
182 182 permissions.append(user_data)
183 183
184 184 for _user_group in repo.permission_user_groups():
185 185 user_group_data = {
186 186 'name': _user_group.users_group_name,
187 187 'permission': _user_group.permission,
188 188 'origin': get_origin(_user_group),
189 189 'type': "user_group",
190 190 }
191 191 permissions.append(user_group_data)
192 192
193 193 following_users = [
194 194 user.user.get_api_data(include_secrets=include_secrets)
195 195 for user in repo.followers]
196 196
197 197 if not cache:
198 198 repo.update_commit_cache()
199 199 data = repo.get_api_data(include_secrets=include_secrets)
200 200 data['permissions'] = permissions
201 201 data['followers'] = following_users
202 202 return data
203 203
204 204
205 205 @jsonrpc_method()
206 206 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
207 207 """
208 208 Lists all existing repositories.
209 209
210 210 This command can only be run using an |authtoken| with admin rights,
211 211 or users with at least read rights to |repos|.
212 212
213 213 :param apiuser: This is filled automatically from the |authtoken|.
214 214 :type apiuser: AuthUser
215 215 :param root: specify root repository group to fetch repositories.
216 216 filters the returned repositories to be members of given root group.
217 217 :type root: Optional(None)
218 218 :param traverse: traverse given root into subrepositories. With this flag
219 219 set to False, it will only return top-level repositories from `root`.
220 220 if root is empty it will return just top-level repositories.
221 221 :type traverse: Optional(True)
222 222
223 223
224 224 Example output:
225 225
226 226 .. code-block:: bash
227 227
228 228 id : <id_given_in_input>
229 229 result: [
230 230 {
231 231 "repo_id" : "<repo_id>",
232 232 "repo_name" : "<reponame>"
233 233 "repo_type" : "<repo_type>",
234 234 "clone_uri" : "<clone_uri>",
235 235 "private": : "<bool>",
236 236 "created_on" : "<datetimecreated>",
237 237 "description" : "<description>",
238 238 "landing_rev": "<landing_rev>",
239 239 "owner": "<repo_owner>",
240 240 "fork_of": "<name_of_fork_parent>",
241 241 "enable_downloads": "<bool>",
242 242 "enable_locking": "<bool>",
243 243 "enable_statistics": "<bool>",
244 244 },
245 245 ...
246 246 ]
247 247 error: null
248 248 """
249 249
250 250 include_secrets = has_superadmin_permission(apiuser)
251 251 _perms = ('repository.read', 'repository.write', 'repository.admin',)
252 252 extras = {'user': apiuser}
253 253
254 254 root = Optional.extract(root)
255 255 traverse = Optional.extract(traverse, binary=True)
256 256
257 257 if root:
258 258 # verify parent existance, if it's empty return an error
259 259 parent = RepoGroup.get_by_group_name(root)
260 260 if not parent:
261 261 raise JSONRPCError(
262 262 'Root repository group `{}` does not exist'.format(root))
263 263
264 264 if traverse:
265 265 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
266 266 else:
267 267 repos = RepoModel().get_repos_for_root(root=parent)
268 268 else:
269 269 if traverse:
270 270 repos = RepoModel().get_all()
271 271 else:
272 272 # return just top-level
273 273 repos = RepoModel().get_repos_for_root(root=None)
274 274
275 275 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
276 276 return [repo.get_api_data(include_secrets=include_secrets)
277 277 for repo in repo_list]
278 278
279 279
280 280 @jsonrpc_method()
281 281 def get_repo_changeset(request, apiuser, repoid, revision,
282 282 details=Optional('basic')):
283 283 """
284 284 Returns information about a changeset.
285 285
286 286 Additionally parameters define the amount of details returned by
287 287 this function.
288 288
289 289 This command can only be run using an |authtoken| with admin rights,
290 290 or users with at least read rights to the |repo|.
291 291
292 292 :param apiuser: This is filled automatically from the |authtoken|.
293 293 :type apiuser: AuthUser
294 294 :param repoid: The repository name or repository id
295 295 :type repoid: str or int
296 296 :param revision: revision for which listing should be done
297 297 :type revision: str
298 298 :param details: details can be 'basic|extended|full' full gives diff
299 299 info details like the diff itself, and number of changed files etc.
300 300 :type details: Optional(str)
301 301
302 302 """
303 303 repo = get_repo_or_error(repoid)
304 304 if not has_superadmin_permission(apiuser):
305 305 _perms = (
306 306 'repository.admin', 'repository.write', 'repository.read',)
307 307 validate_repo_permissions(apiuser, repoid, repo, _perms)
308 308
309 309 changes_details = Optional.extract(details)
310 310 _changes_details_types = ['basic', 'extended', 'full']
311 311 if changes_details not in _changes_details_types:
312 312 raise JSONRPCError(
313 313 'ret_type must be one of %s' % (
314 314 ','.join(_changes_details_types)))
315 315
316 316 pre_load = ['author', 'branch', 'date', 'message', 'parents',
317 317 'status', '_commit', '_file_paths']
318 318
319 319 try:
320 320 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
321 321 except TypeError as e:
322 322 raise JSONRPCError(safe_str(e))
323 323 _cs_json = cs.__json__()
324 324 _cs_json['diff'] = build_commit_data(cs, changes_details)
325 325 if changes_details == 'full':
326 326 _cs_json['refs'] = cs._get_refs()
327 327 return _cs_json
328 328
329 329
330 330 @jsonrpc_method()
331 331 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
332 332 details=Optional('basic')):
333 333 """
334 334 Returns a set of commits limited by the number starting
335 335 from the `start_rev` option.
336 336
337 337 Additional parameters define the amount of details returned by this
338 338 function.
339 339
340 340 This command can only be run using an |authtoken| with admin rights,
341 341 or users with at least read rights to |repos|.
342 342
343 343 :param apiuser: This is filled automatically from the |authtoken|.
344 344 :type apiuser: AuthUser
345 345 :param repoid: The repository name or repository ID.
346 346 :type repoid: str or int
347 347 :param start_rev: The starting revision from where to get changesets.
348 348 :type start_rev: str
349 349 :param limit: Limit the number of commits to this amount
350 350 :type limit: str or int
351 351 :param details: Set the level of detail returned. Valid option are:
352 352 ``basic``, ``extended`` and ``full``.
353 353 :type details: Optional(str)
354 354
355 355 .. note::
356 356
357 357 Setting the parameter `details` to the value ``full`` is extensive
358 358 and returns details like the diff itself, and the number
359 359 of changed files.
360 360
361 361 """
362 362 repo = get_repo_or_error(repoid)
363 363 if not has_superadmin_permission(apiuser):
364 364 _perms = (
365 365 'repository.admin', 'repository.write', 'repository.read',)
366 366 validate_repo_permissions(apiuser, repoid, repo, _perms)
367 367
368 368 changes_details = Optional.extract(details)
369 369 _changes_details_types = ['basic', 'extended', 'full']
370 370 if changes_details not in _changes_details_types:
371 371 raise JSONRPCError(
372 372 'ret_type must be one of %s' % (
373 373 ','.join(_changes_details_types)))
374 374
375 375 limit = int(limit)
376 376 pre_load = ['author', 'branch', 'date', 'message', 'parents',
377 377 'status', '_commit', '_file_paths']
378 378
379 379 vcs_repo = repo.scm_instance()
380 380 # SVN needs a special case to distinguish its index and commit id
381 381 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
382 382 start_rev = vcs_repo.commit_ids[0]
383 383
384 384 try:
385 385 commits = vcs_repo.get_commits(
386 386 start_id=start_rev, pre_load=pre_load, translate_tags=False)
387 387 except TypeError as e:
388 388 raise JSONRPCError(safe_str(e))
389 389 except Exception:
390 390 log.exception('Fetching of commits failed')
391 391 raise JSONRPCError('Error occurred during commit fetching')
392 392
393 393 ret = []
394 394 for cnt, commit in enumerate(commits):
395 395 if cnt >= limit != -1:
396 396 break
397 397 _cs_json = commit.__json__()
398 398 _cs_json['diff'] = build_commit_data(commit, changes_details)
399 399 if changes_details == 'full':
400 400 _cs_json['refs'] = {
401 401 'branches': [commit.branch],
402 402 'bookmarks': getattr(commit, 'bookmarks', []),
403 403 'tags': commit.tags
404 404 }
405 405 ret.append(_cs_json)
406 406 return ret
407 407
408 408
409 409 @jsonrpc_method()
410 410 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
411 411 ret_type=Optional('all'), details=Optional('basic'),
412 412 max_file_bytes=Optional(None)):
413 413 """
414 414 Returns a list of nodes and children in a flat list for a given
415 415 path at given revision.
416 416
417 417 It's possible to specify ret_type to show only `files` or `dirs`.
418 418
419 419 This command can only be run using an |authtoken| with admin rights,
420 420 or users with at least read rights to |repos|.
421 421
422 422 :param apiuser: This is filled automatically from the |authtoken|.
423 423 :type apiuser: AuthUser
424 424 :param repoid: The repository name or repository ID.
425 425 :type repoid: str or int
426 426 :param revision: The revision for which listing should be done.
427 427 :type revision: str
428 428 :param root_path: The path from which to start displaying.
429 429 :type root_path: str
430 430 :param ret_type: Set the return type. Valid options are
431 431 ``all`` (default), ``files`` and ``dirs``.
432 432 :type ret_type: Optional(str)
433 433 :param details: Returns extended information about nodes, such as
434 434 md5, binary, and or content.
435 435 The valid options are ``basic`` and ``full``.
436 436 :type details: Optional(str)
437 437 :param max_file_bytes: Only return file content under this file size bytes
438 438 :type details: Optional(int)
439 439
440 440 Example output:
441 441
442 442 .. code-block:: bash
443 443
444 444 id : <id_given_in_input>
445 445 result: [
446 446 {
447 447 "binary": false,
448 448 "content": "File line",
449 449 "extension": "md",
450 450 "lines": 2,
451 451 "md5": "059fa5d29b19c0657e384749480f6422",
452 452 "mimetype": "text/x-minidsrc",
453 453 "name": "file.md",
454 454 "size": 580,
455 455 "type": "file"
456 456 },
457 457 ...
458 458 ]
459 459 error: null
460 460 """
461 461
462 462 repo = get_repo_or_error(repoid)
463 463 if not has_superadmin_permission(apiuser):
464 464 _perms = ('repository.admin', 'repository.write', 'repository.read',)
465 465 validate_repo_permissions(apiuser, repoid, repo, _perms)
466 466
467 467 ret_type = Optional.extract(ret_type)
468 468 details = Optional.extract(details)
469 469 _extended_types = ['basic', 'full']
470 470 if details not in _extended_types:
471 471 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
472 472 extended_info = False
473 473 content = False
474 474 if details == 'basic':
475 475 extended_info = True
476 476
477 477 if details == 'full':
478 478 extended_info = content = True
479 479
480 480 _map = {}
481 481 try:
482 482 # check if repo is not empty by any chance, skip quicker if it is.
483 483 _scm = repo.scm_instance()
484 484 if _scm.is_empty():
485 485 return []
486 486
487 487 _d, _f = ScmModel().get_nodes(
488 488 repo, revision, root_path, flat=False,
489 489 extended_info=extended_info, content=content,
490 490 max_file_bytes=max_file_bytes)
491 491 _map = {
492 492 'all': _d + _f,
493 493 'files': _f,
494 494 'dirs': _d,
495 495 }
496 496 return _map[ret_type]
497 497 except KeyError:
498 498 raise JSONRPCError(
499 499 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
500 500 except Exception:
501 501 log.exception("Exception occurred while trying to get repo nodes")
502 502 raise JSONRPCError(
503 503 'failed to get repo: `%s` nodes' % repo.repo_name
504 504 )
505 505
506 506
507 507 @jsonrpc_method()
508 508 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
509 509 max_file_bytes=Optional(None), details=Optional('basic'),
510 510 cache=Optional(True)):
511 511 """
512 512 Returns a single file from repository at given revision.
513 513
514 514 This command can only be run using an |authtoken| with admin rights,
515 515 or users with at least read rights to |repos|.
516 516
517 517 :param apiuser: This is filled automatically from the |authtoken|.
518 518 :type apiuser: AuthUser
519 519 :param repoid: The repository name or repository ID.
520 520 :type repoid: str or int
521 521 :param commit_id: The revision for which listing should be done.
522 522 :type commit_id: str
523 523 :param file_path: The path from which to start displaying.
524 524 :type file_path: str
525 525 :param details: Returns different set of information about nodes.
526 526 The valid options are ``minimal`` ``basic`` and ``full``.
527 527 :type details: Optional(str)
528 528 :param max_file_bytes: Only return file content under this file size bytes
529 529 :type max_file_bytes: Optional(int)
530 530 :param cache: Use internal caches for fetching files. If disabled fetching
531 531 files is slower but more memory efficient
532 532 :type cache: Optional(bool)
533 533
534 534 Example output:
535 535
536 536 .. code-block:: bash
537 537
538 538 id : <id_given_in_input>
539 539 result: {
540 540 "binary": false,
541 541 "extension": "py",
542 542 "lines": 35,
543 543 "content": "....",
544 544 "md5": "76318336366b0f17ee249e11b0c99c41",
545 545 "mimetype": "text/x-python",
546 546 "name": "python.py",
547 547 "size": 817,
548 548 "type": "file",
549 549 }
550 550 error: null
551 551 """
552 552
553 553 repo = get_repo_or_error(repoid)
554 554 if not has_superadmin_permission(apiuser):
555 555 _perms = ('repository.admin', 'repository.write', 'repository.read',)
556 556 validate_repo_permissions(apiuser, repoid, repo, _perms)
557 557
558 558 cache = Optional.extract(cache, binary=True)
559 559 details = Optional.extract(details)
560 560 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
561 561 if details not in _extended_types:
562 562 raise JSONRPCError(
563 563 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
564 564 extended_info = False
565 565 content = False
566 566
567 567 if details == 'minimal':
568 568 extended_info = False
569 569
570 570 elif details == 'basic':
571 571 extended_info = True
572 572
573 573 elif details == 'full':
574 574 extended_info = content = True
575 575
576 576 try:
577 577 # check if repo is not empty by any chance, skip quicker if it is.
578 578 _scm = repo.scm_instance()
579 579 if _scm.is_empty():
580 580 return None
581 581
582 582 node = ScmModel().get_node(
583 583 repo, commit_id, file_path, extended_info=extended_info,
584 584 content=content, max_file_bytes=max_file_bytes, cache=cache)
585 585 except NodeDoesNotExistError:
586 586 raise JSONRPCError('There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
587 587 repo.repo_name, file_path, commit_id))
588 588 except Exception:
589 589 log.exception("Exception occurred while trying to get repo %s file",
590 590 repo.repo_name)
591 591 raise JSONRPCError('failed to get repo: `{}` file at path {}'.format(
592 592 repo.repo_name, file_path))
593 593
594 594 return node
595 595
596 596
597 597 @jsonrpc_method()
598 598 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
599 599 """
600 600 Returns a list of tree nodes for path at given revision. This api is built
601 601 strictly for usage in full text search building, and shouldn't be consumed
602 602
603 603 This command can only be run using an |authtoken| with admin rights,
604 604 or users with at least read rights to |repos|.
605 605
606 606 """
607 607
608 608 repo = get_repo_or_error(repoid)
609 609 if not has_superadmin_permission(apiuser):
610 610 _perms = ('repository.admin', 'repository.write', 'repository.read',)
611 611 validate_repo_permissions(apiuser, repoid, repo, _perms)
612 612
613 613 repo_id = repo.repo_id
614 614 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
615 615 cache_on = cache_seconds > 0
616 616
617 617 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
618 618 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
619 619
620 def compute_fts_tree(repo_id, commit_id, root_path, cache_ver):
620 def compute_fts_tree(cache_ver, repo_id, commit_id, root_path):
621 621 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
622 622
623 623 try:
624 624 # check if repo is not empty by any chance, skip quicker if it is.
625 625 _scm = repo.scm_instance()
626 626 if _scm.is_empty():
627 627 return []
628 628 except RepositoryError:
629 629 log.exception("Exception occurred while trying to get repo nodes")
630 630 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
631 631
632 632 try:
633 633 # we need to resolve commit_id to a FULL sha for cache to work correctly.
634 634 # sending 'master' is a pointer that needs to be translated to current commit.
635 635 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
636 636 log.debug(
637 637 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
638 638 'with caching: %s[TTL: %ss]' % (
639 639 repo_id, commit_id, cache_on, cache_seconds or 0))
640 640
641 tree_files = compute_fts_tree(repo_id, commit_id, root_path, 'v1')
641 tree_files = compute_fts_tree(rc_cache.FILE_TREE_CACHE_VER, repo_id, commit_id, root_path)
642 642 return tree_files
643 643
644 644 except Exception:
645 645 log.exception("Exception occurred while trying to get repo nodes")
646 646 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
647 647
648 648
649 649 @jsonrpc_method()
650 650 def get_repo_refs(request, apiuser, repoid):
651 651 """
652 652 Returns a dictionary of current references. It returns
653 653 bookmarks, branches, closed_branches, and tags for given repository
654 654
655 655 It's possible to specify ret_type to show only `files` or `dirs`.
656 656
657 657 This command can only be run using an |authtoken| with admin rights,
658 658 or users with at least read rights to |repos|.
659 659
660 660 :param apiuser: This is filled automatically from the |authtoken|.
661 661 :type apiuser: AuthUser
662 662 :param repoid: The repository name or repository ID.
663 663 :type repoid: str or int
664 664
665 665 Example output:
666 666
667 667 .. code-block:: bash
668 668
669 669 id : <id_given_in_input>
670 670 "result": {
671 671 "bookmarks": {
672 672 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
673 673 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
674 674 },
675 675 "branches": {
676 676 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
677 677 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
678 678 },
679 679 "branches_closed": {},
680 680 "tags": {
681 681 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
682 682 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
683 683 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
684 684 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
685 685 }
686 686 }
687 687 error: null
688 688 """
689 689
690 690 repo = get_repo_or_error(repoid)
691 691 if not has_superadmin_permission(apiuser):
692 692 _perms = ('repository.admin', 'repository.write', 'repository.read',)
693 693 validate_repo_permissions(apiuser, repoid, repo, _perms)
694 694
695 695 try:
696 696 # check if repo is not empty by any chance, skip quicker if it is.
697 697 vcs_instance = repo.scm_instance()
698 698 refs = vcs_instance.refs()
699 699 return refs
700 700 except Exception:
701 701 log.exception("Exception occurred while trying to get repo refs")
702 702 raise JSONRPCError(
703 703 'failed to get repo: `%s` references' % repo.repo_name
704 704 )
705 705
706 706
707 707 @jsonrpc_method()
708 708 def create_repo(
709 709 request, apiuser, repo_name, repo_type,
710 710 owner=Optional(OAttr('apiuser')),
711 711 description=Optional(''),
712 712 private=Optional(False),
713 713 clone_uri=Optional(None),
714 714 push_uri=Optional(None),
715 715 landing_rev=Optional(None),
716 716 enable_statistics=Optional(False),
717 717 enable_locking=Optional(False),
718 718 enable_downloads=Optional(False),
719 719 copy_permissions=Optional(False)):
720 720 """
721 721 Creates a repository.
722 722
723 723 * If the repository name contains "/", repository will be created inside
724 724 a repository group or nested repository groups
725 725
726 726 For example "foo/bar/repo1" will create |repo| called "repo1" inside
727 727 group "foo/bar". You have to have permissions to access and write to
728 728 the last repository group ("bar" in this example)
729 729
730 730 This command can only be run using an |authtoken| with at least
731 731 permissions to create repositories, or write permissions to
732 732 parent repository groups.
733 733
734 734 :param apiuser: This is filled automatically from the |authtoken|.
735 735 :type apiuser: AuthUser
736 736 :param repo_name: Set the repository name.
737 737 :type repo_name: str
738 738 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
739 739 :type repo_type: str
740 740 :param owner: user_id or username
741 741 :type owner: Optional(str)
742 742 :param description: Set the repository description.
743 743 :type description: Optional(str)
744 744 :param private: set repository as private
745 745 :type private: bool
746 746 :param clone_uri: set clone_uri
747 747 :type clone_uri: str
748 748 :param push_uri: set push_uri
749 749 :type push_uri: str
750 750 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
751 751 :type landing_rev: str
752 752 :param enable_locking:
753 753 :type enable_locking: bool
754 754 :param enable_downloads:
755 755 :type enable_downloads: bool
756 756 :param enable_statistics:
757 757 :type enable_statistics: bool
758 758 :param copy_permissions: Copy permission from group in which the
759 759 repository is being created.
760 760 :type copy_permissions: bool
761 761
762 762
763 763 Example output:
764 764
765 765 .. code-block:: bash
766 766
767 767 id : <id_given_in_input>
768 768 result: {
769 769 "msg": "Created new repository `<reponame>`",
770 770 "success": true,
771 771 "task": "<celery task id or None if done sync>"
772 772 }
773 773 error: null
774 774
775 775
776 776 Example error output:
777 777
778 778 .. code-block:: bash
779 779
780 780 id : <id_given_in_input>
781 781 result : null
782 782 error : {
783 783 'failed to create repository `<repo_name>`'
784 784 }
785 785
786 786 """
787 787
788 788 owner = validate_set_owner_permissions(apiuser, owner)
789 789
790 790 description = Optional.extract(description)
791 791 copy_permissions = Optional.extract(copy_permissions)
792 792 clone_uri = Optional.extract(clone_uri)
793 793 push_uri = Optional.extract(push_uri)
794 794
795 795 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
796 796 if isinstance(private, Optional):
797 797 private = defs.get('repo_private') or Optional.extract(private)
798 798 if isinstance(repo_type, Optional):
799 799 repo_type = defs.get('repo_type')
800 800 if isinstance(enable_statistics, Optional):
801 801 enable_statistics = defs.get('repo_enable_statistics')
802 802 if isinstance(enable_locking, Optional):
803 803 enable_locking = defs.get('repo_enable_locking')
804 804 if isinstance(enable_downloads, Optional):
805 805 enable_downloads = defs.get('repo_enable_downloads')
806 806
807 807 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
808 808 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
809 809 ref_choices = list(set(ref_choices + [landing_ref]))
810 810
811 811 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
812 812
813 813 schema = repo_schema.RepoSchema().bind(
814 814 repo_type_options=rhodecode.BACKENDS.keys(),
815 815 repo_ref_options=ref_choices,
816 816 repo_type=repo_type,
817 817 # user caller
818 818 user=apiuser)
819 819
820 820 try:
821 821 schema_data = schema.deserialize(dict(
822 822 repo_name=repo_name,
823 823 repo_type=repo_type,
824 824 repo_owner=owner.username,
825 825 repo_description=description,
826 826 repo_landing_commit_ref=landing_commit_ref,
827 827 repo_clone_uri=clone_uri,
828 828 repo_push_uri=push_uri,
829 829 repo_private=private,
830 830 repo_copy_permissions=copy_permissions,
831 831 repo_enable_statistics=enable_statistics,
832 832 repo_enable_downloads=enable_downloads,
833 833 repo_enable_locking=enable_locking))
834 834 except validation_schema.Invalid as err:
835 835 raise JSONRPCValidationError(colander_exc=err)
836 836
837 837 try:
838 838 data = {
839 839 'owner': owner,
840 840 'repo_name': schema_data['repo_group']['repo_name_without_group'],
841 841 'repo_name_full': schema_data['repo_name'],
842 842 'repo_group': schema_data['repo_group']['repo_group_id'],
843 843 'repo_type': schema_data['repo_type'],
844 844 'repo_description': schema_data['repo_description'],
845 845 'repo_private': schema_data['repo_private'],
846 846 'clone_uri': schema_data['repo_clone_uri'],
847 847 'push_uri': schema_data['repo_push_uri'],
848 848 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
849 849 'enable_statistics': schema_data['repo_enable_statistics'],
850 850 'enable_locking': schema_data['repo_enable_locking'],
851 851 'enable_downloads': schema_data['repo_enable_downloads'],
852 852 'repo_copy_permissions': schema_data['repo_copy_permissions'],
853 853 }
854 854
855 855 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
856 856 task_id = get_task_id(task)
857 857 # no commit, it's done in RepoModel, or async via celery
858 858 return {
859 859 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
860 860 'success': True, # cannot return the repo data here since fork
861 861 # can be done async
862 862 'task': task_id
863 863 }
864 864 except Exception:
865 865 log.exception(
866 866 u"Exception while trying to create the repository %s",
867 867 schema_data['repo_name'])
868 868 raise JSONRPCError(
869 869 'failed to create repository `%s`' % (schema_data['repo_name'],))
870 870
871 871
872 872 @jsonrpc_method()
873 873 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
874 874 description=Optional('')):
875 875 """
876 876 Adds an extra field to a repository.
877 877
878 878 This command can only be run using an |authtoken| with at least
879 879 write permissions to the |repo|.
880 880
881 881 :param apiuser: This is filled automatically from the |authtoken|.
882 882 :type apiuser: AuthUser
883 883 :param repoid: Set the repository name or repository id.
884 884 :type repoid: str or int
885 885 :param key: Create a unique field key for this repository.
886 886 :type key: str
887 887 :param label:
888 888 :type label: Optional(str)
889 889 :param description:
890 890 :type description: Optional(str)
891 891 """
892 892 repo = get_repo_or_error(repoid)
893 893 if not has_superadmin_permission(apiuser):
894 894 _perms = ('repository.admin',)
895 895 validate_repo_permissions(apiuser, repoid, repo, _perms)
896 896
897 897 label = Optional.extract(label) or key
898 898 description = Optional.extract(description)
899 899
900 900 field = RepositoryField.get_by_key_name(key, repo)
901 901 if field:
902 902 raise JSONRPCError('Field with key '
903 903 '`%s` exists for repo `%s`' % (key, repoid))
904 904
905 905 try:
906 906 RepoModel().add_repo_field(repo, key, field_label=label,
907 907 field_desc=description)
908 908 Session().commit()
909 909 return {
910 910 'msg': "Added new repository field `%s`" % (key,),
911 911 'success': True,
912 912 }
913 913 except Exception:
914 914 log.exception("Exception occurred while trying to add field to repo")
915 915 raise JSONRPCError(
916 916 'failed to create new field for repository `%s`' % (repoid,))
917 917
918 918
919 919 @jsonrpc_method()
920 920 def remove_field_from_repo(request, apiuser, repoid, key):
921 921 """
922 922 Removes an extra field from a repository.
923 923
924 924 This command can only be run using an |authtoken| with at least
925 925 write permissions to the |repo|.
926 926
927 927 :param apiuser: This is filled automatically from the |authtoken|.
928 928 :type apiuser: AuthUser
929 929 :param repoid: Set the repository name or repository ID.
930 930 :type repoid: str or int
931 931 :param key: Set the unique field key for this repository.
932 932 :type key: str
933 933 """
934 934
935 935 repo = get_repo_or_error(repoid)
936 936 if not has_superadmin_permission(apiuser):
937 937 _perms = ('repository.admin',)
938 938 validate_repo_permissions(apiuser, repoid, repo, _perms)
939 939
940 940 field = RepositoryField.get_by_key_name(key, repo)
941 941 if not field:
942 942 raise JSONRPCError('Field with key `%s` does not '
943 943 'exists for repo `%s`' % (key, repoid))
944 944
945 945 try:
946 946 RepoModel().delete_repo_field(repo, field_key=key)
947 947 Session().commit()
948 948 return {
949 949 'msg': "Deleted repository field `%s`" % (key,),
950 950 'success': True,
951 951 }
952 952 except Exception:
953 953 log.exception(
954 954 "Exception occurred while trying to delete field from repo")
955 955 raise JSONRPCError(
956 956 'failed to delete field for repository `%s`' % (repoid,))
957 957
958 958
959 959 @jsonrpc_method()
960 960 def update_repo(
961 961 request, apiuser, repoid, repo_name=Optional(None),
962 962 owner=Optional(OAttr('apiuser')), description=Optional(''),
963 963 private=Optional(False),
964 964 clone_uri=Optional(None), push_uri=Optional(None),
965 965 landing_rev=Optional(None), fork_of=Optional(None),
966 966 enable_statistics=Optional(False),
967 967 enable_locking=Optional(False),
968 968 enable_downloads=Optional(False), fields=Optional('')):
969 969 """
970 970 Updates a repository with the given information.
971 971
972 972 This command can only be run using an |authtoken| with at least
973 973 admin permissions to the |repo|.
974 974
975 975 * If the repository name contains "/", repository will be updated
976 976 accordingly with a repository group or nested repository groups
977 977
978 978 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
979 979 called "repo-test" and place it inside group "foo/bar".
980 980 You have to have permissions to access and write to the last repository
981 981 group ("bar" in this example)
982 982
983 983 :param apiuser: This is filled automatically from the |authtoken|.
984 984 :type apiuser: AuthUser
985 985 :param repoid: repository name or repository ID.
986 986 :type repoid: str or int
987 987 :param repo_name: Update the |repo| name, including the
988 988 repository group it's in.
989 989 :type repo_name: str
990 990 :param owner: Set the |repo| owner.
991 991 :type owner: str
992 992 :param fork_of: Set the |repo| as fork of another |repo|.
993 993 :type fork_of: str
994 994 :param description: Update the |repo| description.
995 995 :type description: str
996 996 :param private: Set the |repo| as private. (True | False)
997 997 :type private: bool
998 998 :param clone_uri: Update the |repo| clone URI.
999 999 :type clone_uri: str
1000 1000 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1001 1001 :type landing_rev: str
1002 1002 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1003 1003 :type enable_statistics: bool
1004 1004 :param enable_locking: Enable |repo| locking.
1005 1005 :type enable_locking: bool
1006 1006 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1007 1007 :type enable_downloads: bool
1008 1008 :param fields: Add extra fields to the |repo|. Use the following
1009 1009 example format: ``field_key=field_val,field_key2=fieldval2``.
1010 1010 Escape ', ' with \,
1011 1011 :type fields: str
1012 1012 """
1013 1013
1014 1014 repo = get_repo_or_error(repoid)
1015 1015
1016 1016 include_secrets = False
1017 1017 if not has_superadmin_permission(apiuser):
1018 1018 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1019 1019 else:
1020 1020 include_secrets = True
1021 1021
1022 1022 updates = dict(
1023 1023 repo_name=repo_name
1024 1024 if not isinstance(repo_name, Optional) else repo.repo_name,
1025 1025
1026 1026 fork_id=fork_of
1027 1027 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1028 1028
1029 1029 user=owner
1030 1030 if not isinstance(owner, Optional) else repo.user.username,
1031 1031
1032 1032 repo_description=description
1033 1033 if not isinstance(description, Optional) else repo.description,
1034 1034
1035 1035 repo_private=private
1036 1036 if not isinstance(private, Optional) else repo.private,
1037 1037
1038 1038 clone_uri=clone_uri
1039 1039 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1040 1040
1041 1041 push_uri=push_uri
1042 1042 if not isinstance(push_uri, Optional) else repo.push_uri,
1043 1043
1044 1044 repo_landing_rev=landing_rev
1045 1045 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1046 1046
1047 1047 repo_enable_statistics=enable_statistics
1048 1048 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1049 1049
1050 1050 repo_enable_locking=enable_locking
1051 1051 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1052 1052
1053 1053 repo_enable_downloads=enable_downloads
1054 1054 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1055 1055
1056 1056 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1057 1057 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1058 1058 request.translate, repo=repo)
1059 1059 ref_choices = list(set(ref_choices + [landing_ref]))
1060 1060
1061 1061 old_values = repo.get_api_data()
1062 1062 repo_type = repo.repo_type
1063 1063 schema = repo_schema.RepoSchema().bind(
1064 1064 repo_type_options=rhodecode.BACKENDS.keys(),
1065 1065 repo_ref_options=ref_choices,
1066 1066 repo_type=repo_type,
1067 1067 # user caller
1068 1068 user=apiuser,
1069 1069 old_values=old_values)
1070 1070 try:
1071 1071 schema_data = schema.deserialize(dict(
1072 1072 # we save old value, users cannot change type
1073 1073 repo_type=repo_type,
1074 1074
1075 1075 repo_name=updates['repo_name'],
1076 1076 repo_owner=updates['user'],
1077 1077 repo_description=updates['repo_description'],
1078 1078 repo_clone_uri=updates['clone_uri'],
1079 1079 repo_push_uri=updates['push_uri'],
1080 1080 repo_fork_of=updates['fork_id'],
1081 1081 repo_private=updates['repo_private'],
1082 1082 repo_landing_commit_ref=updates['repo_landing_rev'],
1083 1083 repo_enable_statistics=updates['repo_enable_statistics'],
1084 1084 repo_enable_downloads=updates['repo_enable_downloads'],
1085 1085 repo_enable_locking=updates['repo_enable_locking']))
1086 1086 except validation_schema.Invalid as err:
1087 1087 raise JSONRPCValidationError(colander_exc=err)
1088 1088
1089 1089 # save validated data back into the updates dict
1090 1090 validated_updates = dict(
1091 1091 repo_name=schema_data['repo_group']['repo_name_without_group'],
1092 1092 repo_group=schema_data['repo_group']['repo_group_id'],
1093 1093
1094 1094 user=schema_data['repo_owner'],
1095 1095 repo_description=schema_data['repo_description'],
1096 1096 repo_private=schema_data['repo_private'],
1097 1097 clone_uri=schema_data['repo_clone_uri'],
1098 1098 push_uri=schema_data['repo_push_uri'],
1099 1099 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1100 1100 repo_enable_statistics=schema_data['repo_enable_statistics'],
1101 1101 repo_enable_locking=schema_data['repo_enable_locking'],
1102 1102 repo_enable_downloads=schema_data['repo_enable_downloads'],
1103 1103 )
1104 1104
1105 1105 if schema_data['repo_fork_of']:
1106 1106 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1107 1107 validated_updates['fork_id'] = fork_repo.repo_id
1108 1108
1109 1109 # extra fields
1110 1110 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1111 1111 if fields:
1112 1112 validated_updates.update(fields)
1113 1113
1114 1114 try:
1115 1115 RepoModel().update(repo, **validated_updates)
1116 1116 audit_logger.store_api(
1117 1117 'repo.edit', action_data={'old_data': old_values},
1118 1118 user=apiuser, repo=repo)
1119 1119 Session().commit()
1120 1120 return {
1121 1121 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1122 1122 'repository': repo.get_api_data(include_secrets=include_secrets)
1123 1123 }
1124 1124 except Exception:
1125 1125 log.exception(
1126 1126 u"Exception while trying to update the repository %s",
1127 1127 repoid)
1128 1128 raise JSONRPCError('failed to update repo `%s`' % repoid)
1129 1129
1130 1130
1131 1131 @jsonrpc_method()
1132 1132 def fork_repo(request, apiuser, repoid, fork_name,
1133 1133 owner=Optional(OAttr('apiuser')),
1134 1134 description=Optional(''),
1135 1135 private=Optional(False),
1136 1136 clone_uri=Optional(None),
1137 1137 landing_rev=Optional(None),
1138 1138 copy_permissions=Optional(False)):
1139 1139 """
1140 1140 Creates a fork of the specified |repo|.
1141 1141
1142 1142 * If the fork_name contains "/", fork will be created inside
1143 1143 a repository group or nested repository groups
1144 1144
1145 1145 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1146 1146 inside group "foo/bar". You have to have permissions to access and
1147 1147 write to the last repository group ("bar" in this example)
1148 1148
1149 1149 This command can only be run using an |authtoken| with minimum
1150 1150 read permissions of the forked repo, create fork permissions for an user.
1151 1151
1152 1152 :param apiuser: This is filled automatically from the |authtoken|.
1153 1153 :type apiuser: AuthUser
1154 1154 :param repoid: Set repository name or repository ID.
1155 1155 :type repoid: str or int
1156 1156 :param fork_name: Set the fork name, including it's repository group membership.
1157 1157 :type fork_name: str
1158 1158 :param owner: Set the fork owner.
1159 1159 :type owner: str
1160 1160 :param description: Set the fork description.
1161 1161 :type description: str
1162 1162 :param copy_permissions: Copy permissions from parent |repo|. The
1163 1163 default is False.
1164 1164 :type copy_permissions: bool
1165 1165 :param private: Make the fork private. The default is False.
1166 1166 :type private: bool
1167 1167 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1168 1168
1169 1169 Example output:
1170 1170
1171 1171 .. code-block:: bash
1172 1172
1173 1173 id : <id_for_response>
1174 1174 api_key : "<api_key>"
1175 1175 args: {
1176 1176 "repoid" : "<reponame or repo_id>",
1177 1177 "fork_name": "<forkname>",
1178 1178 "owner": "<username or user_id = Optional(=apiuser)>",
1179 1179 "description": "<description>",
1180 1180 "copy_permissions": "<bool>",
1181 1181 "private": "<bool>",
1182 1182 "landing_rev": "<landing_rev>"
1183 1183 }
1184 1184
1185 1185 Example error output:
1186 1186
1187 1187 .. code-block:: bash
1188 1188
1189 1189 id : <id_given_in_input>
1190 1190 result: {
1191 1191 "msg": "Created fork of `<reponame>` as `<forkname>`",
1192 1192 "success": true,
1193 1193 "task": "<celery task id or None if done sync>"
1194 1194 }
1195 1195 error: null
1196 1196
1197 1197 """
1198 1198
1199 1199 repo = get_repo_or_error(repoid)
1200 1200 repo_name = repo.repo_name
1201 1201
1202 1202 if not has_superadmin_permission(apiuser):
1203 1203 # check if we have at least read permission for
1204 1204 # this repo that we fork !
1205 1205 _perms = (
1206 1206 'repository.admin', 'repository.write', 'repository.read')
1207 1207 validate_repo_permissions(apiuser, repoid, repo, _perms)
1208 1208
1209 1209 # check if the regular user has at least fork permissions as well
1210 1210 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1211 1211 raise JSONRPCForbidden()
1212 1212
1213 1213 # check if user can set owner parameter
1214 1214 owner = validate_set_owner_permissions(apiuser, owner)
1215 1215
1216 1216 description = Optional.extract(description)
1217 1217 copy_permissions = Optional.extract(copy_permissions)
1218 1218 clone_uri = Optional.extract(clone_uri)
1219 1219
1220 1220 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1221 1221 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1222 1222 ref_choices = list(set(ref_choices + [landing_ref]))
1223 1223 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1224 1224
1225 1225 private = Optional.extract(private)
1226 1226
1227 1227 schema = repo_schema.RepoSchema().bind(
1228 1228 repo_type_options=rhodecode.BACKENDS.keys(),
1229 1229 repo_ref_options=ref_choices,
1230 1230 repo_type=repo.repo_type,
1231 1231 # user caller
1232 1232 user=apiuser)
1233 1233
1234 1234 try:
1235 1235 schema_data = schema.deserialize(dict(
1236 1236 repo_name=fork_name,
1237 1237 repo_type=repo.repo_type,
1238 1238 repo_owner=owner.username,
1239 1239 repo_description=description,
1240 1240 repo_landing_commit_ref=landing_commit_ref,
1241 1241 repo_clone_uri=clone_uri,
1242 1242 repo_private=private,
1243 1243 repo_copy_permissions=copy_permissions))
1244 1244 except validation_schema.Invalid as err:
1245 1245 raise JSONRPCValidationError(colander_exc=err)
1246 1246
1247 1247 try:
1248 1248 data = {
1249 1249 'fork_parent_id': repo.repo_id,
1250 1250
1251 1251 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1252 1252 'repo_name_full': schema_data['repo_name'],
1253 1253 'repo_group': schema_data['repo_group']['repo_group_id'],
1254 1254 'repo_type': schema_data['repo_type'],
1255 1255 'description': schema_data['repo_description'],
1256 1256 'private': schema_data['repo_private'],
1257 1257 'copy_permissions': schema_data['repo_copy_permissions'],
1258 1258 'landing_rev': schema_data['repo_landing_commit_ref'],
1259 1259 }
1260 1260
1261 1261 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1262 1262 # no commit, it's done in RepoModel, or async via celery
1263 1263 task_id = get_task_id(task)
1264 1264
1265 1265 return {
1266 1266 'msg': 'Created fork of `%s` as `%s`' % (
1267 1267 repo.repo_name, schema_data['repo_name']),
1268 1268 'success': True, # cannot return the repo data here since fork
1269 1269 # can be done async
1270 1270 'task': task_id
1271 1271 }
1272 1272 except Exception:
1273 1273 log.exception(
1274 1274 u"Exception while trying to create fork %s",
1275 1275 schema_data['repo_name'])
1276 1276 raise JSONRPCError(
1277 1277 'failed to fork repository `%s` as `%s`' % (
1278 1278 repo_name, schema_data['repo_name']))
1279 1279
1280 1280
1281 1281 @jsonrpc_method()
1282 1282 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1283 1283 """
1284 1284 Deletes a repository.
1285 1285
1286 1286 * When the `forks` parameter is set it's possible to detach or delete
1287 1287 forks of deleted repository.
1288 1288
1289 1289 This command can only be run using an |authtoken| with admin
1290 1290 permissions on the |repo|.
1291 1291
1292 1292 :param apiuser: This is filled automatically from the |authtoken|.
1293 1293 :type apiuser: AuthUser
1294 1294 :param repoid: Set the repository name or repository ID.
1295 1295 :type repoid: str or int
1296 1296 :param forks: Set to `detach` or `delete` forks from the |repo|.
1297 1297 :type forks: Optional(str)
1298 1298
1299 1299 Example error output:
1300 1300
1301 1301 .. code-block:: bash
1302 1302
1303 1303 id : <id_given_in_input>
1304 1304 result: {
1305 1305 "msg": "Deleted repository `<reponame>`",
1306 1306 "success": true
1307 1307 }
1308 1308 error: null
1309 1309 """
1310 1310
1311 1311 repo = get_repo_or_error(repoid)
1312 1312 repo_name = repo.repo_name
1313 1313 if not has_superadmin_permission(apiuser):
1314 1314 _perms = ('repository.admin',)
1315 1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1316 1316
1317 1317 try:
1318 1318 handle_forks = Optional.extract(forks)
1319 1319 _forks_msg = ''
1320 1320 _forks = [f for f in repo.forks]
1321 1321 if handle_forks == 'detach':
1322 1322 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1323 1323 elif handle_forks == 'delete':
1324 1324 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1325 1325 elif _forks:
1326 1326 raise JSONRPCError(
1327 1327 'Cannot delete `%s` it still contains attached forks' %
1328 1328 (repo.repo_name,)
1329 1329 )
1330 1330 old_data = repo.get_api_data()
1331 1331 RepoModel().delete(repo, forks=forks)
1332 1332
1333 1333 repo = audit_logger.RepoWrap(repo_id=None,
1334 1334 repo_name=repo.repo_name)
1335 1335
1336 1336 audit_logger.store_api(
1337 1337 'repo.delete', action_data={'old_data': old_data},
1338 1338 user=apiuser, repo=repo)
1339 1339
1340 1340 ScmModel().mark_for_invalidation(repo_name, delete=True)
1341 1341 Session().commit()
1342 1342 return {
1343 1343 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1344 1344 'success': True
1345 1345 }
1346 1346 except Exception:
1347 1347 log.exception("Exception occurred while trying to delete repo")
1348 1348 raise JSONRPCError(
1349 1349 'failed to delete repository `%s`' % (repo_name,)
1350 1350 )
1351 1351
1352 1352
1353 1353 #TODO: marcink, change name ?
1354 1354 @jsonrpc_method()
1355 1355 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1356 1356 """
1357 1357 Invalidates the cache for the specified repository.
1358 1358
1359 1359 This command can only be run using an |authtoken| with admin rights to
1360 1360 the specified repository.
1361 1361
1362 1362 This command takes the following options:
1363 1363
1364 1364 :param apiuser: This is filled automatically from |authtoken|.
1365 1365 :type apiuser: AuthUser
1366 1366 :param repoid: Sets the repository name or repository ID.
1367 1367 :type repoid: str or int
1368 1368 :param delete_keys: This deletes the invalidated keys instead of
1369 1369 just flagging them.
1370 1370 :type delete_keys: Optional(``True`` | ``False``)
1371 1371
1372 1372 Example output:
1373 1373
1374 1374 .. code-block:: bash
1375 1375
1376 1376 id : <id_given_in_input>
1377 1377 result : {
1378 1378 'msg': Cache for repository `<repository name>` was invalidated,
1379 1379 'repository': <repository name>
1380 1380 }
1381 1381 error : null
1382 1382
1383 1383 Example error output:
1384 1384
1385 1385 .. code-block:: bash
1386 1386
1387 1387 id : <id_given_in_input>
1388 1388 result : null
1389 1389 error : {
1390 1390 'Error occurred during cache invalidation action'
1391 1391 }
1392 1392
1393 1393 """
1394 1394
1395 1395 repo = get_repo_or_error(repoid)
1396 1396 if not has_superadmin_permission(apiuser):
1397 1397 _perms = ('repository.admin', 'repository.write',)
1398 1398 validate_repo_permissions(apiuser, repoid, repo, _perms)
1399 1399
1400 1400 delete = Optional.extract(delete_keys)
1401 1401 try:
1402 1402 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1403 1403 return {
1404 1404 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1405 1405 'repository': repo.repo_name
1406 1406 }
1407 1407 except Exception:
1408 1408 log.exception(
1409 1409 "Exception occurred while trying to invalidate repo cache")
1410 1410 raise JSONRPCError(
1411 1411 'Error occurred during cache invalidation action'
1412 1412 )
1413 1413
1414 1414
1415 1415 #TODO: marcink, change name ?
1416 1416 @jsonrpc_method()
1417 1417 def lock(request, apiuser, repoid, locked=Optional(None),
1418 1418 userid=Optional(OAttr('apiuser'))):
1419 1419 """
1420 1420 Sets the lock state of the specified |repo| by the given user.
1421 1421 From more information, see :ref:`repo-locking`.
1422 1422
1423 1423 * If the ``userid`` option is not set, the repository is locked to the
1424 1424 user who called the method.
1425 1425 * If the ``locked`` parameter is not set, the current lock state of the
1426 1426 repository is displayed.
1427 1427
1428 1428 This command can only be run using an |authtoken| with admin rights to
1429 1429 the specified repository.
1430 1430
1431 1431 This command takes the following options:
1432 1432
1433 1433 :param apiuser: This is filled automatically from the |authtoken|.
1434 1434 :type apiuser: AuthUser
1435 1435 :param repoid: Sets the repository name or repository ID.
1436 1436 :type repoid: str or int
1437 1437 :param locked: Sets the lock state.
1438 1438 :type locked: Optional(``True`` | ``False``)
1439 1439 :param userid: Set the repository lock to this user.
1440 1440 :type userid: Optional(str or int)
1441 1441
1442 1442 Example error output:
1443 1443
1444 1444 .. code-block:: bash
1445 1445
1446 1446 id : <id_given_in_input>
1447 1447 result : {
1448 1448 'repo': '<reponame>',
1449 1449 'locked': <bool: lock state>,
1450 1450 'locked_since': <int: lock timestamp>,
1451 1451 'locked_by': <username of person who made the lock>,
1452 1452 'lock_reason': <str: reason for locking>,
1453 1453 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1454 1454 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1455 1455 or
1456 1456 'msg': 'Repo `<repository name>` not locked.'
1457 1457 or
1458 1458 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1459 1459 }
1460 1460 error : null
1461 1461
1462 1462 Example error output:
1463 1463
1464 1464 .. code-block:: bash
1465 1465
1466 1466 id : <id_given_in_input>
1467 1467 result : null
1468 1468 error : {
1469 1469 'Error occurred locking repository `<reponame>`'
1470 1470 }
1471 1471 """
1472 1472
1473 1473 repo = get_repo_or_error(repoid)
1474 1474 if not has_superadmin_permission(apiuser):
1475 1475 # check if we have at least write permission for this repo !
1476 1476 _perms = ('repository.admin', 'repository.write',)
1477 1477 validate_repo_permissions(apiuser, repoid, repo, _perms)
1478 1478
1479 1479 # make sure normal user does not pass someone else userid,
1480 1480 # he is not allowed to do that
1481 1481 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1482 1482 raise JSONRPCError('userid is not the same as your user')
1483 1483
1484 1484 if isinstance(userid, Optional):
1485 1485 userid = apiuser.user_id
1486 1486
1487 1487 user = get_user_or_error(userid)
1488 1488
1489 1489 if isinstance(locked, Optional):
1490 1490 lockobj = repo.locked
1491 1491
1492 1492 if lockobj[0] is None:
1493 1493 _d = {
1494 1494 'repo': repo.repo_name,
1495 1495 'locked': False,
1496 1496 'locked_since': None,
1497 1497 'locked_by': None,
1498 1498 'lock_reason': None,
1499 1499 'lock_state_changed': False,
1500 1500 'msg': 'Repo `%s` not locked.' % repo.repo_name
1501 1501 }
1502 1502 return _d
1503 1503 else:
1504 1504 _user_id, _time, _reason = lockobj
1505 1505 lock_user = get_user_or_error(userid)
1506 1506 _d = {
1507 1507 'repo': repo.repo_name,
1508 1508 'locked': True,
1509 1509 'locked_since': _time,
1510 1510 'locked_by': lock_user.username,
1511 1511 'lock_reason': _reason,
1512 1512 'lock_state_changed': False,
1513 1513 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1514 1514 % (repo.repo_name, lock_user.username,
1515 1515 json.dumps(time_to_datetime(_time))))
1516 1516 }
1517 1517 return _d
1518 1518
1519 1519 # force locked state through a flag
1520 1520 else:
1521 1521 locked = str2bool(locked)
1522 1522 lock_reason = Repository.LOCK_API
1523 1523 try:
1524 1524 if locked:
1525 1525 lock_time = time.time()
1526 1526 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1527 1527 else:
1528 1528 lock_time = None
1529 1529 Repository.unlock(repo)
1530 1530 _d = {
1531 1531 'repo': repo.repo_name,
1532 1532 'locked': locked,
1533 1533 'locked_since': lock_time,
1534 1534 'locked_by': user.username,
1535 1535 'lock_reason': lock_reason,
1536 1536 'lock_state_changed': True,
1537 1537 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1538 1538 % (user.username, repo.repo_name, locked))
1539 1539 }
1540 1540 return _d
1541 1541 except Exception:
1542 1542 log.exception(
1543 1543 "Exception occurred while trying to lock repository")
1544 1544 raise JSONRPCError(
1545 1545 'Error occurred locking repository `%s`' % repo.repo_name
1546 1546 )
1547 1547
1548 1548
1549 1549 @jsonrpc_method()
1550 1550 def comment_commit(
1551 1551 request, apiuser, repoid, commit_id, message, status=Optional(None),
1552 1552 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1553 1553 resolves_comment_id=Optional(None),
1554 1554 userid=Optional(OAttr('apiuser'))):
1555 1555 """
1556 1556 Set a commit comment, and optionally change the status of the commit.
1557 1557
1558 1558 :param apiuser: This is filled automatically from the |authtoken|.
1559 1559 :type apiuser: AuthUser
1560 1560 :param repoid: Set the repository name or repository ID.
1561 1561 :type repoid: str or int
1562 1562 :param commit_id: Specify the commit_id for which to set a comment.
1563 1563 :type commit_id: str
1564 1564 :param message: The comment text.
1565 1565 :type message: str
1566 1566 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1567 1567 'approved', 'rejected', 'under_review'
1568 1568 :type status: str
1569 1569 :param comment_type: Comment type, one of: 'note', 'todo'
1570 1570 :type comment_type: Optional(str), default: 'note'
1571 1571 :param userid: Set the user name of the comment creator.
1572 1572 :type userid: Optional(str or int)
1573 1573
1574 1574 Example error output:
1575 1575
1576 1576 .. code-block:: bash
1577 1577
1578 1578 {
1579 1579 "id" : <id_given_in_input>,
1580 1580 "result" : {
1581 1581 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1582 1582 "status_change": null or <status>,
1583 1583 "success": true
1584 1584 },
1585 1585 "error" : null
1586 1586 }
1587 1587
1588 1588 """
1589 1589 repo = get_repo_or_error(repoid)
1590 1590 if not has_superadmin_permission(apiuser):
1591 1591 _perms = ('repository.read', 'repository.write', 'repository.admin')
1592 1592 validate_repo_permissions(apiuser, repoid, repo, _perms)
1593 1593
1594 1594 try:
1595 1595 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1596 1596 except Exception as e:
1597 1597 log.exception('Failed to fetch commit')
1598 1598 raise JSONRPCError(safe_str(e))
1599 1599
1600 1600 if isinstance(userid, Optional):
1601 1601 userid = apiuser.user_id
1602 1602
1603 1603 user = get_user_or_error(userid)
1604 1604 status = Optional.extract(status)
1605 1605 comment_type = Optional.extract(comment_type)
1606 1606 resolves_comment_id = Optional.extract(resolves_comment_id)
1607 1607
1608 1608 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1609 1609 if status and status not in allowed_statuses:
1610 1610 raise JSONRPCError('Bad status, must be on '
1611 1611 'of %s got %s' % (allowed_statuses, status,))
1612 1612
1613 1613 if resolves_comment_id:
1614 1614 comment = ChangesetComment.get(resolves_comment_id)
1615 1615 if not comment:
1616 1616 raise JSONRPCError(
1617 1617 'Invalid resolves_comment_id `%s` for this commit.'
1618 1618 % resolves_comment_id)
1619 1619 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1620 1620 raise JSONRPCError(
1621 1621 'Comment `%s` is wrong type for setting status to resolved.'
1622 1622 % resolves_comment_id)
1623 1623
1624 1624 try:
1625 1625 rc_config = SettingsModel().get_all_settings()
1626 1626 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1627 1627 status_change_label = ChangesetStatus.get_status_lbl(status)
1628 1628 comment = CommentsModel().create(
1629 1629 message, repo, user, commit_id=commit_id,
1630 1630 status_change=status_change_label,
1631 1631 status_change_type=status,
1632 1632 renderer=renderer,
1633 1633 comment_type=comment_type,
1634 1634 resolves_comment_id=resolves_comment_id,
1635 1635 auth_user=apiuser
1636 1636 )
1637 1637 if status:
1638 1638 # also do a status change
1639 1639 try:
1640 1640 ChangesetStatusModel().set_status(
1641 1641 repo, status, user, comment, revision=commit_id,
1642 1642 dont_allow_on_closed_pull_request=True
1643 1643 )
1644 1644 except StatusChangeOnClosedPullRequestError:
1645 1645 log.exception(
1646 1646 "Exception occurred while trying to change repo commit status")
1647 1647 msg = ('Changing status on a changeset associated with '
1648 1648 'a closed pull request is not allowed')
1649 1649 raise JSONRPCError(msg)
1650 1650
1651 1651 Session().commit()
1652 1652 return {
1653 1653 'msg': (
1654 1654 'Commented on commit `%s` for repository `%s`' % (
1655 1655 comment.revision, repo.repo_name)),
1656 1656 'status_change': status,
1657 1657 'success': True,
1658 1658 }
1659 1659 except JSONRPCError:
1660 1660 # catch any inside errors, and re-raise them to prevent from
1661 1661 # below global catch to silence them
1662 1662 raise
1663 1663 except Exception:
1664 1664 log.exception("Exception occurred while trying to comment on commit")
1665 1665 raise JSONRPCError(
1666 1666 'failed to set comment on repository `%s`' % (repo.repo_name,)
1667 1667 )
1668 1668
1669 1669
1670 1670 @jsonrpc_method()
1671 1671 def get_repo_comments(request, apiuser, repoid,
1672 1672 commit_id=Optional(None), comment_type=Optional(None),
1673 1673 userid=Optional(None)):
1674 1674 """
1675 1675 Get all comments for a repository
1676 1676
1677 1677 :param apiuser: This is filled automatically from the |authtoken|.
1678 1678 :type apiuser: AuthUser
1679 1679 :param repoid: Set the repository name or repository ID.
1680 1680 :type repoid: str or int
1681 1681 :param commit_id: Optionally filter the comments by the commit_id
1682 1682 :type commit_id: Optional(str), default: None
1683 1683 :param comment_type: Optionally filter the comments by the comment_type
1684 1684 one of: 'note', 'todo'
1685 1685 :type comment_type: Optional(str), default: None
1686 1686 :param userid: Optionally filter the comments by the author of comment
1687 1687 :type userid: Optional(str or int), Default: None
1688 1688
1689 1689 Example error output:
1690 1690
1691 1691 .. code-block:: bash
1692 1692
1693 1693 {
1694 1694 "id" : <id_given_in_input>,
1695 1695 "result" : [
1696 1696 {
1697 1697 "comment_author": <USER_DETAILS>,
1698 1698 "comment_created_on": "2017-02-01T14:38:16.309",
1699 1699 "comment_f_path": "file.txt",
1700 1700 "comment_id": 282,
1701 1701 "comment_lineno": "n1",
1702 1702 "comment_resolved_by": null,
1703 1703 "comment_status": [],
1704 1704 "comment_text": "This file needs a header",
1705 1705 "comment_type": "todo"
1706 1706 }
1707 1707 ],
1708 1708 "error" : null
1709 1709 }
1710 1710
1711 1711 """
1712 1712 repo = get_repo_or_error(repoid)
1713 1713 if not has_superadmin_permission(apiuser):
1714 1714 _perms = ('repository.read', 'repository.write', 'repository.admin')
1715 1715 validate_repo_permissions(apiuser, repoid, repo, _perms)
1716 1716
1717 1717 commit_id = Optional.extract(commit_id)
1718 1718
1719 1719 userid = Optional.extract(userid)
1720 1720 if userid:
1721 1721 user = get_user_or_error(userid)
1722 1722 else:
1723 1723 user = None
1724 1724
1725 1725 comment_type = Optional.extract(comment_type)
1726 1726 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1727 1727 raise JSONRPCError(
1728 1728 'comment_type must be one of `{}` got {}'.format(
1729 1729 ChangesetComment.COMMENT_TYPES, comment_type)
1730 1730 )
1731 1731
1732 1732 comments = CommentsModel().get_repository_comments(
1733 1733 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1734 1734 return comments
1735 1735
1736 1736
1737 1737 @jsonrpc_method()
1738 1738 def grant_user_permission(request, apiuser, repoid, userid, perm):
1739 1739 """
1740 1740 Grant permissions for the specified user on the given repository,
1741 1741 or update existing permissions if found.
1742 1742
1743 1743 This command can only be run using an |authtoken| with admin
1744 1744 permissions on the |repo|.
1745 1745
1746 1746 :param apiuser: This is filled automatically from the |authtoken|.
1747 1747 :type apiuser: AuthUser
1748 1748 :param repoid: Set the repository name or repository ID.
1749 1749 :type repoid: str or int
1750 1750 :param userid: Set the user name.
1751 1751 :type userid: str
1752 1752 :param perm: Set the user permissions, using the following format
1753 1753 ``(repository.(none|read|write|admin))``
1754 1754 :type perm: str
1755 1755
1756 1756 Example output:
1757 1757
1758 1758 .. code-block:: bash
1759 1759
1760 1760 id : <id_given_in_input>
1761 1761 result: {
1762 1762 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1763 1763 "success": true
1764 1764 }
1765 1765 error: null
1766 1766 """
1767 1767
1768 1768 repo = get_repo_or_error(repoid)
1769 1769 user = get_user_or_error(userid)
1770 1770 perm = get_perm_or_error(perm)
1771 1771 if not has_superadmin_permission(apiuser):
1772 1772 _perms = ('repository.admin',)
1773 1773 validate_repo_permissions(apiuser, repoid, repo, _perms)
1774 1774
1775 1775 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1776 1776 try:
1777 1777 changes = RepoModel().update_permissions(
1778 1778 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1779 1779
1780 1780 action_data = {
1781 1781 'added': changes['added'],
1782 1782 'updated': changes['updated'],
1783 1783 'deleted': changes['deleted'],
1784 1784 }
1785 1785 audit_logger.store_api(
1786 1786 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1787 1787 Session().commit()
1788 1788 PermissionModel().flush_user_permission_caches(changes)
1789 1789
1790 1790 return {
1791 1791 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1792 1792 perm.permission_name, user.username, repo.repo_name
1793 1793 ),
1794 1794 'success': True
1795 1795 }
1796 1796 except Exception:
1797 1797 log.exception("Exception occurred while trying edit permissions for repo")
1798 1798 raise JSONRPCError(
1799 1799 'failed to edit permission for user: `%s` in repo: `%s`' % (
1800 1800 userid, repoid
1801 1801 )
1802 1802 )
1803 1803
1804 1804
1805 1805 @jsonrpc_method()
1806 1806 def revoke_user_permission(request, apiuser, repoid, userid):
1807 1807 """
1808 1808 Revoke permission for a user on the specified repository.
1809 1809
1810 1810 This command can only be run using an |authtoken| with admin
1811 1811 permissions on the |repo|.
1812 1812
1813 1813 :param apiuser: This is filled automatically from the |authtoken|.
1814 1814 :type apiuser: AuthUser
1815 1815 :param repoid: Set the repository name or repository ID.
1816 1816 :type repoid: str or int
1817 1817 :param userid: Set the user name of revoked user.
1818 1818 :type userid: str or int
1819 1819
1820 1820 Example error output:
1821 1821
1822 1822 .. code-block:: bash
1823 1823
1824 1824 id : <id_given_in_input>
1825 1825 result: {
1826 1826 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1827 1827 "success": true
1828 1828 }
1829 1829 error: null
1830 1830 """
1831 1831
1832 1832 repo = get_repo_or_error(repoid)
1833 1833 user = get_user_or_error(userid)
1834 1834 if not has_superadmin_permission(apiuser):
1835 1835 _perms = ('repository.admin',)
1836 1836 validate_repo_permissions(apiuser, repoid, repo, _perms)
1837 1837
1838 1838 perm_deletions = [[user.user_id, None, "user"]]
1839 1839 try:
1840 1840 changes = RepoModel().update_permissions(
1841 1841 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1842 1842
1843 1843 action_data = {
1844 1844 'added': changes['added'],
1845 1845 'updated': changes['updated'],
1846 1846 'deleted': changes['deleted'],
1847 1847 }
1848 1848 audit_logger.store_api(
1849 1849 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1850 1850 Session().commit()
1851 1851 PermissionModel().flush_user_permission_caches(changes)
1852 1852
1853 1853 return {
1854 1854 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1855 1855 user.username, repo.repo_name
1856 1856 ),
1857 1857 'success': True
1858 1858 }
1859 1859 except Exception:
1860 1860 log.exception("Exception occurred while trying revoke permissions to repo")
1861 1861 raise JSONRPCError(
1862 1862 'failed to edit permission for user: `%s` in repo: `%s`' % (
1863 1863 userid, repoid
1864 1864 )
1865 1865 )
1866 1866
1867 1867
1868 1868 @jsonrpc_method()
1869 1869 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1870 1870 """
1871 1871 Grant permission for a user group on the specified repository,
1872 1872 or update existing permissions.
1873 1873
1874 1874 This command can only be run using an |authtoken| with admin
1875 1875 permissions on the |repo|.
1876 1876
1877 1877 :param apiuser: This is filled automatically from the |authtoken|.
1878 1878 :type apiuser: AuthUser
1879 1879 :param repoid: Set the repository name or repository ID.
1880 1880 :type repoid: str or int
1881 1881 :param usergroupid: Specify the ID of the user group.
1882 1882 :type usergroupid: str or int
1883 1883 :param perm: Set the user group permissions using the following
1884 1884 format: (repository.(none|read|write|admin))
1885 1885 :type perm: str
1886 1886
1887 1887 Example output:
1888 1888
1889 1889 .. code-block:: bash
1890 1890
1891 1891 id : <id_given_in_input>
1892 1892 result : {
1893 1893 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1894 1894 "success": true
1895 1895
1896 1896 }
1897 1897 error : null
1898 1898
1899 1899 Example error output:
1900 1900
1901 1901 .. code-block:: bash
1902 1902
1903 1903 id : <id_given_in_input>
1904 1904 result : null
1905 1905 error : {
1906 1906 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1907 1907 }
1908 1908
1909 1909 """
1910 1910
1911 1911 repo = get_repo_or_error(repoid)
1912 1912 perm = get_perm_or_error(perm)
1913 1913 if not has_superadmin_permission(apiuser):
1914 1914 _perms = ('repository.admin',)
1915 1915 validate_repo_permissions(apiuser, repoid, repo, _perms)
1916 1916
1917 1917 user_group = get_user_group_or_error(usergroupid)
1918 1918 if not has_superadmin_permission(apiuser):
1919 1919 # check if we have at least read permission for this user group !
1920 1920 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1921 1921 if not HasUserGroupPermissionAnyApi(*_perms)(
1922 1922 user=apiuser, user_group_name=user_group.users_group_name):
1923 1923 raise JSONRPCError(
1924 1924 'user group `%s` does not exist' % (usergroupid,))
1925 1925
1926 1926 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1927 1927 try:
1928 1928 changes = RepoModel().update_permissions(
1929 1929 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1930 1930 action_data = {
1931 1931 'added': changes['added'],
1932 1932 'updated': changes['updated'],
1933 1933 'deleted': changes['deleted'],
1934 1934 }
1935 1935 audit_logger.store_api(
1936 1936 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1937 1937 Session().commit()
1938 1938 PermissionModel().flush_user_permission_caches(changes)
1939 1939
1940 1940 return {
1941 1941 'msg': 'Granted perm: `%s` for user group: `%s` in '
1942 1942 'repo: `%s`' % (
1943 1943 perm.permission_name, user_group.users_group_name,
1944 1944 repo.repo_name
1945 1945 ),
1946 1946 'success': True
1947 1947 }
1948 1948 except Exception:
1949 1949 log.exception(
1950 1950 "Exception occurred while trying change permission on repo")
1951 1951 raise JSONRPCError(
1952 1952 'failed to edit permission for user group: `%s` in '
1953 1953 'repo: `%s`' % (
1954 1954 usergroupid, repo.repo_name
1955 1955 )
1956 1956 )
1957 1957
1958 1958
1959 1959 @jsonrpc_method()
1960 1960 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1961 1961 """
1962 1962 Revoke the permissions of a user group on a given repository.
1963 1963
1964 1964 This command can only be run using an |authtoken| with admin
1965 1965 permissions on the |repo|.
1966 1966
1967 1967 :param apiuser: This is filled automatically from the |authtoken|.
1968 1968 :type apiuser: AuthUser
1969 1969 :param repoid: Set the repository name or repository ID.
1970 1970 :type repoid: str or int
1971 1971 :param usergroupid: Specify the user group ID.
1972 1972 :type usergroupid: str or int
1973 1973
1974 1974 Example output:
1975 1975
1976 1976 .. code-block:: bash
1977 1977
1978 1978 id : <id_given_in_input>
1979 1979 result: {
1980 1980 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1981 1981 "success": true
1982 1982 }
1983 1983 error: null
1984 1984 """
1985 1985
1986 1986 repo = get_repo_or_error(repoid)
1987 1987 if not has_superadmin_permission(apiuser):
1988 1988 _perms = ('repository.admin',)
1989 1989 validate_repo_permissions(apiuser, repoid, repo, _perms)
1990 1990
1991 1991 user_group = get_user_group_or_error(usergroupid)
1992 1992 if not has_superadmin_permission(apiuser):
1993 1993 # check if we have at least read permission for this user group !
1994 1994 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1995 1995 if not HasUserGroupPermissionAnyApi(*_perms)(
1996 1996 user=apiuser, user_group_name=user_group.users_group_name):
1997 1997 raise JSONRPCError(
1998 1998 'user group `%s` does not exist' % (usergroupid,))
1999 1999
2000 2000 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2001 2001 try:
2002 2002 changes = RepoModel().update_permissions(
2003 2003 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2004 2004 action_data = {
2005 2005 'added': changes['added'],
2006 2006 'updated': changes['updated'],
2007 2007 'deleted': changes['deleted'],
2008 2008 }
2009 2009 audit_logger.store_api(
2010 2010 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2011 2011 Session().commit()
2012 2012 PermissionModel().flush_user_permission_caches(changes)
2013 2013
2014 2014 return {
2015 2015 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2016 2016 user_group.users_group_name, repo.repo_name
2017 2017 ),
2018 2018 'success': True
2019 2019 }
2020 2020 except Exception:
2021 2021 log.exception("Exception occurred while trying revoke "
2022 2022 "user group permission on repo")
2023 2023 raise JSONRPCError(
2024 2024 'failed to edit permission for user group: `%s` in '
2025 2025 'repo: `%s`' % (
2026 2026 user_group.users_group_name, repo.repo_name
2027 2027 )
2028 2028 )
2029 2029
2030 2030
2031 2031 @jsonrpc_method()
2032 2032 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2033 2033 """
2034 2034 Triggers a pull on the given repository from a remote location. You
2035 2035 can use this to keep remote repositories up-to-date.
2036 2036
2037 2037 This command can only be run using an |authtoken| with admin
2038 2038 rights to the specified repository. For more information,
2039 2039 see :ref:`config-token-ref`.
2040 2040
2041 2041 This command takes the following options:
2042 2042
2043 2043 :param apiuser: This is filled automatically from the |authtoken|.
2044 2044 :type apiuser: AuthUser
2045 2045 :param repoid: The repository name or repository ID.
2046 2046 :type repoid: str or int
2047 2047 :param remote_uri: Optional remote URI to pass in for pull
2048 2048 :type remote_uri: str
2049 2049
2050 2050 Example output:
2051 2051
2052 2052 .. code-block:: bash
2053 2053
2054 2054 id : <id_given_in_input>
2055 2055 result : {
2056 2056 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2057 2057 "repository": "<repository name>"
2058 2058 }
2059 2059 error : null
2060 2060
2061 2061 Example error output:
2062 2062
2063 2063 .. code-block:: bash
2064 2064
2065 2065 id : <id_given_in_input>
2066 2066 result : null
2067 2067 error : {
2068 2068 "Unable to push changes from `<remote_url>`"
2069 2069 }
2070 2070
2071 2071 """
2072 2072
2073 2073 repo = get_repo_or_error(repoid)
2074 2074 remote_uri = Optional.extract(remote_uri)
2075 2075 remote_uri_display = remote_uri or repo.clone_uri_hidden
2076 2076 if not has_superadmin_permission(apiuser):
2077 2077 _perms = ('repository.admin',)
2078 2078 validate_repo_permissions(apiuser, repoid, repo, _perms)
2079 2079
2080 2080 try:
2081 2081 ScmModel().pull_changes(
2082 2082 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2083 2083 return {
2084 2084 'msg': 'Pulled from url `%s` on repo `%s`' % (
2085 2085 remote_uri_display, repo.repo_name),
2086 2086 'repository': repo.repo_name
2087 2087 }
2088 2088 except Exception:
2089 2089 log.exception("Exception occurred while trying to "
2090 2090 "pull changes from remote location")
2091 2091 raise JSONRPCError(
2092 2092 'Unable to pull changes from `%s`' % remote_uri_display
2093 2093 )
2094 2094
2095 2095
2096 2096 @jsonrpc_method()
2097 2097 def strip(request, apiuser, repoid, revision, branch):
2098 2098 """
2099 2099 Strips the given revision from the specified repository.
2100 2100
2101 2101 * This will remove the revision and all of its decendants.
2102 2102
2103 2103 This command can only be run using an |authtoken| with admin rights to
2104 2104 the specified repository.
2105 2105
2106 2106 This command takes the following options:
2107 2107
2108 2108 :param apiuser: This is filled automatically from the |authtoken|.
2109 2109 :type apiuser: AuthUser
2110 2110 :param repoid: The repository name or repository ID.
2111 2111 :type repoid: str or int
2112 2112 :param revision: The revision you wish to strip.
2113 2113 :type revision: str
2114 2114 :param branch: The branch from which to strip the revision.
2115 2115 :type branch: str
2116 2116
2117 2117 Example output:
2118 2118
2119 2119 .. code-block:: bash
2120 2120
2121 2121 id : <id_given_in_input>
2122 2122 result : {
2123 2123 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2124 2124 "repository": "<repository name>"
2125 2125 }
2126 2126 error : null
2127 2127
2128 2128 Example error output:
2129 2129
2130 2130 .. code-block:: bash
2131 2131
2132 2132 id : <id_given_in_input>
2133 2133 result : null
2134 2134 error : {
2135 2135 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2136 2136 }
2137 2137
2138 2138 """
2139 2139
2140 2140 repo = get_repo_or_error(repoid)
2141 2141 if not has_superadmin_permission(apiuser):
2142 2142 _perms = ('repository.admin',)
2143 2143 validate_repo_permissions(apiuser, repoid, repo, _perms)
2144 2144
2145 2145 try:
2146 2146 ScmModel().strip(repo, revision, branch)
2147 2147 audit_logger.store_api(
2148 2148 'repo.commit.strip', action_data={'commit_id': revision},
2149 2149 repo=repo,
2150 2150 user=apiuser, commit=True)
2151 2151
2152 2152 return {
2153 2153 'msg': 'Stripped commit %s from repo `%s`' % (
2154 2154 revision, repo.repo_name),
2155 2155 'repository': repo.repo_name
2156 2156 }
2157 2157 except Exception:
2158 2158 log.exception("Exception while trying to strip")
2159 2159 raise JSONRPCError(
2160 2160 'Unable to strip commit %s from repo `%s`' % (
2161 2161 revision, repo.repo_name)
2162 2162 )
2163 2163
2164 2164
2165 2165 @jsonrpc_method()
2166 2166 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2167 2167 """
2168 2168 Returns all settings for a repository. If key is given it only returns the
2169 2169 setting identified by the key or null.
2170 2170
2171 2171 :param apiuser: This is filled automatically from the |authtoken|.
2172 2172 :type apiuser: AuthUser
2173 2173 :param repoid: The repository name or repository id.
2174 2174 :type repoid: str or int
2175 2175 :param key: Key of the setting to return.
2176 2176 :type: key: Optional(str)
2177 2177
2178 2178 Example output:
2179 2179
2180 2180 .. code-block:: bash
2181 2181
2182 2182 {
2183 2183 "error": null,
2184 2184 "id": 237,
2185 2185 "result": {
2186 2186 "extensions_largefiles": true,
2187 2187 "extensions_evolve": true,
2188 2188 "hooks_changegroup_push_logger": true,
2189 2189 "hooks_changegroup_repo_size": false,
2190 2190 "hooks_outgoing_pull_logger": true,
2191 2191 "phases_publish": "True",
2192 2192 "rhodecode_hg_use_rebase_for_merging": true,
2193 2193 "rhodecode_pr_merge_enabled": true,
2194 2194 "rhodecode_use_outdated_comments": true
2195 2195 }
2196 2196 }
2197 2197 """
2198 2198
2199 2199 # Restrict access to this api method to admins only.
2200 2200 if not has_superadmin_permission(apiuser):
2201 2201 raise JSONRPCForbidden()
2202 2202
2203 2203 try:
2204 2204 repo = get_repo_or_error(repoid)
2205 2205 settings_model = VcsSettingsModel(repo=repo)
2206 2206 settings = settings_model.get_global_settings()
2207 2207 settings.update(settings_model.get_repo_settings())
2208 2208
2209 2209 # If only a single setting is requested fetch it from all settings.
2210 2210 key = Optional.extract(key)
2211 2211 if key is not None:
2212 2212 settings = settings.get(key, None)
2213 2213 except Exception:
2214 2214 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2215 2215 log.exception(msg)
2216 2216 raise JSONRPCError(msg)
2217 2217
2218 2218 return settings
2219 2219
2220 2220
2221 2221 @jsonrpc_method()
2222 2222 def set_repo_settings(request, apiuser, repoid, settings):
2223 2223 """
2224 2224 Update repository settings. Returns true on success.
2225 2225
2226 2226 :param apiuser: This is filled automatically from the |authtoken|.
2227 2227 :type apiuser: AuthUser
2228 2228 :param repoid: The repository name or repository id.
2229 2229 :type repoid: str or int
2230 2230 :param settings: The new settings for the repository.
2231 2231 :type: settings: dict
2232 2232
2233 2233 Example output:
2234 2234
2235 2235 .. code-block:: bash
2236 2236
2237 2237 {
2238 2238 "error": null,
2239 2239 "id": 237,
2240 2240 "result": true
2241 2241 }
2242 2242 """
2243 2243 # Restrict access to this api method to admins only.
2244 2244 if not has_superadmin_permission(apiuser):
2245 2245 raise JSONRPCForbidden()
2246 2246
2247 2247 if type(settings) is not dict:
2248 2248 raise JSONRPCError('Settings have to be a JSON Object.')
2249 2249
2250 2250 try:
2251 2251 settings_model = VcsSettingsModel(repo=repoid)
2252 2252
2253 2253 # Merge global, repo and incoming settings.
2254 2254 new_settings = settings_model.get_global_settings()
2255 2255 new_settings.update(settings_model.get_repo_settings())
2256 2256 new_settings.update(settings)
2257 2257
2258 2258 # Update the settings.
2259 2259 inherit_global_settings = new_settings.get(
2260 2260 'inherit_global_settings', False)
2261 2261 settings_model.create_or_update_repo_settings(
2262 2262 new_settings, inherit_global_settings=inherit_global_settings)
2263 2263 Session().commit()
2264 2264 except Exception:
2265 2265 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2266 2266 log.exception(msg)
2267 2267 raise JSONRPCError(msg)
2268 2268
2269 2269 # Indicate success.
2270 2270 return True
2271 2271
2272 2272
2273 2273 @jsonrpc_method()
2274 2274 def maintenance(request, apiuser, repoid):
2275 2275 """
2276 2276 Triggers a maintenance on the given repository.
2277 2277
2278 2278 This command can only be run using an |authtoken| with admin
2279 2279 rights to the specified repository. For more information,
2280 2280 see :ref:`config-token-ref`.
2281 2281
2282 2282 This command takes the following options:
2283 2283
2284 2284 :param apiuser: This is filled automatically from the |authtoken|.
2285 2285 :type apiuser: AuthUser
2286 2286 :param repoid: The repository name or repository ID.
2287 2287 :type repoid: str or int
2288 2288
2289 2289 Example output:
2290 2290
2291 2291 .. code-block:: bash
2292 2292
2293 2293 id : <id_given_in_input>
2294 2294 result : {
2295 2295 "msg": "executed maintenance command",
2296 2296 "executed_actions": [
2297 2297 <action_message>, <action_message2>...
2298 2298 ],
2299 2299 "repository": "<repository name>"
2300 2300 }
2301 2301 error : null
2302 2302
2303 2303 Example error output:
2304 2304
2305 2305 .. code-block:: bash
2306 2306
2307 2307 id : <id_given_in_input>
2308 2308 result : null
2309 2309 error : {
2310 2310 "Unable to execute maintenance on `<reponame>`"
2311 2311 }
2312 2312
2313 2313 """
2314 2314
2315 2315 repo = get_repo_or_error(repoid)
2316 2316 if not has_superadmin_permission(apiuser):
2317 2317 _perms = ('repository.admin',)
2318 2318 validate_repo_permissions(apiuser, repoid, repo, _perms)
2319 2319
2320 2320 try:
2321 2321 maintenance = repo_maintenance.RepoMaintenance()
2322 2322 executed_actions = maintenance.execute(repo)
2323 2323
2324 2324 return {
2325 2325 'msg': 'executed maintenance command',
2326 2326 'executed_actions': executed_actions,
2327 2327 'repository': repo.repo_name
2328 2328 }
2329 2329 except Exception:
2330 2330 log.exception("Exception occurred while trying to run maintenance")
2331 2331 raise JSONRPCError(
2332 2332 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,1551 +1,1552 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 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 itertools
22 22 import logging
23 23 import os
24 24 import shutil
25 25 import tempfile
26 26 import collections
27 27 import urllib
28 28 import pathlib2
29 29
30 30 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
31 31 from pyramid.view import view_config
32 32 from pyramid.renderers import render
33 33 from pyramid.response import Response
34 34
35 35 import rhodecode
36 36 from rhodecode.apps._base import RepoAppView
37 37
38 38
39 39 from rhodecode.lib import diffs, helpers as h, rc_cache
40 40 from rhodecode.lib import audit_logger
41 41 from rhodecode.lib.view_utils import parse_path_ref
42 42 from rhodecode.lib.exceptions import NonRelativePathError
43 43 from rhodecode.lib.codeblocks import (
44 44 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
45 45 from rhodecode.lib.utils2 import (
46 46 convert_line_endings, detect_mode, safe_str, str2bool, safe_int, sha1, safe_unicode)
47 47 from rhodecode.lib.auth import (
48 48 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
49 49 from rhodecode.lib.vcs import path as vcspath
50 50 from rhodecode.lib.vcs.backends.base import EmptyCommit
51 51 from rhodecode.lib.vcs.conf import settings
52 52 from rhodecode.lib.vcs.nodes import FileNode
53 53 from rhodecode.lib.vcs.exceptions import (
54 54 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
55 55 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
56 56 NodeDoesNotExistError, CommitError, NodeError)
57 57
58 58 from rhodecode.model.scm import ScmModel
59 59 from rhodecode.model.db import Repository
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63
64 64 class RepoFilesView(RepoAppView):
65 65
66 66 @staticmethod
67 67 def adjust_file_path_for_svn(f_path, repo):
68 68 """
69 69 Computes the relative path of `f_path`.
70 70
71 71 This is mainly based on prefix matching of the recognized tags and
72 72 branches in the underlying repository.
73 73 """
74 74 tags_and_branches = itertools.chain(
75 75 repo.branches.iterkeys(),
76 76 repo.tags.iterkeys())
77 77 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
78 78
79 79 for name in tags_and_branches:
80 80 if f_path.startswith('{}/'.format(name)):
81 81 f_path = vcspath.relpath(f_path, name)
82 82 break
83 83 return f_path
84 84
85 85 def load_default_context(self):
86 86 c = self._get_local_tmpl_context(include_app_defaults=True)
87 87 c.rhodecode_repo = self.rhodecode_vcs_repo
88 88 c.enable_downloads = self.db_repo.enable_downloads
89 89 return c
90 90
91 91 def _ensure_not_locked(self, commit_id='tip'):
92 92 _ = self.request.translate
93 93
94 94 repo = self.db_repo
95 95 if repo.enable_locking and repo.locked[0]:
96 96 h.flash(_('This repository has been locked by %s on %s')
97 97 % (h.person_by_id(repo.locked[0]),
98 98 h.format_date(h.time_to_datetime(repo.locked[1]))),
99 99 'warning')
100 100 files_url = h.route_path(
101 101 'repo_files:default_path',
102 102 repo_name=self.db_repo_name, commit_id=commit_id)
103 103 raise HTTPFound(files_url)
104 104
105 105 def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
106 106 _ = self.request.translate
107 107
108 108 if not is_head:
109 109 message = _('Cannot modify file. '
110 110 'Given commit `{}` is not head of a branch.').format(commit_id)
111 111 h.flash(message, category='warning')
112 112
113 113 if json_mode:
114 114 return message
115 115
116 116 files_url = h.route_path(
117 117 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
118 118 f_path=f_path)
119 119 raise HTTPFound(files_url)
120 120
121 121 def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
122 122 _ = self.request.translate
123 123
124 124 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
125 125 self.db_repo_name, branch_name)
126 126 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
127 127 message = _('Branch `{}` changes forbidden by rule {}.').format(
128 128 branch_name, rule)
129 129 h.flash(message, 'warning')
130 130
131 131 if json_mode:
132 132 return message
133 133
134 134 files_url = h.route_path(
135 135 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
136 136
137 137 raise HTTPFound(files_url)
138 138
139 139 def _get_commit_and_path(self):
140 140 default_commit_id = self.db_repo.landing_rev[1]
141 141 default_f_path = '/'
142 142
143 143 commit_id = self.request.matchdict.get(
144 144 'commit_id', default_commit_id)
145 145 f_path = self._get_f_path(self.request.matchdict, default_f_path)
146 146 return commit_id, f_path
147 147
148 148 def _get_default_encoding(self, c):
149 149 enc_list = getattr(c, 'default_encodings', [])
150 150 return enc_list[0] if enc_list else 'UTF-8'
151 151
152 152 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
153 153 """
154 154 This is a safe way to get commit. If an error occurs it redirects to
155 155 tip with proper message
156 156
157 157 :param commit_id: id of commit to fetch
158 158 :param redirect_after: toggle redirection
159 159 """
160 160 _ = self.request.translate
161 161
162 162 try:
163 163 return self.rhodecode_vcs_repo.get_commit(commit_id)
164 164 except EmptyRepositoryError:
165 165 if not redirect_after:
166 166 return None
167 167
168 168 _url = h.route_path(
169 169 'repo_files_add_file',
170 170 repo_name=self.db_repo_name, commit_id=0, f_path='')
171 171
172 172 if h.HasRepoPermissionAny(
173 173 'repository.write', 'repository.admin')(self.db_repo_name):
174 174 add_new = h.link_to(
175 175 _('Click here to add a new file.'), _url, class_="alert-link")
176 176 else:
177 177 add_new = ""
178 178
179 179 h.flash(h.literal(
180 180 _('There are no files yet. %s') % add_new), category='warning')
181 181 raise HTTPFound(
182 182 h.route_path('repo_summary', repo_name=self.db_repo_name))
183 183
184 184 except (CommitDoesNotExistError, LookupError):
185 185 msg = _('No such commit exists for this repository')
186 186 h.flash(msg, category='error')
187 187 raise HTTPNotFound()
188 188 except RepositoryError as e:
189 189 h.flash(safe_str(h.escape(e)), category='error')
190 190 raise HTTPNotFound()
191 191
192 192 def _get_filenode_or_redirect(self, commit_obj, path):
193 193 """
194 194 Returns file_node, if error occurs or given path is directory,
195 195 it'll redirect to top level path
196 196 """
197 197 _ = self.request.translate
198 198
199 199 try:
200 200 file_node = commit_obj.get_node(path)
201 201 if file_node.is_dir():
202 202 raise RepositoryError('The given path is a directory')
203 203 except CommitDoesNotExistError:
204 204 log.exception('No such commit exists for this repository')
205 205 h.flash(_('No such commit exists for this repository'), category='error')
206 206 raise HTTPNotFound()
207 207 except RepositoryError as e:
208 208 log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
209 209 h.flash(safe_str(h.escape(e)), category='error')
210 210 raise HTTPNotFound()
211 211
212 212 return file_node
213 213
214 214 def _is_valid_head(self, commit_id, repo):
215 215 branch_name = sha_commit_id = ''
216 216 is_head = False
217 217 log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
218 218
219 219 for _branch_name, branch_commit_id in repo.branches.items():
220 220 # simple case we pass in branch name, it's a HEAD
221 221 if commit_id == _branch_name:
222 222 is_head = True
223 223 branch_name = _branch_name
224 224 sha_commit_id = branch_commit_id
225 225 break
226 226 # case when we pass in full sha commit_id, which is a head
227 227 elif commit_id == branch_commit_id:
228 228 is_head = True
229 229 branch_name = _branch_name
230 230 sha_commit_id = branch_commit_id
231 231 break
232 232
233 233 if h.is_svn(repo) and not repo.is_empty():
234 234 # Note: Subversion only has one head.
235 235 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
236 236 is_head = True
237 237 return branch_name, sha_commit_id, is_head
238 238
239 239 # checked branches, means we only need to try to get the branch/commit_sha
240 240 if not repo.is_empty():
241 241 commit = repo.get_commit(commit_id=commit_id)
242 242 if commit:
243 243 branch_name = commit.branch
244 244 sha_commit_id = commit.raw_id
245 245
246 246 return branch_name, sha_commit_id, is_head
247 247
248 248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
249 249
250 250 repo_id = self.db_repo.repo_id
251 251 force_recache = self.get_recache_flag()
252 252
253 253 cache_seconds = safe_int(
254 254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
255 255 cache_on = not force_recache and cache_seconds > 0
256 256 log.debug(
257 257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
258 258 'with caching: %s[TTL: %ss]' % (
259 259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
260 260
261 261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
262 262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
263 263
264 264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
265 265 condition=cache_on)
266 266 def compute_file_tree(ver, repo_id, commit_id, f_path, full_load):
267 267 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
268 268 ver, repo_id, commit_id, f_path)
269 269
270 270 c.full_load = full_load
271 271 return render(
272 272 'rhodecode:templates/files/files_browser_tree.mako',
273 273 self._get_template_context(c), self.request)
274 274
275 return compute_file_tree('v1', self.db_repo.repo_id, commit_id, f_path, full_load)
275 return compute_file_tree(
276 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_id, commit_id, f_path, full_load)
276 277
277 278 def _get_archive_spec(self, fname):
278 279 log.debug('Detecting archive spec for: `%s`', fname)
279 280
280 281 fileformat = None
281 282 ext = None
282 283 content_type = None
283 284 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
284 285
285 286 if fname.endswith(extension):
286 287 fileformat = a_type
287 288 log.debug('archive is of type: %s', fileformat)
288 289 ext = extension
289 290 break
290 291
291 292 if not fileformat:
292 293 raise ValueError()
293 294
294 295 # left over part of whole fname is the commit
295 296 commit_id = fname[:-len(ext)]
296 297
297 298 return commit_id, ext, fileformat, content_type
298 299
299 300 def create_pure_path(self, *parts):
300 301 # Split paths and sanitize them, removing any ../ etc
301 302 sanitized_path = [
302 303 x for x in pathlib2.PurePath(*parts).parts
303 304 if x not in ['.', '..']]
304 305
305 306 pure_path = pathlib2.PurePath(*sanitized_path)
306 307 return pure_path
307 308
308 309 def _is_lf_enabled(self, target_repo):
309 310 lf_enabled = False
310 311
311 312 lf_key_for_vcs_map = {
312 313 'hg': 'extensions_largefiles',
313 314 'git': 'vcs_git_lfs_enabled'
314 315 }
315 316
316 317 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
317 318
318 319 if lf_key_for_vcs:
319 320 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
320 321
321 322 return lf_enabled
322 323
323 324 @LoginRequired()
324 325 @HasRepoPermissionAnyDecorator(
325 326 'repository.read', 'repository.write', 'repository.admin')
326 327 @view_config(
327 328 route_name='repo_archivefile', request_method='GET',
328 329 renderer=None)
329 330 def repo_archivefile(self):
330 331 # archive cache config
331 332 from rhodecode import CONFIG
332 333 _ = self.request.translate
333 334 self.load_default_context()
334 335 default_at_path = '/'
335 336 fname = self.request.matchdict['fname']
336 337 subrepos = self.request.GET.get('subrepos') == 'true'
337 338 at_path = self.request.GET.get('at_path') or default_at_path
338 339
339 340 if not self.db_repo.enable_downloads:
340 341 return Response(_('Downloads disabled'))
341 342
342 343 try:
343 344 commit_id, ext, fileformat, content_type = \
344 345 self._get_archive_spec(fname)
345 346 except ValueError:
346 347 return Response(_('Unknown archive type for: `{}`').format(
347 348 h.escape(fname)))
348 349
349 350 try:
350 351 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
351 352 except CommitDoesNotExistError:
352 353 return Response(_('Unknown commit_id {}').format(
353 354 h.escape(commit_id)))
354 355 except EmptyRepositoryError:
355 356 return Response(_('Empty repository'))
356 357
357 358 try:
358 359 at_path = commit.get_node(at_path).path or default_at_path
359 360 except Exception:
360 361 return Response(_('No node at path {} for this repository').format(at_path))
361 362
362 363 path_sha = sha1(at_path)[:8]
363 364
364 365 # original backward compat name of archive
365 366 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
366 367 short_sha = safe_str(commit.short_id)
367 368
368 369 if at_path == default_at_path:
369 370 archive_name = '{}-{}{}{}'.format(
370 371 clean_name,
371 372 '-sub' if subrepos else '',
372 373 short_sha,
373 374 ext)
374 375 # custom path and new name
375 376 else:
376 377 archive_name = '{}-{}{}-{}{}'.format(
377 378 clean_name,
378 379 '-sub' if subrepos else '',
379 380 short_sha,
380 381 path_sha,
381 382 ext)
382 383
383 384 use_cached_archive = False
384 385 archive_cache_enabled = CONFIG.get(
385 386 'archive_cache_dir') and not self.request.GET.get('no_cache')
386 387 cached_archive_path = None
387 388
388 389 if archive_cache_enabled:
389 390 # check if we it's ok to write
390 391 if not os.path.isdir(CONFIG['archive_cache_dir']):
391 392 os.makedirs(CONFIG['archive_cache_dir'])
392 393 cached_archive_path = os.path.join(
393 394 CONFIG['archive_cache_dir'], archive_name)
394 395 if os.path.isfile(cached_archive_path):
395 396 log.debug('Found cached archive in %s', cached_archive_path)
396 397 fd, archive = None, cached_archive_path
397 398 use_cached_archive = True
398 399 else:
399 400 log.debug('Archive %s is not yet cached', archive_name)
400 401
401 402 if not use_cached_archive:
402 403 # generate new archive
403 404 fd, archive = tempfile.mkstemp()
404 405 log.debug('Creating new temp archive in %s', archive)
405 406 try:
406 407 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
407 408 archive_at_path=at_path)
408 409 except ImproperArchiveTypeError:
409 410 return _('Unknown archive type')
410 411 if archive_cache_enabled:
411 412 # if we generated the archive and we have cache enabled
412 413 # let's use this for future
413 414 log.debug('Storing new archive in %s', cached_archive_path)
414 415 shutil.move(archive, cached_archive_path)
415 416 archive = cached_archive_path
416 417
417 418 # store download action
418 419 audit_logger.store_web(
419 420 'repo.archive.download', action_data={
420 421 'user_agent': self.request.user_agent,
421 422 'archive_name': archive_name,
422 423 'archive_spec': fname,
423 424 'archive_cached': use_cached_archive},
424 425 user=self._rhodecode_user,
425 426 repo=self.db_repo,
426 427 commit=True
427 428 )
428 429
429 430 def get_chunked_archive(archive_path):
430 431 with open(archive_path, 'rb') as stream:
431 432 while True:
432 433 data = stream.read(16 * 1024)
433 434 if not data:
434 435 if fd: # fd means we used temporary file
435 436 os.close(fd)
436 437 if not archive_cache_enabled:
437 438 log.debug('Destroying temp archive %s', archive_path)
438 439 os.remove(archive_path)
439 440 break
440 441 yield data
441 442
442 443 response = Response(app_iter=get_chunked_archive(archive))
443 444 response.content_disposition = str(
444 445 'attachment; filename=%s' % archive_name)
445 446 response.content_type = str(content_type)
446 447
447 448 return response
448 449
449 450 def _get_file_node(self, commit_id, f_path):
450 451 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
451 452 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
452 453 try:
453 454 node = commit.get_node(f_path)
454 455 if node.is_dir():
455 456 raise NodeError('%s path is a %s not a file'
456 457 % (node, type(node)))
457 458 except NodeDoesNotExistError:
458 459 commit = EmptyCommit(
459 460 commit_id=commit_id,
460 461 idx=commit.idx,
461 462 repo=commit.repository,
462 463 alias=commit.repository.alias,
463 464 message=commit.message,
464 465 author=commit.author,
465 466 date=commit.date)
466 467 node = FileNode(f_path, '', commit=commit)
467 468 else:
468 469 commit = EmptyCommit(
469 470 repo=self.rhodecode_vcs_repo,
470 471 alias=self.rhodecode_vcs_repo.alias)
471 472 node = FileNode(f_path, '', commit=commit)
472 473 return node
473 474
474 475 @LoginRequired()
475 476 @HasRepoPermissionAnyDecorator(
476 477 'repository.read', 'repository.write', 'repository.admin')
477 478 @view_config(
478 479 route_name='repo_files_diff', request_method='GET',
479 480 renderer=None)
480 481 def repo_files_diff(self):
481 482 c = self.load_default_context()
482 483 f_path = self._get_f_path(self.request.matchdict)
483 484 diff1 = self.request.GET.get('diff1', '')
484 485 diff2 = self.request.GET.get('diff2', '')
485 486
486 487 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
487 488
488 489 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
489 490 line_context = self.request.GET.get('context', 3)
490 491
491 492 if not any((diff1, diff2)):
492 493 h.flash(
493 494 'Need query parameter "diff1" or "diff2" to generate a diff.',
494 495 category='error')
495 496 raise HTTPBadRequest()
496 497
497 498 c.action = self.request.GET.get('diff')
498 499 if c.action not in ['download', 'raw']:
499 500 compare_url = h.route_path(
500 501 'repo_compare',
501 502 repo_name=self.db_repo_name,
502 503 source_ref_type='rev',
503 504 source_ref=diff1,
504 505 target_repo=self.db_repo_name,
505 506 target_ref_type='rev',
506 507 target_ref=diff2,
507 508 _query=dict(f_path=f_path))
508 509 # redirect to new view if we render diff
509 510 raise HTTPFound(compare_url)
510 511
511 512 try:
512 513 node1 = self._get_file_node(diff1, path1)
513 514 node2 = self._get_file_node(diff2, f_path)
514 515 except (RepositoryError, NodeError):
515 516 log.exception("Exception while trying to get node from repository")
516 517 raise HTTPFound(
517 518 h.route_path('repo_files', repo_name=self.db_repo_name,
518 519 commit_id='tip', f_path=f_path))
519 520
520 521 if all(isinstance(node.commit, EmptyCommit)
521 522 for node in (node1, node2)):
522 523 raise HTTPNotFound()
523 524
524 525 c.commit_1 = node1.commit
525 526 c.commit_2 = node2.commit
526 527
527 528 if c.action == 'download':
528 529 _diff = diffs.get_gitdiff(node1, node2,
529 530 ignore_whitespace=ignore_whitespace,
530 531 context=line_context)
531 532 diff = diffs.DiffProcessor(_diff, format='gitdiff')
532 533
533 534 response = Response(self.path_filter.get_raw_patch(diff))
534 535 response.content_type = 'text/plain'
535 536 response.content_disposition = (
536 537 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
537 538 )
538 539 charset = self._get_default_encoding(c)
539 540 if charset:
540 541 response.charset = charset
541 542 return response
542 543
543 544 elif c.action == 'raw':
544 545 _diff = diffs.get_gitdiff(node1, node2,
545 546 ignore_whitespace=ignore_whitespace,
546 547 context=line_context)
547 548 diff = diffs.DiffProcessor(_diff, format='gitdiff')
548 549
549 550 response = Response(self.path_filter.get_raw_patch(diff))
550 551 response.content_type = 'text/plain'
551 552 charset = self._get_default_encoding(c)
552 553 if charset:
553 554 response.charset = charset
554 555 return response
555 556
556 557 # in case we ever end up here
557 558 raise HTTPNotFound()
558 559
559 560 @LoginRequired()
560 561 @HasRepoPermissionAnyDecorator(
561 562 'repository.read', 'repository.write', 'repository.admin')
562 563 @view_config(
563 564 route_name='repo_files_diff_2way_redirect', request_method='GET',
564 565 renderer=None)
565 566 def repo_files_diff_2way_redirect(self):
566 567 """
567 568 Kept only to make OLD links work
568 569 """
569 570 f_path = self._get_f_path_unchecked(self.request.matchdict)
570 571 diff1 = self.request.GET.get('diff1', '')
571 572 diff2 = self.request.GET.get('diff2', '')
572 573
573 574 if not any((diff1, diff2)):
574 575 h.flash(
575 576 'Need query parameter "diff1" or "diff2" to generate a diff.',
576 577 category='error')
577 578 raise HTTPBadRequest()
578 579
579 580 compare_url = h.route_path(
580 581 'repo_compare',
581 582 repo_name=self.db_repo_name,
582 583 source_ref_type='rev',
583 584 source_ref=diff1,
584 585 target_ref_type='rev',
585 586 target_ref=diff2,
586 587 _query=dict(f_path=f_path, diffmode='sideside',
587 588 target_repo=self.db_repo_name,))
588 589 raise HTTPFound(compare_url)
589 590
590 591 @LoginRequired()
591 592 @HasRepoPermissionAnyDecorator(
592 593 'repository.read', 'repository.write', 'repository.admin')
593 594 @view_config(
594 595 route_name='repo_files', request_method='GET',
595 596 renderer=None)
596 597 @view_config(
597 598 route_name='repo_files:default_path', request_method='GET',
598 599 renderer=None)
599 600 @view_config(
600 601 route_name='repo_files:default_commit', request_method='GET',
601 602 renderer=None)
602 603 @view_config(
603 604 route_name='repo_files:rendered', request_method='GET',
604 605 renderer=None)
605 606 @view_config(
606 607 route_name='repo_files:annotated', request_method='GET',
607 608 renderer=None)
608 609 def repo_files(self):
609 610 c = self.load_default_context()
610 611
611 612 view_name = getattr(self.request.matched_route, 'name', None)
612 613
613 614 c.annotate = view_name == 'repo_files:annotated'
614 615 # default is false, but .rst/.md files later are auto rendered, we can
615 616 # overwrite auto rendering by setting this GET flag
616 617 c.renderer = view_name == 'repo_files:rendered' or \
617 618 not self.request.GET.get('no-render', False)
618 619
619 620 # redirect to given commit_id from form if given
620 621 get_commit_id = self.request.GET.get('at_rev', None)
621 622 if get_commit_id:
622 623 self._get_commit_or_redirect(get_commit_id)
623 624
624 625 commit_id, f_path = self._get_commit_and_path()
625 626 c.commit = self._get_commit_or_redirect(commit_id)
626 627 c.branch = self.request.GET.get('branch', None)
627 628 c.f_path = f_path
628 629
629 630 # prev link
630 631 try:
631 632 prev_commit = c.commit.prev(c.branch)
632 633 c.prev_commit = prev_commit
633 634 c.url_prev = h.route_path(
634 635 'repo_files', repo_name=self.db_repo_name,
635 636 commit_id=prev_commit.raw_id, f_path=f_path)
636 637 if c.branch:
637 638 c.url_prev += '?branch=%s' % c.branch
638 639 except (CommitDoesNotExistError, VCSError):
639 640 c.url_prev = '#'
640 641 c.prev_commit = EmptyCommit()
641 642
642 643 # next link
643 644 try:
644 645 next_commit = c.commit.next(c.branch)
645 646 c.next_commit = next_commit
646 647 c.url_next = h.route_path(
647 648 'repo_files', repo_name=self.db_repo_name,
648 649 commit_id=next_commit.raw_id, f_path=f_path)
649 650 if c.branch:
650 651 c.url_next += '?branch=%s' % c.branch
651 652 except (CommitDoesNotExistError, VCSError):
652 653 c.url_next = '#'
653 654 c.next_commit = EmptyCommit()
654 655
655 656 # files or dirs
656 657 try:
657 658 c.file = c.commit.get_node(f_path)
658 659 c.file_author = True
659 660 c.file_tree = ''
660 661
661 662 # load file content
662 663 if c.file.is_file():
663 664 c.lf_node = {}
664 665
665 666 has_lf_enabled = self._is_lf_enabled(self.db_repo)
666 667 if has_lf_enabled:
667 668 c.lf_node = c.file.get_largefile_node()
668 669
669 670 c.file_source_page = 'true'
670 671 c.file_last_commit = c.file.last_commit
671 672
672 673 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
673 674
674 675 if not (c.file_size_too_big or c.file.is_binary):
675 676 if c.annotate: # annotation has precedence over renderer
676 677 c.annotated_lines = filenode_as_annotated_lines_tokens(
677 678 c.file
678 679 )
679 680 else:
680 681 c.renderer = (
681 682 c.renderer and h.renderer_from_filename(c.file.path)
682 683 )
683 684 if not c.renderer:
684 685 c.lines = filenode_as_lines_tokens(c.file)
685 686
686 687 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
687 688 commit_id, self.rhodecode_vcs_repo)
688 689 c.on_branch_head = is_head
689 690
690 691 branch = c.commit.branch if (
691 692 c.commit.branch and '/' not in c.commit.branch) else None
692 693 c.branch_or_raw_id = branch or c.commit.raw_id
693 694 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
694 695
695 696 author = c.file_last_commit.author
696 697 c.authors = [[
697 698 h.email(author),
698 699 h.person(author, 'username_or_name_or_email'),
699 700 1
700 701 ]]
701 702
702 703 else: # load tree content at path
703 704 c.file_source_page = 'false'
704 705 c.authors = []
705 706 # this loads a simple tree without metadata to speed things up
706 707 # later via ajax we call repo_nodetree_full and fetch whole
707 708 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
708 709
709 710 c.readme_data, c.readme_file = \
710 711 self._get_readme_data(self.db_repo, c.visual.default_renderer,
711 712 c.commit.raw_id, f_path)
712 713
713 714 except RepositoryError as e:
714 715 h.flash(safe_str(h.escape(e)), category='error')
715 716 raise HTTPNotFound()
716 717
717 718 if self.request.environ.get('HTTP_X_PJAX'):
718 719 html = render('rhodecode:templates/files/files_pjax.mako',
719 720 self._get_template_context(c), self.request)
720 721 else:
721 722 html = render('rhodecode:templates/files/files.mako',
722 723 self._get_template_context(c), self.request)
723 724 return Response(html)
724 725
725 726 @HasRepoPermissionAnyDecorator(
726 727 'repository.read', 'repository.write', 'repository.admin')
727 728 @view_config(
728 729 route_name='repo_files:annotated_previous', request_method='GET',
729 730 renderer=None)
730 731 def repo_files_annotated_previous(self):
731 732 self.load_default_context()
732 733
733 734 commit_id, f_path = self._get_commit_and_path()
734 735 commit = self._get_commit_or_redirect(commit_id)
735 736 prev_commit_id = commit.raw_id
736 737 line_anchor = self.request.GET.get('line_anchor')
737 738 is_file = False
738 739 try:
739 740 _file = commit.get_node(f_path)
740 741 is_file = _file.is_file()
741 742 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
742 743 pass
743 744
744 745 if is_file:
745 746 history = commit.get_path_history(f_path)
746 747 prev_commit_id = history[1].raw_id \
747 748 if len(history) > 1 else prev_commit_id
748 749 prev_url = h.route_path(
749 750 'repo_files:annotated', repo_name=self.db_repo_name,
750 751 commit_id=prev_commit_id, f_path=f_path,
751 752 _anchor='L{}'.format(line_anchor))
752 753
753 754 raise HTTPFound(prev_url)
754 755
755 756 @LoginRequired()
756 757 @HasRepoPermissionAnyDecorator(
757 758 'repository.read', 'repository.write', 'repository.admin')
758 759 @view_config(
759 760 route_name='repo_nodetree_full', request_method='GET',
760 761 renderer=None, xhr=True)
761 762 @view_config(
762 763 route_name='repo_nodetree_full:default_path', request_method='GET',
763 764 renderer=None, xhr=True)
764 765 def repo_nodetree_full(self):
765 766 """
766 767 Returns rendered html of file tree that contains commit date,
767 768 author, commit_id for the specified combination of
768 769 repo, commit_id and file path
769 770 """
770 771 c = self.load_default_context()
771 772
772 773 commit_id, f_path = self._get_commit_and_path()
773 774 commit = self._get_commit_or_redirect(commit_id)
774 775 try:
775 776 dir_node = commit.get_node(f_path)
776 777 except RepositoryError as e:
777 778 return Response('error: {}'.format(h.escape(safe_str(e))))
778 779
779 780 if dir_node.is_file():
780 781 return Response('')
781 782
782 783 c.file = dir_node
783 784 c.commit = commit
784 785
785 786 html = self._get_tree_at_commit(
786 787 c, commit.raw_id, dir_node.path, full_load=True)
787 788
788 789 return Response(html)
789 790
790 791 def _get_attachement_headers(self, f_path):
791 792 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
792 793 safe_path = f_name.replace('"', '\\"')
793 794 encoded_path = urllib.quote(f_name)
794 795
795 796 return "attachment; " \
796 797 "filename=\"{}\"; " \
797 798 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
798 799
799 800 @LoginRequired()
800 801 @HasRepoPermissionAnyDecorator(
801 802 'repository.read', 'repository.write', 'repository.admin')
802 803 @view_config(
803 804 route_name='repo_file_raw', request_method='GET',
804 805 renderer=None)
805 806 def repo_file_raw(self):
806 807 """
807 808 Action for show as raw, some mimetypes are "rendered",
808 809 those include images, icons.
809 810 """
810 811 c = self.load_default_context()
811 812
812 813 commit_id, f_path = self._get_commit_and_path()
813 814 commit = self._get_commit_or_redirect(commit_id)
814 815 file_node = self._get_filenode_or_redirect(commit, f_path)
815 816
816 817 raw_mimetype_mapping = {
817 818 # map original mimetype to a mimetype used for "show as raw"
818 819 # you can also provide a content-disposition to override the
819 820 # default "attachment" disposition.
820 821 # orig_type: (new_type, new_dispo)
821 822
822 823 # show images inline:
823 824 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
824 825 # for example render an SVG with javascript inside or even render
825 826 # HTML.
826 827 'image/x-icon': ('image/x-icon', 'inline'),
827 828 'image/png': ('image/png', 'inline'),
828 829 'image/gif': ('image/gif', 'inline'),
829 830 'image/jpeg': ('image/jpeg', 'inline'),
830 831 'application/pdf': ('application/pdf', 'inline'),
831 832 }
832 833
833 834 mimetype = file_node.mimetype
834 835 try:
835 836 mimetype, disposition = raw_mimetype_mapping[mimetype]
836 837 except KeyError:
837 838 # we don't know anything special about this, handle it safely
838 839 if file_node.is_binary:
839 840 # do same as download raw for binary files
840 841 mimetype, disposition = 'application/octet-stream', 'attachment'
841 842 else:
842 843 # do not just use the original mimetype, but force text/plain,
843 844 # otherwise it would serve text/html and that might be unsafe.
844 845 # Note: underlying vcs library fakes text/plain mimetype if the
845 846 # mimetype can not be determined and it thinks it is not
846 847 # binary.This might lead to erroneous text display in some
847 848 # cases, but helps in other cases, like with text files
848 849 # without extension.
849 850 mimetype, disposition = 'text/plain', 'inline'
850 851
851 852 if disposition == 'attachment':
852 853 disposition = self._get_attachement_headers(f_path)
853 854
854 855 stream_content = file_node.stream_bytes()
855 856
856 857 response = Response(app_iter=stream_content)
857 858 response.content_disposition = disposition
858 859 response.content_type = mimetype
859 860
860 861 charset = self._get_default_encoding(c)
861 862 if charset:
862 863 response.charset = charset
863 864
864 865 return response
865 866
866 867 @LoginRequired()
867 868 @HasRepoPermissionAnyDecorator(
868 869 'repository.read', 'repository.write', 'repository.admin')
869 870 @view_config(
870 871 route_name='repo_file_download', request_method='GET',
871 872 renderer=None)
872 873 @view_config(
873 874 route_name='repo_file_download:legacy', request_method='GET',
874 875 renderer=None)
875 876 def repo_file_download(self):
876 877 c = self.load_default_context()
877 878
878 879 commit_id, f_path = self._get_commit_and_path()
879 880 commit = self._get_commit_or_redirect(commit_id)
880 881 file_node = self._get_filenode_or_redirect(commit, f_path)
881 882
882 883 if self.request.GET.get('lf'):
883 884 # only if lf get flag is passed, we download this file
884 885 # as LFS/Largefile
885 886 lf_node = file_node.get_largefile_node()
886 887 if lf_node:
887 888 # overwrite our pointer with the REAL large-file
888 889 file_node = lf_node
889 890
890 891 disposition = self._get_attachement_headers(f_path)
891 892
892 893 stream_content = file_node.stream_bytes()
893 894
894 895 response = Response(app_iter=stream_content)
895 896 response.content_disposition = disposition
896 897 response.content_type = file_node.mimetype
897 898
898 899 charset = self._get_default_encoding(c)
899 900 if charset:
900 901 response.charset = charset
901 902
902 903 return response
903 904
904 905 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
905 906
906 907 cache_seconds = safe_int(
907 908 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
908 909 cache_on = cache_seconds > 0
909 910 log.debug(
910 911 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
911 912 'with caching: %s[TTL: %ss]' % (
912 913 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
913 914
914 915 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
915 916 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
916 917
917 918 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
918 919 condition=cache_on)
919 920 def compute_file_search(repo_id, commit_id, f_path):
920 921 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
921 922 repo_id, commit_id, f_path)
922 923 try:
923 924 _d, _f = ScmModel().get_quick_filter_nodes(repo_name, commit_id, f_path)
924 925 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
925 926 log.exception(safe_str(e))
926 927 h.flash(safe_str(h.escape(e)), category='error')
927 928 raise HTTPFound(h.route_path(
928 929 'repo_files', repo_name=self.db_repo_name,
929 930 commit_id='tip', f_path='/'))
930 931
931 932 return _d + _f
932 933
933 934 result = compute_file_search(self.db_repo.repo_id, commit_id, f_path)
934 935 return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
935 936
936 937 @LoginRequired()
937 938 @HasRepoPermissionAnyDecorator(
938 939 'repository.read', 'repository.write', 'repository.admin')
939 940 @view_config(
940 941 route_name='repo_files_nodelist', request_method='GET',
941 942 renderer='json_ext', xhr=True)
942 943 def repo_nodelist(self):
943 944 self.load_default_context()
944 945
945 946 commit_id, f_path = self._get_commit_and_path()
946 947 commit = self._get_commit_or_redirect(commit_id)
947 948
948 949 metadata = self._get_nodelist_at_commit(
949 950 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
950 951 return {'nodes': metadata}
951 952
952 953 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
953 954 items = []
954 955 for name, commit_id in branches_or_tags.items():
955 956 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
956 957 items.append((sym_ref, name, ref_type))
957 958 return items
958 959
959 960 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
960 961 return commit_id
961 962
962 963 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
963 964 new_f_path = vcspath.join(name, f_path)
964 965 return u'%s@%s' % (new_f_path, commit_id)
965 966
966 967 def _get_node_history(self, commit_obj, f_path, commits=None):
967 968 """
968 969 get commit history for given node
969 970
970 971 :param commit_obj: commit to calculate history
971 972 :param f_path: path for node to calculate history for
972 973 :param commits: if passed don't calculate history and take
973 974 commits defined in this list
974 975 """
975 976 _ = self.request.translate
976 977
977 978 # calculate history based on tip
978 979 tip = self.rhodecode_vcs_repo.get_commit()
979 980 if commits is None:
980 981 pre_load = ["author", "branch"]
981 982 try:
982 983 commits = tip.get_path_history(f_path, pre_load=pre_load)
983 984 except (NodeDoesNotExistError, CommitError):
984 985 # this node is not present at tip!
985 986 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
986 987
987 988 history = []
988 989 commits_group = ([], _("Changesets"))
989 990 for commit in commits:
990 991 branch = ' (%s)' % commit.branch if commit.branch else ''
991 992 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
992 993 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
993 994 history.append(commits_group)
994 995
995 996 symbolic_reference = self._symbolic_reference
996 997
997 998 if self.rhodecode_vcs_repo.alias == 'svn':
998 999 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
999 1000 f_path, self.rhodecode_vcs_repo)
1000 1001 if adjusted_f_path != f_path:
1001 1002 log.debug(
1002 1003 'Recognized svn tag or branch in file "%s", using svn '
1003 1004 'specific symbolic references', f_path)
1004 1005 f_path = adjusted_f_path
1005 1006 symbolic_reference = self._symbolic_reference_svn
1006 1007
1007 1008 branches = self._create_references(
1008 1009 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1009 1010 branches_group = (branches, _("Branches"))
1010 1011
1011 1012 tags = self._create_references(
1012 1013 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1013 1014 tags_group = (tags, _("Tags"))
1014 1015
1015 1016 history.append(branches_group)
1016 1017 history.append(tags_group)
1017 1018
1018 1019 return history, commits
1019 1020
1020 1021 @LoginRequired()
1021 1022 @HasRepoPermissionAnyDecorator(
1022 1023 'repository.read', 'repository.write', 'repository.admin')
1023 1024 @view_config(
1024 1025 route_name='repo_file_history', request_method='GET',
1025 1026 renderer='json_ext')
1026 1027 def repo_file_history(self):
1027 1028 self.load_default_context()
1028 1029
1029 1030 commit_id, f_path = self._get_commit_and_path()
1030 1031 commit = self._get_commit_or_redirect(commit_id)
1031 1032 file_node = self._get_filenode_or_redirect(commit, f_path)
1032 1033
1033 1034 if file_node.is_file():
1034 1035 file_history, _hist = self._get_node_history(commit, f_path)
1035 1036
1036 1037 res = []
1037 1038 for obj in file_history:
1038 1039 res.append({
1039 1040 'text': obj[1],
1040 1041 'children': [{'id': o[0], 'text': o[1], 'type': o[2]} for o in obj[0]]
1041 1042 })
1042 1043
1043 1044 data = {
1044 1045 'more': False,
1045 1046 'results': res
1046 1047 }
1047 1048 return data
1048 1049
1049 1050 log.warning('Cannot fetch history for directory')
1050 1051 raise HTTPBadRequest()
1051 1052
1052 1053 @LoginRequired()
1053 1054 @HasRepoPermissionAnyDecorator(
1054 1055 'repository.read', 'repository.write', 'repository.admin')
1055 1056 @view_config(
1056 1057 route_name='repo_file_authors', request_method='GET',
1057 1058 renderer='rhodecode:templates/files/file_authors_box.mako')
1058 1059 def repo_file_authors(self):
1059 1060 c = self.load_default_context()
1060 1061
1061 1062 commit_id, f_path = self._get_commit_and_path()
1062 1063 commit = self._get_commit_or_redirect(commit_id)
1063 1064 file_node = self._get_filenode_or_redirect(commit, f_path)
1064 1065
1065 1066 if not file_node.is_file():
1066 1067 raise HTTPBadRequest()
1067 1068
1068 1069 c.file_last_commit = file_node.last_commit
1069 1070 if self.request.GET.get('annotate') == '1':
1070 1071 # use _hist from annotation if annotation mode is on
1071 1072 commit_ids = set(x[1] for x in file_node.annotate)
1072 1073 _hist = (
1073 1074 self.rhodecode_vcs_repo.get_commit(commit_id)
1074 1075 for commit_id in commit_ids)
1075 1076 else:
1076 1077 _f_history, _hist = self._get_node_history(commit, f_path)
1077 1078 c.file_author = False
1078 1079
1079 1080 unique = collections.OrderedDict()
1080 1081 for commit in _hist:
1081 1082 author = commit.author
1082 1083 if author not in unique:
1083 1084 unique[commit.author] = [
1084 1085 h.email(author),
1085 1086 h.person(author, 'username_or_name_or_email'),
1086 1087 1 # counter
1087 1088 ]
1088 1089
1089 1090 else:
1090 1091 # increase counter
1091 1092 unique[commit.author][2] += 1
1092 1093
1093 1094 c.authors = [val for val in unique.values()]
1094 1095
1095 1096 return self._get_template_context(c)
1096 1097
1097 1098 @LoginRequired()
1098 1099 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1099 1100 @view_config(
1100 1101 route_name='repo_files_remove_file', request_method='GET',
1101 1102 renderer='rhodecode:templates/files/files_delete.mako')
1102 1103 def repo_files_remove_file(self):
1103 1104 _ = self.request.translate
1104 1105 c = self.load_default_context()
1105 1106 commit_id, f_path = self._get_commit_and_path()
1106 1107
1107 1108 self._ensure_not_locked()
1108 1109 _branch_name, _sha_commit_id, is_head = \
1109 1110 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1110 1111
1111 1112 self.forbid_non_head(is_head, f_path)
1112 1113 self.check_branch_permission(_branch_name)
1113 1114
1114 1115 c.commit = self._get_commit_or_redirect(commit_id)
1115 1116 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1116 1117
1117 1118 c.default_message = _(
1118 1119 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1119 1120 c.f_path = f_path
1120 1121
1121 1122 return self._get_template_context(c)
1122 1123
1123 1124 @LoginRequired()
1124 1125 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1125 1126 @CSRFRequired()
1126 1127 @view_config(
1127 1128 route_name='repo_files_delete_file', request_method='POST',
1128 1129 renderer=None)
1129 1130 def repo_files_delete_file(self):
1130 1131 _ = self.request.translate
1131 1132
1132 1133 c = self.load_default_context()
1133 1134 commit_id, f_path = self._get_commit_and_path()
1134 1135
1135 1136 self._ensure_not_locked()
1136 1137 _branch_name, _sha_commit_id, is_head = \
1137 1138 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1138 1139
1139 1140 self.forbid_non_head(is_head, f_path)
1140 1141 self.check_branch_permission(_branch_name)
1141 1142
1142 1143 c.commit = self._get_commit_or_redirect(commit_id)
1143 1144 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1144 1145
1145 1146 c.default_message = _(
1146 1147 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1147 1148 c.f_path = f_path
1148 1149 node_path = f_path
1149 1150 author = self._rhodecode_db_user.full_contact
1150 1151 message = self.request.POST.get('message') or c.default_message
1151 1152 try:
1152 1153 nodes = {
1153 1154 node_path: {
1154 1155 'content': ''
1155 1156 }
1156 1157 }
1157 1158 ScmModel().delete_nodes(
1158 1159 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1159 1160 message=message,
1160 1161 nodes=nodes,
1161 1162 parent_commit=c.commit,
1162 1163 author=author,
1163 1164 )
1164 1165
1165 1166 h.flash(
1166 1167 _('Successfully deleted file `{}`').format(
1167 1168 h.escape(f_path)), category='success')
1168 1169 except Exception:
1169 1170 log.exception('Error during commit operation')
1170 1171 h.flash(_('Error occurred during commit'), category='error')
1171 1172 raise HTTPFound(
1172 1173 h.route_path('repo_commit', repo_name=self.db_repo_name,
1173 1174 commit_id='tip'))
1174 1175
1175 1176 @LoginRequired()
1176 1177 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1177 1178 @view_config(
1178 1179 route_name='repo_files_edit_file', request_method='GET',
1179 1180 renderer='rhodecode:templates/files/files_edit.mako')
1180 1181 def repo_files_edit_file(self):
1181 1182 _ = self.request.translate
1182 1183 c = self.load_default_context()
1183 1184 commit_id, f_path = self._get_commit_and_path()
1184 1185
1185 1186 self._ensure_not_locked()
1186 1187 _branch_name, _sha_commit_id, is_head = \
1187 1188 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1188 1189
1189 1190 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1190 1191 self.check_branch_permission(_branch_name, commit_id=commit_id)
1191 1192
1192 1193 c.commit = self._get_commit_or_redirect(commit_id)
1193 1194 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1194 1195
1195 1196 if c.file.is_binary:
1196 1197 files_url = h.route_path(
1197 1198 'repo_files',
1198 1199 repo_name=self.db_repo_name,
1199 1200 commit_id=c.commit.raw_id, f_path=f_path)
1200 1201 raise HTTPFound(files_url)
1201 1202
1202 1203 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1203 1204 c.f_path = f_path
1204 1205
1205 1206 return self._get_template_context(c)
1206 1207
1207 1208 @LoginRequired()
1208 1209 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1209 1210 @CSRFRequired()
1210 1211 @view_config(
1211 1212 route_name='repo_files_update_file', request_method='POST',
1212 1213 renderer=None)
1213 1214 def repo_files_update_file(self):
1214 1215 _ = self.request.translate
1215 1216 c = self.load_default_context()
1216 1217 commit_id, f_path = self._get_commit_and_path()
1217 1218
1218 1219 self._ensure_not_locked()
1219 1220
1220 1221 c.commit = self._get_commit_or_redirect(commit_id)
1221 1222 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1222 1223
1223 1224 if c.file.is_binary:
1224 1225 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1225 1226 commit_id=c.commit.raw_id, f_path=f_path))
1226 1227
1227 1228 _branch_name, _sha_commit_id, is_head = \
1228 1229 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1229 1230
1230 1231 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1231 1232 self.check_branch_permission(_branch_name, commit_id=commit_id)
1232 1233
1233 1234 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1234 1235 c.f_path = f_path
1235 1236
1236 1237 old_content = c.file.content
1237 1238 sl = old_content.splitlines(1)
1238 1239 first_line = sl[0] if sl else ''
1239 1240
1240 1241 r_post = self.request.POST
1241 1242 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1242 1243 line_ending_mode = detect_mode(first_line, 0)
1243 1244 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1244 1245
1245 1246 message = r_post.get('message') or c.default_message
1246 1247 org_node_path = c.file.unicode_path
1247 1248 filename = r_post['filename']
1248 1249
1249 1250 root_path = c.file.dir_path
1250 1251 pure_path = self.create_pure_path(root_path, filename)
1251 1252 node_path = safe_unicode(bytes(pure_path))
1252 1253
1253 1254 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1254 1255 commit_id=commit_id)
1255 1256 if content == old_content and node_path == org_node_path:
1256 1257 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1257 1258 category='warning')
1258 1259 raise HTTPFound(default_redirect_url)
1259 1260
1260 1261 try:
1261 1262 mapping = {
1262 1263 org_node_path: {
1263 1264 'org_filename': org_node_path,
1264 1265 'filename': node_path,
1265 1266 'content': content,
1266 1267 'lexer': '',
1267 1268 'op': 'mod',
1268 1269 'mode': c.file.mode
1269 1270 }
1270 1271 }
1271 1272
1272 1273 commit = ScmModel().update_nodes(
1273 1274 user=self._rhodecode_db_user.user_id,
1274 1275 repo=self.db_repo,
1275 1276 message=message,
1276 1277 nodes=mapping,
1277 1278 parent_commit=c.commit,
1278 1279 )
1279 1280
1280 1281 h.flash(_('Successfully committed changes to file `{}`').format(
1281 1282 h.escape(f_path)), category='success')
1282 1283 default_redirect_url = h.route_path(
1283 1284 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1284 1285
1285 1286 except Exception:
1286 1287 log.exception('Error occurred during commit')
1287 1288 h.flash(_('Error occurred during commit'), category='error')
1288 1289
1289 1290 raise HTTPFound(default_redirect_url)
1290 1291
1291 1292 @LoginRequired()
1292 1293 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1293 1294 @view_config(
1294 1295 route_name='repo_files_add_file', request_method='GET',
1295 1296 renderer='rhodecode:templates/files/files_add.mako')
1296 1297 @view_config(
1297 1298 route_name='repo_files_upload_file', request_method='GET',
1298 1299 renderer='rhodecode:templates/files/files_upload.mako')
1299 1300 def repo_files_add_file(self):
1300 1301 _ = self.request.translate
1301 1302 c = self.load_default_context()
1302 1303 commit_id, f_path = self._get_commit_and_path()
1303 1304
1304 1305 self._ensure_not_locked()
1305 1306
1306 1307 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1307 1308 if c.commit is None:
1308 1309 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1309 1310
1310 1311 if self.rhodecode_vcs_repo.is_empty():
1311 1312 # for empty repository we cannot check for current branch, we rely on
1312 1313 # c.commit.branch instead
1313 1314 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1314 1315 else:
1315 1316 _branch_name, _sha_commit_id, is_head = \
1316 1317 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1317 1318
1318 1319 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1319 1320 self.check_branch_permission(_branch_name, commit_id=commit_id)
1320 1321
1321 1322 c.default_message = (_('Added file via RhodeCode Enterprise'))
1322 1323 c.f_path = f_path.lstrip('/') # ensure not relative path
1323 1324
1324 1325 return self._get_template_context(c)
1325 1326
1326 1327 @LoginRequired()
1327 1328 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1328 1329 @CSRFRequired()
1329 1330 @view_config(
1330 1331 route_name='repo_files_create_file', request_method='POST',
1331 1332 renderer=None)
1332 1333 def repo_files_create_file(self):
1333 1334 _ = self.request.translate
1334 1335 c = self.load_default_context()
1335 1336 commit_id, f_path = self._get_commit_and_path()
1336 1337
1337 1338 self._ensure_not_locked()
1338 1339
1339 1340 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1340 1341 if c.commit is None:
1341 1342 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1342 1343
1343 1344 # calculate redirect URL
1344 1345 if self.rhodecode_vcs_repo.is_empty():
1345 1346 default_redirect_url = h.route_path(
1346 1347 'repo_summary', repo_name=self.db_repo_name)
1347 1348 else:
1348 1349 default_redirect_url = h.route_path(
1349 1350 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1350 1351
1351 1352 if self.rhodecode_vcs_repo.is_empty():
1352 1353 # for empty repository we cannot check for current branch, we rely on
1353 1354 # c.commit.branch instead
1354 1355 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1355 1356 else:
1356 1357 _branch_name, _sha_commit_id, is_head = \
1357 1358 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1358 1359
1359 1360 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1360 1361 self.check_branch_permission(_branch_name, commit_id=commit_id)
1361 1362
1362 1363 c.default_message = (_('Added file via RhodeCode Enterprise'))
1363 1364 c.f_path = f_path
1364 1365
1365 1366 r_post = self.request.POST
1366 1367 message = r_post.get('message') or c.default_message
1367 1368 filename = r_post.get('filename')
1368 1369 unix_mode = 0
1369 1370 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1370 1371
1371 1372 if not filename:
1372 1373 # If there's no commit, redirect to repo summary
1373 1374 if type(c.commit) is EmptyCommit:
1374 1375 redirect_url = h.route_path(
1375 1376 'repo_summary', repo_name=self.db_repo_name)
1376 1377 else:
1377 1378 redirect_url = default_redirect_url
1378 1379 h.flash(_('No filename specified'), category='warning')
1379 1380 raise HTTPFound(redirect_url)
1380 1381
1381 1382 root_path = f_path
1382 1383 pure_path = self.create_pure_path(root_path, filename)
1383 1384 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1384 1385
1385 1386 author = self._rhodecode_db_user.full_contact
1386 1387 nodes = {
1387 1388 node_path: {
1388 1389 'content': content
1389 1390 }
1390 1391 }
1391 1392
1392 1393 try:
1393 1394
1394 1395 commit = ScmModel().create_nodes(
1395 1396 user=self._rhodecode_db_user.user_id,
1396 1397 repo=self.db_repo,
1397 1398 message=message,
1398 1399 nodes=nodes,
1399 1400 parent_commit=c.commit,
1400 1401 author=author,
1401 1402 )
1402 1403
1403 1404 h.flash(_('Successfully committed new file `{}`').format(
1404 1405 h.escape(node_path)), category='success')
1405 1406
1406 1407 default_redirect_url = h.route_path(
1407 1408 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1408 1409
1409 1410 except NonRelativePathError:
1410 1411 log.exception('Non Relative path found')
1411 1412 h.flash(_('The location specified must be a relative path and must not '
1412 1413 'contain .. in the path'), category='warning')
1413 1414 raise HTTPFound(default_redirect_url)
1414 1415 except (NodeError, NodeAlreadyExistsError) as e:
1415 1416 h.flash(_(h.escape(e)), category='error')
1416 1417 except Exception:
1417 1418 log.exception('Error occurred during commit')
1418 1419 h.flash(_('Error occurred during commit'), category='error')
1419 1420
1420 1421 raise HTTPFound(default_redirect_url)
1421 1422
1422 1423 @LoginRequired()
1423 1424 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1424 1425 @CSRFRequired()
1425 1426 @view_config(
1426 1427 route_name='repo_files_upload_file', request_method='POST',
1427 1428 renderer='json_ext')
1428 1429 def repo_files_upload_file(self):
1429 1430 _ = self.request.translate
1430 1431 c = self.load_default_context()
1431 1432 commit_id, f_path = self._get_commit_and_path()
1432 1433
1433 1434 self._ensure_not_locked()
1434 1435
1435 1436 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1436 1437 if c.commit is None:
1437 1438 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1438 1439
1439 1440 # calculate redirect URL
1440 1441 if self.rhodecode_vcs_repo.is_empty():
1441 1442 default_redirect_url = h.route_path(
1442 1443 'repo_summary', repo_name=self.db_repo_name)
1443 1444 else:
1444 1445 default_redirect_url = h.route_path(
1445 1446 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1446 1447
1447 1448 if self.rhodecode_vcs_repo.is_empty():
1448 1449 # for empty repository we cannot check for current branch, we rely on
1449 1450 # c.commit.branch instead
1450 1451 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1451 1452 else:
1452 1453 _branch_name, _sha_commit_id, is_head = \
1453 1454 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1454 1455
1455 1456 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1456 1457 if error:
1457 1458 return {
1458 1459 'error': error,
1459 1460 'redirect_url': default_redirect_url
1460 1461 }
1461 1462 error = self.check_branch_permission(_branch_name, json_mode=True)
1462 1463 if error:
1463 1464 return {
1464 1465 'error': error,
1465 1466 'redirect_url': default_redirect_url
1466 1467 }
1467 1468
1468 1469 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1469 1470 c.f_path = f_path
1470 1471
1471 1472 r_post = self.request.POST
1472 1473
1473 1474 message = c.default_message
1474 1475 user_message = r_post.getall('message')
1475 1476 if isinstance(user_message, list) and user_message:
1476 1477 # we take the first from duplicated results if it's not empty
1477 1478 message = user_message[0] if user_message[0] else message
1478 1479
1479 1480 nodes = {}
1480 1481
1481 1482 for file_obj in r_post.getall('files_upload') or []:
1482 1483 content = file_obj.file
1483 1484 filename = file_obj.filename
1484 1485
1485 1486 root_path = f_path
1486 1487 pure_path = self.create_pure_path(root_path, filename)
1487 1488 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1488 1489
1489 1490 nodes[node_path] = {
1490 1491 'content': content
1491 1492 }
1492 1493
1493 1494 if not nodes:
1494 1495 error = 'missing files'
1495 1496 return {
1496 1497 'error': error,
1497 1498 'redirect_url': default_redirect_url
1498 1499 }
1499 1500
1500 1501 author = self._rhodecode_db_user.full_contact
1501 1502
1502 1503 try:
1503 1504 commit = ScmModel().create_nodes(
1504 1505 user=self._rhodecode_db_user.user_id,
1505 1506 repo=self.db_repo,
1506 1507 message=message,
1507 1508 nodes=nodes,
1508 1509 parent_commit=c.commit,
1509 1510 author=author,
1510 1511 )
1511 1512 if len(nodes) == 1:
1512 1513 flash_message = _('Successfully committed {} new files').format(len(nodes))
1513 1514 else:
1514 1515 flash_message = _('Successfully committed 1 new file')
1515 1516
1516 1517 h.flash(flash_message, category='success')
1517 1518
1518 1519 default_redirect_url = h.route_path(
1519 1520 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1520 1521
1521 1522 except NonRelativePathError:
1522 1523 log.exception('Non Relative path found')
1523 1524 error = _('The location specified must be a relative path and must not '
1524 1525 'contain .. in the path')
1525 1526 h.flash(error, category='warning')
1526 1527
1527 1528 return {
1528 1529 'error': error,
1529 1530 'redirect_url': default_redirect_url
1530 1531 }
1531 1532 except (NodeError, NodeAlreadyExistsError) as e:
1532 1533 error = h.escape(e)
1533 1534 h.flash(error, category='error')
1534 1535
1535 1536 return {
1536 1537 'error': error,
1537 1538 'redirect_url': default_redirect_url
1538 1539 }
1539 1540 except Exception:
1540 1541 log.exception('Error occurred during commit')
1541 1542 error = _('Error occurred during commit')
1542 1543 h.flash(error, category='error')
1543 1544 return {
1544 1545 'error': error,
1545 1546 'redirect_url': default_redirect_url
1546 1547 }
1547 1548
1548 1549 return {
1549 1550 'error': None,
1550 1551 'redirect_url': default_redirect_url
1551 1552 }
@@ -1,78 +1,81 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2015-2019 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 from dogpile.cache import register_backend
23 23
24 24 register_backend(
25 25 "dogpile.cache.rc.memory_lru", "rhodecode.lib.rc_cache.backends",
26 26 "LRUMemoryBackend")
27 27
28 28 register_backend(
29 29 "dogpile.cache.rc.file_namespace", "rhodecode.lib.rc_cache.backends",
30 30 "FileNamespaceBackend")
31 31
32 32 register_backend(
33 33 "dogpile.cache.rc.redis", "rhodecode.lib.rc_cache.backends",
34 34 "RedisPickleBackend")
35 35
36 36 register_backend(
37 37 "dogpile.cache.rc.redis_msgpack", "rhodecode.lib.rc_cache.backends",
38 38 "RedisMsgPackBackend")
39 39
40 40
41 41 log = logging.getLogger(__name__)
42 42
43 43 from . import region_meta
44 44 from .utils import (
45 45 get_default_cache_settings, backend_key_generator, get_or_create_region,
46 46 clear_cache_namespace, make_region, InvalidationContext,
47 47 FreshRegionCache, ActiveRegionCache)
48 48
49 49
50 FILE_TREE_CACHE_VER = 'v2'
51
52
50 53 def configure_dogpile_cache(settings):
51 54 cache_dir = settings.get('cache_dir')
52 55 if cache_dir:
53 56 region_meta.dogpile_config_defaults['cache_dir'] = cache_dir
54 57
55 58 rc_cache_data = get_default_cache_settings(settings, prefixes=['rc_cache.'])
56 59
57 60 # inspect available namespaces
58 61 avail_regions = set()
59 62 for key in rc_cache_data.keys():
60 63 namespace_name = key.split('.', 1)[0]
61 64 avail_regions.add(namespace_name)
62 65 log.debug('dogpile: found following cache regions: %s', avail_regions)
63 66
64 67 # register them into namespace
65 68 for region_name in avail_regions:
66 69 new_region = make_region(
67 70 name=region_name,
68 71 function_key_generator=None
69 72 )
70 73
71 74 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
72 75 new_region.function_key_generator = backend_key_generator(new_region.actual_backend)
73 76 log.debug('dogpile: registering a new region %s[%s]', region_name, new_region.__dict__)
74 77 region_meta.dogpile_cache_regions[region_name] = new_region
75 78
76 79
77 80 def includeme(config):
78 81 configure_dogpile_cache(config.registry.settings)
General Comments 0
You need to be logged in to leave comments. Login now