##// END OF EJS Templates
files: use custom lexer on mako files....
marcink -
r1584:e5e81e45 default
parent child Browse files
Show More
@@ -1,1037 +1,1042 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Utilities library for RhodeCode
23 23 """
24 24
25 25 import datetime
26 26 import decorator
27 27 import json
28 28 import logging
29 29 import os
30 30 import re
31 31 import shutil
32 32 import tempfile
33 33 import traceback
34 34 import tarfile
35 35 import warnings
36 36 import hashlib
37 37 from os.path import join as jn
38 38
39 39 import paste
40 40 import pkg_resources
41 41 from paste.script.command import Command, BadCommand
42 42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 43 from mako import exceptions
44 44 from pyramid.threadlocal import get_current_registry
45 45 from pyramid.request import Request
46 46
47 47 from rhodecode.lib.fakemod import create_module
48 48 from rhodecode.lib.vcs.backends.base import Config
49 49 from rhodecode.lib.vcs.exceptions import VCSError
50 50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
51 51 from rhodecode.lib.utils2 import (
52 52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
53 53 from rhodecode.model import meta
54 54 from rhodecode.model.db import (
55 55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
56 56 from rhodecode.model.meta import Session
57 57
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
62 62
63 63 # String which contains characters that are not allowed in slug names for
64 64 # repositories or repository groups. It is properly escaped to use it in
65 65 # regular expressions.
66 66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
67 67
68 68 # Regex that matches forbidden characters in repo/group slugs.
69 69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
70 70
71 71 # Regex that matches allowed characters in repo/group slugs.
72 72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
73 73
74 74 # Regex that matches whole repo/group slugs.
75 75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
76 76
77 77 _license_cache = None
78 78
79 79
80 80 def repo_name_slug(value):
81 81 """
82 82 Return slug of name of repository
83 83 This function is called on each creation/modification
84 84 of repository to prevent bad names in repo
85 85 """
86 86 replacement_char = '-'
87 87
88 88 slug = remove_formatting(value)
89 89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
90 90 slug = re.sub('[\s]+', '-', slug)
91 91 slug = collapse(slug, replacement_char)
92 92 return slug
93 93
94 94
95 95 #==============================================================================
96 96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
97 97 #==============================================================================
98 98 def get_repo_slug(request):
99 99 if isinstance(request, Request) and getattr(request, 'matchdict', None):
100 100 # pyramid
101 101 _repo = request.matchdict.get('repo_name')
102 102 else:
103 103 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 104
105 105 if _repo:
106 106 _repo = _repo.rstrip('/')
107 107 return _repo
108 108
109 109
110 110 def get_repo_group_slug(request):
111 111 if isinstance(request, Request) and getattr(request, 'matchdict', None):
112 112 # pyramid
113 113 _group = request.matchdict.get('group_name')
114 114 else:
115 115 _group = request.environ['pylons.routes_dict'].get('group_name')
116 116
117 117 if _group:
118 118 _group = _group.rstrip('/')
119 119 return _group
120 120
121 121
122 122 def get_user_group_slug(request):
123 123 if isinstance(request, Request) and getattr(request, 'matchdict', None):
124 124 # pyramid
125 125 _group = request.matchdict.get('user_group_id')
126 126 else:
127 127 _group = request.environ['pylons.routes_dict'].get('user_group_id')
128 128
129 129 try:
130 130 _group = UserGroup.get(_group)
131 131 if _group:
132 132 _group = _group.users_group_name
133 133 except Exception:
134 134 log.debug(traceback.format_exc())
135 135 # catch all failures here
136 136 pass
137 137
138 138 return _group
139 139
140 140
141 141 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
142 142 """
143 143 Action logger for various actions made by users
144 144
145 145 :param user: user that made this action, can be a unique username string or
146 146 object containing user_id attribute
147 147 :param action: action to log, should be on of predefined unique actions for
148 148 easy translations
149 149 :param repo: string name of repository or object containing repo_id,
150 150 that action was made on
151 151 :param ipaddr: optional ip address from what the action was made
152 152 :param sa: optional sqlalchemy session
153 153
154 154 """
155 155
156 156 if not sa:
157 157 sa = meta.Session()
158 158 # if we don't get explicit IP address try to get one from registered user
159 159 # in tmpl context var
160 160 if not ipaddr:
161 161 ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
162 162
163 163 try:
164 164 if getattr(user, 'user_id', None):
165 165 user_obj = User.get(user.user_id)
166 166 elif isinstance(user, basestring):
167 167 user_obj = User.get_by_username(user)
168 168 else:
169 169 raise Exception('You have to provide a user object or a username')
170 170
171 171 if getattr(repo, 'repo_id', None):
172 172 repo_obj = Repository.get(repo.repo_id)
173 173 repo_name = repo_obj.repo_name
174 174 elif isinstance(repo, basestring):
175 175 repo_name = repo.lstrip('/')
176 176 repo_obj = Repository.get_by_repo_name(repo_name)
177 177 else:
178 178 repo_obj = None
179 179 repo_name = ''
180 180
181 181 user_log = UserLog()
182 182 user_log.user_id = user_obj.user_id
183 183 user_log.username = user_obj.username
184 184 action = safe_unicode(action)
185 185 user_log.action = action[:1200000]
186 186
187 187 user_log.repository = repo_obj
188 188 user_log.repository_name = repo_name
189 189
190 190 user_log.action_date = datetime.datetime.now()
191 191 user_log.user_ip = ipaddr
192 192 sa.add(user_log)
193 193
194 194 log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s',
195 195 action, safe_unicode(repo), user_obj, ipaddr)
196 196 if commit:
197 197 sa.commit()
198 198 except Exception:
199 199 log.error(traceback.format_exc())
200 200 raise
201 201
202 202
203 203 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
204 204 """
205 205 Scans given path for repos and return (name,(type,path)) tuple
206 206
207 207 :param path: path to scan for repositories
208 208 :param recursive: recursive search and return names with subdirs in front
209 209 """
210 210
211 211 # remove ending slash for better results
212 212 path = path.rstrip(os.sep)
213 213 log.debug('now scanning in %s location recursive:%s...', path, recursive)
214 214
215 215 def _get_repos(p):
216 216 dirpaths = _get_dirpaths(p)
217 217 if not _is_dir_writable(p):
218 218 log.warning('repo path without write access: %s', p)
219 219
220 220 for dirpath in dirpaths:
221 221 if os.path.isfile(os.path.join(p, dirpath)):
222 222 continue
223 223 cur_path = os.path.join(p, dirpath)
224 224
225 225 # skip removed repos
226 226 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
227 227 continue
228 228
229 229 #skip .<somethin> dirs
230 230 if dirpath.startswith('.'):
231 231 continue
232 232
233 233 try:
234 234 scm_info = get_scm(cur_path)
235 235 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
236 236 except VCSError:
237 237 if not recursive:
238 238 continue
239 239 #check if this dir containts other repos for recursive scan
240 240 rec_path = os.path.join(p, dirpath)
241 241 if os.path.isdir(rec_path):
242 242 for inner_scm in _get_repos(rec_path):
243 243 yield inner_scm
244 244
245 245 return _get_repos(path)
246 246
247 247
248 248 def _get_dirpaths(p):
249 249 try:
250 250 # OS-independable way of checking if we have at least read-only
251 251 # access or not.
252 252 dirpaths = os.listdir(p)
253 253 except OSError:
254 254 log.warning('ignoring repo path without read access: %s', p)
255 255 return []
256 256
257 257 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
258 258 # decode paths and suddenly returns unicode objects itself. The items it
259 259 # cannot decode are returned as strings and cause issues.
260 260 #
261 261 # Those paths are ignored here until a solid solution for path handling has
262 262 # been built.
263 263 expected_type = type(p)
264 264
265 265 def _has_correct_type(item):
266 266 if type(item) is not expected_type:
267 267 log.error(
268 268 u"Ignoring path %s since it cannot be decoded into unicode.",
269 269 # Using "repr" to make sure that we see the byte value in case
270 270 # of support.
271 271 repr(item))
272 272 return False
273 273 return True
274 274
275 275 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
276 276
277 277 return dirpaths
278 278
279 279
280 280 def _is_dir_writable(path):
281 281 """
282 282 Probe if `path` is writable.
283 283
284 284 Due to trouble on Cygwin / Windows, this is actually probing if it is
285 285 possible to create a file inside of `path`, stat does not produce reliable
286 286 results in this case.
287 287 """
288 288 try:
289 289 with tempfile.TemporaryFile(dir=path):
290 290 pass
291 291 except OSError:
292 292 return False
293 293 return True
294 294
295 295
296 296 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
297 297 """
298 298 Returns True if given path is a valid repository False otherwise.
299 299 If expect_scm param is given also, compare if given scm is the same
300 300 as expected from scm parameter. If explicit_scm is given don't try to
301 301 detect the scm, just use the given one to check if repo is valid
302 302
303 303 :param repo_name:
304 304 :param base_path:
305 305 :param expect_scm:
306 306 :param explicit_scm:
307 307
308 308 :return True: if given path is a valid repository
309 309 """
310 310 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
311 311 log.debug('Checking if `%s` is a valid path for repository. '
312 312 'Explicit type: %s', repo_name, explicit_scm)
313 313
314 314 try:
315 315 if explicit_scm:
316 316 detected_scms = [get_scm_backend(explicit_scm)]
317 317 else:
318 318 detected_scms = get_scm(full_path)
319 319
320 320 if expect_scm:
321 321 return detected_scms[0] == expect_scm
322 322 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
323 323 return True
324 324 except VCSError:
325 325 log.debug('path: %s is not a valid repo !', full_path)
326 326 return False
327 327
328 328
329 329 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
330 330 """
331 331 Returns True if given path is a repository group, False otherwise
332 332
333 333 :param repo_name:
334 334 :param base_path:
335 335 """
336 336 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
337 337 log.debug('Checking if `%s` is a valid path for repository group',
338 338 repo_group_name)
339 339
340 340 # check if it's not a repo
341 341 if is_valid_repo(repo_group_name, base_path):
342 342 log.debug('Repo called %s exist, it is not a valid '
343 343 'repo group' % repo_group_name)
344 344 return False
345 345
346 346 try:
347 347 # we need to check bare git repos at higher level
348 348 # since we might match branches/hooks/info/objects or possible
349 349 # other things inside bare git repo
350 350 scm_ = get_scm(os.path.dirname(full_path))
351 351 log.debug('path: %s is a vcs object:%s, not valid '
352 352 'repo group' % (full_path, scm_))
353 353 return False
354 354 except VCSError:
355 355 pass
356 356
357 357 # check if it's a valid path
358 358 if skip_path_check or os.path.isdir(full_path):
359 359 log.debug('path: %s is a valid repo group !', full_path)
360 360 return True
361 361
362 362 log.debug('path: %s is not a valid repo group !', full_path)
363 363 return False
364 364
365 365
366 366 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
367 367 while True:
368 368 ok = raw_input(prompt)
369 369 if ok.lower() in ('y', 'ye', 'yes'):
370 370 return True
371 371 if ok.lower() in ('n', 'no', 'nop', 'nope'):
372 372 return False
373 373 retries = retries - 1
374 374 if retries < 0:
375 375 raise IOError
376 376 print(complaint)
377 377
378 378 # propagated from mercurial documentation
379 379 ui_sections = [
380 380 'alias', 'auth',
381 381 'decode/encode', 'defaults',
382 382 'diff', 'email',
383 383 'extensions', 'format',
384 384 'merge-patterns', 'merge-tools',
385 385 'hooks', 'http_proxy',
386 386 'smtp', 'patch',
387 387 'paths', 'profiling',
388 388 'server', 'trusted',
389 389 'ui', 'web', ]
390 390
391 391
392 392 def config_data_from_db(clear_session=True, repo=None):
393 393 """
394 394 Read the configuration data from the database and return configuration
395 395 tuples.
396 396 """
397 397 from rhodecode.model.settings import VcsSettingsModel
398 398
399 399 config = []
400 400
401 401 sa = meta.Session()
402 402 settings_model = VcsSettingsModel(repo=repo, sa=sa)
403 403
404 404 ui_settings = settings_model.get_ui_settings()
405 405
406 406 for setting in ui_settings:
407 407 if setting.active:
408 408 log.debug(
409 409 'settings ui from db: [%s] %s=%s',
410 410 setting.section, setting.key, setting.value)
411 411 config.append((
412 412 safe_str(setting.section), safe_str(setting.key),
413 413 safe_str(setting.value)))
414 414 if setting.key == 'push_ssl':
415 415 # force set push_ssl requirement to False, rhodecode
416 416 # handles that
417 417 config.append((
418 418 safe_str(setting.section), safe_str(setting.key), False))
419 419 if clear_session:
420 420 meta.Session.remove()
421 421
422 422 # TODO: mikhail: probably it makes no sense to re-read hooks information.
423 423 # It's already there and activated/deactivated
424 424 skip_entries = []
425 425 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
426 426 if 'pull' not in enabled_hook_classes:
427 427 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
428 428 if 'push' not in enabled_hook_classes:
429 429 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
430 430 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
431 431
432 432 config = [entry for entry in config if entry[:2] not in skip_entries]
433 433
434 434 return config
435 435
436 436
437 437 def make_db_config(clear_session=True, repo=None):
438 438 """
439 439 Create a :class:`Config` instance based on the values in the database.
440 440 """
441 441 config = Config()
442 442 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
443 443 for section, option, value in config_data:
444 444 config.set(section, option, value)
445 445 return config
446 446
447 447
448 448 def get_enabled_hook_classes(ui_settings):
449 449 """
450 450 Return the enabled hook classes.
451 451
452 452 :param ui_settings: List of ui_settings as returned
453 453 by :meth:`VcsSettingsModel.get_ui_settings`
454 454
455 455 :return: a list with the enabled hook classes. The order is not guaranteed.
456 456 :rtype: list
457 457 """
458 458 enabled_hooks = []
459 459 active_hook_keys = [
460 460 key for section, key, value, active in ui_settings
461 461 if section == 'hooks' and active]
462 462
463 463 hook_names = {
464 464 RhodeCodeUi.HOOK_PUSH: 'push',
465 465 RhodeCodeUi.HOOK_PULL: 'pull',
466 466 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
467 467 }
468 468
469 469 for key in active_hook_keys:
470 470 hook = hook_names.get(key)
471 471 if hook:
472 472 enabled_hooks.append(hook)
473 473
474 474 return enabled_hooks
475 475
476 476
477 477 def set_rhodecode_config(config):
478 478 """
479 479 Updates pylons config with new settings from database
480 480
481 481 :param config:
482 482 """
483 483 from rhodecode.model.settings import SettingsModel
484 484 app_settings = SettingsModel().get_all_settings()
485 485
486 486 for k, v in app_settings.items():
487 487 config[k] = v
488 488
489 489
490 490 def get_rhodecode_realm():
491 491 """
492 492 Return the rhodecode realm from database.
493 493 """
494 494 from rhodecode.model.settings import SettingsModel
495 495 realm = SettingsModel().get_setting_by_name('realm')
496 496 return safe_str(realm.app_settings_value)
497 497
498 498
499 499 def get_rhodecode_base_path():
500 500 """
501 501 Returns the base path. The base path is the filesystem path which points
502 502 to the repository store.
503 503 """
504 504 from rhodecode.model.settings import SettingsModel
505 505 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
506 506 return safe_str(paths_ui.ui_value)
507 507
508 508
509 509 def map_groups(path):
510 510 """
511 511 Given a full path to a repository, create all nested groups that this
512 512 repo is inside. This function creates parent-child relationships between
513 513 groups and creates default perms for all new groups.
514 514
515 515 :param paths: full path to repository
516 516 """
517 517 from rhodecode.model.repo_group import RepoGroupModel
518 518 sa = meta.Session()
519 519 groups = path.split(Repository.NAME_SEP)
520 520 parent = None
521 521 group = None
522 522
523 523 # last element is repo in nested groups structure
524 524 groups = groups[:-1]
525 525 rgm = RepoGroupModel(sa)
526 526 owner = User.get_first_super_admin()
527 527 for lvl, group_name in enumerate(groups):
528 528 group_name = '/'.join(groups[:lvl] + [group_name])
529 529 group = RepoGroup.get_by_group_name(group_name)
530 530 desc = '%s group' % group_name
531 531
532 532 # skip folders that are now removed repos
533 533 if REMOVED_REPO_PAT.match(group_name):
534 534 break
535 535
536 536 if group is None:
537 537 log.debug('creating group level: %s group_name: %s',
538 538 lvl, group_name)
539 539 group = RepoGroup(group_name, parent)
540 540 group.group_description = desc
541 541 group.user = owner
542 542 sa.add(group)
543 543 perm_obj = rgm._create_default_perms(group)
544 544 sa.add(perm_obj)
545 545 sa.flush()
546 546
547 547 parent = group
548 548 return group
549 549
550 550
551 551 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
552 552 """
553 553 maps all repos given in initial_repo_list, non existing repositories
554 554 are created, if remove_obsolete is True it also checks for db entries
555 555 that are not in initial_repo_list and removes them.
556 556
557 557 :param initial_repo_list: list of repositories found by scanning methods
558 558 :param remove_obsolete: check for obsolete entries in database
559 559 """
560 560 from rhodecode.model.repo import RepoModel
561 561 from rhodecode.model.scm import ScmModel
562 562 from rhodecode.model.repo_group import RepoGroupModel
563 563 from rhodecode.model.settings import SettingsModel
564 564
565 565 sa = meta.Session()
566 566 repo_model = RepoModel()
567 567 user = User.get_first_super_admin()
568 568 added = []
569 569
570 570 # creation defaults
571 571 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
572 572 enable_statistics = defs.get('repo_enable_statistics')
573 573 enable_locking = defs.get('repo_enable_locking')
574 574 enable_downloads = defs.get('repo_enable_downloads')
575 575 private = defs.get('repo_private')
576 576
577 577 for name, repo in initial_repo_list.items():
578 578 group = map_groups(name)
579 579 unicode_name = safe_unicode(name)
580 580 db_repo = repo_model.get_by_repo_name(unicode_name)
581 581 # found repo that is on filesystem not in RhodeCode database
582 582 if not db_repo:
583 583 log.info('repository %s not found, creating now', name)
584 584 added.append(name)
585 585 desc = (repo.description
586 586 if repo.description != 'unknown'
587 587 else '%s repository' % name)
588 588
589 589 db_repo = repo_model._create_repo(
590 590 repo_name=name,
591 591 repo_type=repo.alias,
592 592 description=desc,
593 593 repo_group=getattr(group, 'group_id', None),
594 594 owner=user,
595 595 enable_locking=enable_locking,
596 596 enable_downloads=enable_downloads,
597 597 enable_statistics=enable_statistics,
598 598 private=private,
599 599 state=Repository.STATE_CREATED
600 600 )
601 601 sa.commit()
602 602 # we added that repo just now, and make sure we updated server info
603 603 if db_repo.repo_type == 'git':
604 604 git_repo = db_repo.scm_instance()
605 605 # update repository server-info
606 606 log.debug('Running update server info')
607 607 git_repo._update_server_info()
608 608
609 609 db_repo.update_commit_cache()
610 610
611 611 config = db_repo._config
612 612 config.set('extensions', 'largefiles', '')
613 613 ScmModel().install_hooks(
614 614 db_repo.scm_instance(config=config),
615 615 repo_type=db_repo.repo_type)
616 616
617 617 removed = []
618 618 if remove_obsolete:
619 619 # remove from database those repositories that are not in the filesystem
620 620 for repo in sa.query(Repository).all():
621 621 if repo.repo_name not in initial_repo_list.keys():
622 622 log.debug("Removing non-existing repository found in db `%s`",
623 623 repo.repo_name)
624 624 try:
625 625 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
626 626 sa.commit()
627 627 removed.append(repo.repo_name)
628 628 except Exception:
629 629 # don't hold further removals on error
630 630 log.error(traceback.format_exc())
631 631 sa.rollback()
632 632
633 633 def splitter(full_repo_name):
634 634 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
635 635 gr_name = None
636 636 if len(_parts) == 2:
637 637 gr_name = _parts[0]
638 638 return gr_name
639 639
640 640 initial_repo_group_list = [splitter(x) for x in
641 641 initial_repo_list.keys() if splitter(x)]
642 642
643 643 # remove from database those repository groups that are not in the
644 644 # filesystem due to parent child relationships we need to delete them
645 645 # in a specific order of most nested first
646 646 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
647 647 nested_sort = lambda gr: len(gr.split('/'))
648 648 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
649 649 if group_name not in initial_repo_group_list:
650 650 repo_group = RepoGroup.get_by_group_name(group_name)
651 651 if (repo_group.children.all() or
652 652 not RepoGroupModel().check_exist_filesystem(
653 653 group_name=group_name, exc_on_failure=False)):
654 654 continue
655 655
656 656 log.info(
657 657 'Removing non-existing repository group found in db `%s`',
658 658 group_name)
659 659 try:
660 660 RepoGroupModel(sa).delete(group_name, fs_remove=False)
661 661 sa.commit()
662 662 removed.append(group_name)
663 663 except Exception:
664 664 # don't hold further removals on error
665 665 log.exception(
666 666 'Unable to remove repository group `%s`',
667 667 group_name)
668 668 sa.rollback()
669 669 raise
670 670
671 671 return added, removed
672 672
673 673
674 674 def get_default_cache_settings(settings):
675 675 cache_settings = {}
676 676 for key in settings.keys():
677 677 for prefix in ['beaker.cache.', 'cache.']:
678 678 if key.startswith(prefix):
679 679 name = key.split(prefix)[1].strip()
680 680 cache_settings[name] = settings[key].strip()
681 681 return cache_settings
682 682
683 683
684 684 # set cache regions for beaker so celery can utilise it
685 685 def add_cache(settings):
686 686 from rhodecode.lib import caches
687 687 cache_settings = {'regions': None}
688 688 # main cache settings used as default ...
689 689 cache_settings.update(get_default_cache_settings(settings))
690 690
691 691 if cache_settings['regions']:
692 692 for region in cache_settings['regions'].split(','):
693 693 region = region.strip()
694 694 region_settings = {}
695 695 for key, value in cache_settings.items():
696 696 if key.startswith(region):
697 697 region_settings[key.split('.')[1]] = value
698 698
699 699 caches.configure_cache_region(
700 700 region, region_settings, cache_settings)
701 701
702 702
703 703 def load_rcextensions(root_path):
704 704 import rhodecode
705 705 from rhodecode.config import conf
706 706
707 707 path = os.path.join(root_path, 'rcextensions', '__init__.py')
708 708 if os.path.isfile(path):
709 709 rcext = create_module('rc', path)
710 710 EXT = rhodecode.EXTENSIONS = rcext
711 711 log.debug('Found rcextensions now loading %s...', rcext)
712 712
713 713 # Additional mappings that are not present in the pygments lexers
714 714 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
715 715
716 716 # auto check if the module is not missing any data, set to default if is
717 717 # this will help autoupdate new feature of rcext module
718 718 #from rhodecode.config import rcextensions
719 719 #for k in dir(rcextensions):
720 720 # if not k.startswith('_') and not hasattr(EXT, k):
721 721 # setattr(EXT, k, getattr(rcextensions, k))
722 722
723 723
724 724 def get_custom_lexer(extension):
725 725 """
726 726 returns a custom lexer if it is defined in rcextensions module, or None
727 727 if there's no custom lexer defined
728 728 """
729 729 import rhodecode
730 730 from pygments import lexers
731
732 # custom override made by RhodeCode
733 if extension in ['mako']:
734 return lexers.get_lexer_by_name('Mako')
735
731 736 # check if we didn't define this extension as other lexer
732 737 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
733 738 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
734 739 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
735 740 return lexers.get_lexer_by_name(_lexer_name)
736 741
737 742
738 743 #==============================================================================
739 744 # TEST FUNCTIONS AND CREATORS
740 745 #==============================================================================
741 746 def create_test_index(repo_location, config):
742 747 """
743 748 Makes default test index.
744 749 """
745 750 import rc_testdata
746 751
747 752 rc_testdata.extract_search_index(
748 753 'vcs_search_index', os.path.dirname(config['search.location']))
749 754
750 755
751 756 def create_test_directory(test_path):
752 757 """
753 758 Create test directory if it doesn't exist.
754 759 """
755 760 if not os.path.isdir(test_path):
756 761 log.debug('Creating testdir %s', test_path)
757 762 os.makedirs(test_path)
758 763
759 764
760 765 def create_test_database(test_path, config):
761 766 """
762 767 Makes a fresh database.
763 768 """
764 769 from rhodecode.lib.db_manage import DbManage
765 770
766 771 # PART ONE create db
767 772 dbconf = config['sqlalchemy.db1.url']
768 773 log.debug('making test db %s', dbconf)
769 774
770 775 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
771 776 tests=True, cli_args={'force_ask': True})
772 777 dbmanage.create_tables(override=True)
773 778 dbmanage.set_db_version()
774 779 # for tests dynamically set new root paths based on generated content
775 780 dbmanage.create_settings(dbmanage.config_prompt(test_path))
776 781 dbmanage.create_default_user()
777 782 dbmanage.create_test_admin_and_users()
778 783 dbmanage.create_permissions()
779 784 dbmanage.populate_default_permissions()
780 785 Session().commit()
781 786
782 787
783 788 def create_test_repositories(test_path, config):
784 789 """
785 790 Creates test repositories in the temporary directory. Repositories are
786 791 extracted from archives within the rc_testdata package.
787 792 """
788 793 import rc_testdata
789 794 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
790 795
791 796 log.debug('making test vcs repositories')
792 797
793 798 idx_path = config['search.location']
794 799 data_path = config['cache_dir']
795 800
796 801 # clean index and data
797 802 if idx_path and os.path.exists(idx_path):
798 803 log.debug('remove %s', idx_path)
799 804 shutil.rmtree(idx_path)
800 805
801 806 if data_path and os.path.exists(data_path):
802 807 log.debug('remove %s', data_path)
803 808 shutil.rmtree(data_path)
804 809
805 810 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
806 811 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
807 812
808 813 # Note: Subversion is in the process of being integrated with the system,
809 814 # until we have a properly packed version of the test svn repository, this
810 815 # tries to copy over the repo from a package "rc_testdata"
811 816 svn_repo_path = rc_testdata.get_svn_repo_archive()
812 817 with tarfile.open(svn_repo_path) as tar:
813 818 tar.extractall(jn(test_path, SVN_REPO))
814 819
815 820
816 821 #==============================================================================
817 822 # PASTER COMMANDS
818 823 #==============================================================================
819 824 class BasePasterCommand(Command):
820 825 """
821 826 Abstract Base Class for paster commands.
822 827
823 828 The celery commands are somewhat aggressive about loading
824 829 celery.conf, and since our module sets the `CELERY_LOADER`
825 830 environment variable to our loader, we have to bootstrap a bit and
826 831 make sure we've had a chance to load the pylons config off of the
827 832 command line, otherwise everything fails.
828 833 """
829 834 min_args = 1
830 835 min_args_error = "Please provide a paster config file as an argument."
831 836 takes_config_file = 1
832 837 requires_config_file = True
833 838
834 839 def notify_msg(self, msg, log=False):
835 840 """Make a notification to user, additionally if logger is passed
836 841 it logs this action using given logger
837 842
838 843 :param msg: message that will be printed to user
839 844 :param log: logging instance, to use to additionally log this message
840 845
841 846 """
842 847 if log and isinstance(log, logging):
843 848 log(msg)
844 849
845 850 def run(self, args):
846 851 """
847 852 Overrides Command.run
848 853
849 854 Checks for a config file argument and loads it.
850 855 """
851 856 if len(args) < self.min_args:
852 857 raise BadCommand(
853 858 self.min_args_error % {'min_args': self.min_args,
854 859 'actual_args': len(args)})
855 860
856 861 # Decrement because we're going to lob off the first argument.
857 862 # @@ This is hacky
858 863 self.min_args -= 1
859 864 self.bootstrap_config(args[0])
860 865 self.update_parser()
861 866 return super(BasePasterCommand, self).run(args[1:])
862 867
863 868 def update_parser(self):
864 869 """
865 870 Abstract method. Allows for the class' parser to be updated
866 871 before the superclass' `run` method is called. Necessary to
867 872 allow options/arguments to be passed through to the underlying
868 873 celery command.
869 874 """
870 875 raise NotImplementedError("Abstract Method.")
871 876
872 877 def bootstrap_config(self, conf):
873 878 """
874 879 Loads the pylons configuration.
875 880 """
876 881 from pylons import config as pylonsconfig
877 882
878 883 self.path_to_ini_file = os.path.realpath(conf)
879 884 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
880 885 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
881 886
882 887 def _init_session(self):
883 888 """
884 889 Inits SqlAlchemy Session
885 890 """
886 891 logging.config.fileConfig(self.path_to_ini_file)
887 892 from pylons import config
888 893 from rhodecode.config.utils import initialize_database
889 894
890 895 # get to remove repos !!
891 896 add_cache(config)
892 897 initialize_database(config)
893 898
894 899
895 900 @decorator.decorator
896 901 def jsonify(func, *args, **kwargs):
897 902 """Action decorator that formats output for JSON
898 903
899 904 Given a function that will return content, this decorator will turn
900 905 the result into JSON, with a content-type of 'application/json' and
901 906 output it.
902 907
903 908 """
904 909 from pylons.decorators.util import get_pylons
905 910 from rhodecode.lib.ext_json import json
906 911 pylons = get_pylons(args)
907 912 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
908 913 data = func(*args, **kwargs)
909 914 if isinstance(data, (list, tuple)):
910 915 msg = "JSON responses with Array envelopes are susceptible to " \
911 916 "cross-site data leak attacks, see " \
912 917 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
913 918 warnings.warn(msg, Warning, 2)
914 919 log.warning(msg)
915 920 log.debug("Returning JSON wrapped action output")
916 921 return json.dumps(data, encoding='utf-8')
917 922
918 923
919 924 class PartialRenderer(object):
920 925 """
921 926 Partial renderer used to render chunks of html used in datagrids
922 927 use like::
923 928
924 929 _render = PartialRenderer('data_table/_dt_elements.mako')
925 930 _render('quick_menu', args, kwargs)
926 931 PartialRenderer.h,
927 932 c,
928 933 _,
929 934 ungettext
930 935 are the template stuff initialized inside and can be re-used later
931 936
932 937 :param tmpl_name: template path relate to /templates/ dir
933 938 """
934 939
935 940 def __init__(self, tmpl_name):
936 941 import rhodecode
937 942 from pylons import request, tmpl_context as c
938 943 from pylons.i18n.translation import _, ungettext
939 944 from rhodecode.lib import helpers as h
940 945
941 946 self.tmpl_name = tmpl_name
942 947 self.rhodecode = rhodecode
943 948 self.c = c
944 949 self._ = _
945 950 self.ungettext = ungettext
946 951 self.h = h
947 952 self.request = request
948 953
949 954 def _mako_lookup(self):
950 955 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
951 956 return _tmpl_lookup.get_template(self.tmpl_name)
952 957
953 958 def _update_kwargs_for_render(self, kwargs):
954 959 """
955 960 Inject params required for Mako rendering
956 961 """
957 962 _kwargs = {
958 963 '_': self._,
959 964 'h': self.h,
960 965 'c': self.c,
961 966 'request': self.request,
962 967 'ungettext': self.ungettext,
963 968 }
964 969 _kwargs.update(kwargs)
965 970 return _kwargs
966 971
967 972 def _render_with_exc(self, render_func, args, kwargs):
968 973 try:
969 974 return render_func.render(*args, **kwargs)
970 975 except:
971 976 log.error(exceptions.text_error_template().render())
972 977 raise
973 978
974 979 def _get_template(self, template_obj, def_name):
975 980 if def_name:
976 981 tmpl = template_obj.get_def(def_name)
977 982 else:
978 983 tmpl = template_obj
979 984 return tmpl
980 985
981 986 def render(self, def_name, *args, **kwargs):
982 987 lookup_obj = self._mako_lookup()
983 988 tmpl = self._get_template(lookup_obj, def_name=def_name)
984 989 kwargs = self._update_kwargs_for_render(kwargs)
985 990 return self._render_with_exc(tmpl, args, kwargs)
986 991
987 992 def __call__(self, tmpl, *args, **kwargs):
988 993 return self.render(tmpl, *args, **kwargs)
989 994
990 995
991 996 def password_changed(auth_user, session):
992 997 # Never report password change in case of default user or anonymous user.
993 998 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
994 999 return False
995 1000
996 1001 password_hash = md5(auth_user.password) if auth_user.password else None
997 1002 rhodecode_user = session.get('rhodecode_user', {})
998 1003 session_password_hash = rhodecode_user.get('password', '')
999 1004 return password_hash != session_password_hash
1000 1005
1001 1006
1002 1007 def read_opensource_licenses():
1003 1008 global _license_cache
1004 1009
1005 1010 if not _license_cache:
1006 1011 licenses = pkg_resources.resource_string(
1007 1012 'rhodecode', 'config/licenses.json')
1008 1013 _license_cache = json.loads(licenses)
1009 1014
1010 1015 return _license_cache
1011 1016
1012 1017
1013 1018 def get_registry(request):
1014 1019 """
1015 1020 Utility to get the pyramid registry from a request. During migration to
1016 1021 pyramid we sometimes want to use the pyramid registry from pylons context.
1017 1022 Therefore this utility returns `request.registry` for pyramid requests and
1018 1023 uses `get_current_registry()` for pylons requests.
1019 1024 """
1020 1025 try:
1021 1026 return request.registry
1022 1027 except AttributeError:
1023 1028 return get_current_registry()
1024 1029
1025 1030
1026 1031 def generate_platform_uuid():
1027 1032 """
1028 1033 Generates platform UUID based on it's name
1029 1034 """
1030 1035 import platform
1031 1036
1032 1037 try:
1033 1038 uuid_list = [platform.platform()]
1034 1039 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
1035 1040 except Exception as e:
1036 1041 log.error('Failed to generate host uuid: %s' % e)
1037 1042 return 'UNDEFINED'
General Comments 0
You need to be logged in to leave comments. Login now