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