##// END OF EJS Templates
fixed UnicodeWarning on pushing from sqlalchemy
marcink -
r2249:a3eb31cc beta
parent child Browse files
Show More
@@ -1,666 +1,666 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import re
28 28 import logging
29 29 import datetime
30 30 import traceback
31 31 import paste
32 32 import beaker
33 33 import tarfile
34 34 import shutil
35 35 from os.path import abspath
36 36 from os.path import dirname as dn, join as jn
37 37
38 38 from paste.script.command import Command, BadCommand
39 39
40 40 from mercurial import ui, config
41 41
42 42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 43
44 44 from rhodecode.lib.vcs import get_backend
45 45 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.utils.helpers import get_scm
48 48 from rhodecode.lib.vcs.exceptions import VCSError
49 49
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 from rhodecode.model import meta
53 53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 54 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm,\
55 55 CacheInvalidation
56 56 from rhodecode.model.meta import Session
57 57 from rhodecode.model.repos_group import ReposGroupModel
58 58 from rhodecode.lib.utils2 import safe_str, safe_unicode
59 59 from rhodecode.lib.vcs.utils.fakemod import create_module
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
64 64
65 65
66 66 def recursive_replace(str_, replace=' '):
67 67 """
68 68 Recursive replace of given sign to just one instance
69 69
70 70 :param str_: given string
71 71 :param replace: char to find and replace multiple instances
72 72
73 73 Examples::
74 74 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
75 75 'Mighty-Mighty-Bo-sstones'
76 76 """
77 77
78 78 if str_.find(replace * 2) == -1:
79 79 return str_
80 80 else:
81 81 str_ = str_.replace(replace * 2, replace)
82 82 return recursive_replace(str_, replace)
83 83
84 84
85 85 def repo_name_slug(value):
86 86 """
87 87 Return slug of name of repository
88 88 This function is called on each creation/modification
89 89 of repository to prevent bad names in repo
90 90 """
91 91
92 92 slug = remove_formatting(value)
93 93 slug = strip_tags(slug)
94 94
95 95 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
96 96 slug = slug.replace(c, '-')
97 97 slug = recursive_replace(slug, '-')
98 98 slug = collapse(slug, '-')
99 99 return slug
100 100
101 101
102 102 def get_repo_slug(request):
103 103 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 104 if _repo:
105 105 _repo = _repo.rstrip('/')
106 106 return _repo
107 107
108 108
109 109 def get_repos_group_slug(request):
110 110 _group = request.environ['pylons.routes_dict'].get('group_name')
111 111 if _group:
112 112 _group = _group.rstrip('/')
113 113 return _group
114 114
115 115
116 116 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
117 117 """
118 118 Action logger for various actions made by users
119 119
120 120 :param user: user that made this action, can be a unique username string or
121 121 object containing user_id attribute
122 122 :param action: action to log, should be on of predefined unique actions for
123 123 easy translations
124 124 :param repo: string name of repository or object containing repo_id,
125 125 that action was made on
126 126 :param ipaddr: optional ip address from what the action was made
127 127 :param sa: optional sqlalchemy session
128 128
129 129 """
130 130
131 131 if not sa:
132 132 sa = meta.Session
133 133
134 134 try:
135 135 if hasattr(user, 'user_id'):
136 136 user_obj = user
137 137 elif isinstance(user, basestring):
138 138 user_obj = User.get_by_username(user)
139 139 else:
140 140 raise Exception('You have to provide user object or username')
141 141
142 142 if hasattr(repo, 'repo_id'):
143 143 repo_obj = Repository.get(repo.repo_id)
144 144 repo_name = repo_obj.repo_name
145 145 elif isinstance(repo, basestring):
146 146 repo_name = repo.lstrip('/')
147 147 repo_obj = Repository.get_by_repo_name(repo_name)
148 148 else:
149 149 raise Exception('You have to provide repository to action logger')
150 150
151 151 user_log = UserLog()
152 152 user_log.user_id = user_obj.user_id
153 user_log.action = action
153 user_log.action = safe_unicode(action)
154 154
155 155 user_log.repository_id = repo_obj.repo_id
156 156 user_log.repository_name = repo_name
157 157
158 158 user_log.action_date = datetime.datetime.now()
159 159 user_log.user_ip = ipaddr
160 160 sa.add(user_log)
161 161
162 162 log.info(
163 163 'Adding user %s, action %s on %s' % (user_obj, action,
164 164 safe_unicode(repo))
165 165 )
166 166 if commit:
167 167 sa.commit()
168 168 except:
169 169 log.error(traceback.format_exc())
170 170 raise
171 171
172 172
173 173 def get_repos(path, recursive=False):
174 174 """
175 175 Scans given path for repos and return (name,(type,path)) tuple
176 176
177 177 :param path: path to scan for repositories
178 178 :param recursive: recursive search and return names with subdirs in front
179 179 """
180 180
181 181 # remove ending slash for better results
182 182 path = path.rstrip(os.sep)
183 183
184 184 def _get_repos(p):
185 185 if not os.access(p, os.W_OK):
186 186 return
187 187 for dirpath in os.listdir(p):
188 188 if os.path.isfile(os.path.join(p, dirpath)):
189 189 continue
190 190 cur_path = os.path.join(p, dirpath)
191 191 try:
192 192 scm_info = get_scm(cur_path)
193 193 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
194 194 except VCSError:
195 195 if not recursive:
196 196 continue
197 197 #check if this dir containts other repos for recursive scan
198 198 rec_path = os.path.join(p, dirpath)
199 199 if os.path.isdir(rec_path):
200 200 for inner_scm in _get_repos(rec_path):
201 201 yield inner_scm
202 202
203 203 return _get_repos(path)
204 204
205 205
206 206 def is_valid_repo(repo_name, base_path):
207 207 """
208 208 Returns True if given path is a valid repository False otherwise
209 209
210 210 :param repo_name:
211 211 :param base_path:
212 212
213 213 :return True: if given path is a valid repository
214 214 """
215 215 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
216 216
217 217 try:
218 218 get_scm(full_path)
219 219 return True
220 220 except VCSError:
221 221 return False
222 222
223 223
224 224 def is_valid_repos_group(repos_group_name, base_path):
225 225 """
226 226 Returns True if given path is a repos group False otherwise
227 227
228 228 :param repo_name:
229 229 :param base_path:
230 230 """
231 231 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
232 232
233 233 # check if it's not a repo
234 234 if is_valid_repo(repos_group_name, base_path):
235 235 return False
236 236
237 237 # check if it's a valid path
238 238 if os.path.isdir(full_path):
239 239 return True
240 240
241 241 return False
242 242
243 243
244 244 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
245 245 while True:
246 246 ok = raw_input(prompt)
247 247 if ok in ('y', 'ye', 'yes'):
248 248 return True
249 249 if ok in ('n', 'no', 'nop', 'nope'):
250 250 return False
251 251 retries = retries - 1
252 252 if retries < 0:
253 253 raise IOError
254 254 print complaint
255 255
256 256 #propagated from mercurial documentation
257 257 ui_sections = ['alias', 'auth',
258 258 'decode/encode', 'defaults',
259 259 'diff', 'email',
260 260 'extensions', 'format',
261 261 'merge-patterns', 'merge-tools',
262 262 'hooks', 'http_proxy',
263 263 'smtp', 'patch',
264 264 'paths', 'profiling',
265 265 'server', 'trusted',
266 266 'ui', 'web', ]
267 267
268 268
269 269 def make_ui(read_from='file', path=None, checkpaths=True):
270 270 """
271 271 A function that will read python rc files or database
272 272 and make an mercurial ui object from read options
273 273
274 274 :param path: path to mercurial config file
275 275 :param checkpaths: check the path
276 276 :param read_from: read from 'file' or 'db'
277 277 """
278 278
279 279 baseui = ui.ui()
280 280
281 281 # clean the baseui object
282 282 baseui._ocfg = config.config()
283 283 baseui._ucfg = config.config()
284 284 baseui._tcfg = config.config()
285 285
286 286 if read_from == 'file':
287 287 if not os.path.isfile(path):
288 288 log.debug('hgrc file is not present at %s skipping...' % path)
289 289 return False
290 290 log.debug('reading hgrc from %s' % path)
291 291 cfg = config.config()
292 292 cfg.read(path)
293 293 for section in ui_sections:
294 294 for k, v in cfg.items(section):
295 295 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
296 296 baseui.setconfig(section, k, v)
297 297
298 298 elif read_from == 'db':
299 299 sa = meta.Session
300 300 ret = sa.query(RhodeCodeUi)\
301 301 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
302 302 .all()
303 303
304 304 hg_ui = ret
305 305 for ui_ in hg_ui:
306 306 if ui_.ui_active:
307 307 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
308 308 ui_.ui_key, ui_.ui_value)
309 309 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
310 310
311 311 meta.Session.remove()
312 312 return baseui
313 313
314 314
315 315 def set_rhodecode_config(config):
316 316 """
317 317 Updates pylons config with new settings from database
318 318
319 319 :param config:
320 320 """
321 321 hgsettings = RhodeCodeSetting.get_app_settings()
322 322
323 323 for k, v in hgsettings.items():
324 324 config[k] = v
325 325
326 326
327 327 def invalidate_cache(cache_key, *args):
328 328 """
329 329 Puts cache invalidation task into db for
330 330 further global cache invalidation
331 331 """
332 332
333 333 from rhodecode.model.scm import ScmModel
334 334
335 335 if cache_key.startswith('get_repo_cached_'):
336 336 name = cache_key.split('get_repo_cached_')[-1]
337 337 ScmModel().mark_for_invalidation(name)
338 338
339 339
340 340 class EmptyChangeset(BaseChangeset):
341 341 """
342 342 An dummy empty changeset. It's possible to pass hash when creating
343 343 an EmptyChangeset
344 344 """
345 345
346 346 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
347 347 alias=None):
348 348 self._empty_cs = cs
349 349 self.revision = -1
350 350 self.message = ''
351 351 self.author = ''
352 352 self.date = ''
353 353 self.repository = repo
354 354 self.requested_revision = requested_revision
355 355 self.alias = alias
356 356
357 357 @LazyProperty
358 358 def raw_id(self):
359 359 """
360 360 Returns raw string identifying this changeset, useful for web
361 361 representation.
362 362 """
363 363
364 364 return self._empty_cs
365 365
366 366 @LazyProperty
367 367 def branch(self):
368 368 return get_backend(self.alias).DEFAULT_BRANCH_NAME
369 369
370 370 @LazyProperty
371 371 def short_id(self):
372 372 return self.raw_id[:12]
373 373
374 374 def get_file_changeset(self, path):
375 375 return self
376 376
377 377 def get_file_content(self, path):
378 378 return u''
379 379
380 380 def get_file_size(self, path):
381 381 return 0
382 382
383 383
384 384 def map_groups(path):
385 385 """
386 386 Given a full path to a repository, create all nested groups that this
387 387 repo is inside. This function creates parent-child relationships between
388 388 groups and creates default perms for all new groups.
389 389
390 390 :param paths: full path to repository
391 391 """
392 392 sa = meta.Session
393 393 groups = path.split(Repository.url_sep())
394 394 parent = None
395 395 group = None
396 396
397 397 # last element is repo in nested groups structure
398 398 groups = groups[:-1]
399 399 rgm = ReposGroupModel(sa)
400 400 for lvl, group_name in enumerate(groups):
401 401 group_name = '/'.join(groups[:lvl] + [group_name])
402 402 group = RepoGroup.get_by_group_name(group_name)
403 403 desc = '%s group' % group_name
404 404
405 405 # skip folders that are now removed repos
406 406 if REMOVED_REPO_PAT.match(group_name):
407 407 break
408 408
409 409 if group is None:
410 410 log.debug('creating group level: %s group_name: %s' % (lvl,
411 411 group_name))
412 412 group = RepoGroup(group_name, parent)
413 413 group.group_description = desc
414 414 sa.add(group)
415 415 rgm._create_default_perms(group)
416 416 sa.flush()
417 417 parent = group
418 418 return group
419 419
420 420
421 421 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
422 422 """
423 423 maps all repos given in initial_repo_list, non existing repositories
424 424 are created, if remove_obsolete is True it also check for db entries
425 425 that are not in initial_repo_list and removes them.
426 426
427 427 :param initial_repo_list: list of repositories found by scanning methods
428 428 :param remove_obsolete: check for obsolete entries in database
429 429 """
430 430 from rhodecode.model.repo import RepoModel
431 431 sa = meta.Session
432 432 rm = RepoModel()
433 433 user = sa.query(User).filter(User.admin == True).first()
434 434 if user is None:
435 435 raise Exception('Missing administrative account !')
436 436 added = []
437 437
438 438 for name, repo in initial_repo_list.items():
439 439 group = map_groups(name)
440 440 if not rm.get_by_repo_name(name, cache=False):
441 441 log.info('repository %s not found creating default' % name)
442 442 added.append(name)
443 443 form_data = {
444 444 'repo_name': name,
445 445 'repo_name_full': name,
446 446 'repo_type': repo.alias,
447 447 'description': repo.description \
448 448 if repo.description != 'unknown' else '%s repository' % name,
449 449 'private': False,
450 450 'group_id': getattr(group, 'group_id', None)
451 451 }
452 452 rm.create(form_data, user, just_db=True)
453 453 sa.commit()
454 454 removed = []
455 455 if remove_obsolete:
456 456 # remove from database those repositories that are not in the filesystem
457 457 for repo in sa.query(Repository).all():
458 458 if repo.repo_name not in initial_repo_list.keys():
459 459 log.debug("Removing non existing repository found in db %s" %
460 460 repo.repo_name)
461 461 removed.append(repo.repo_name)
462 462 sa.delete(repo)
463 463 sa.commit()
464 464
465 465 # clear cache keys
466 466 log.debug("Clearing cache keys now...")
467 467 CacheInvalidation.clear_cache()
468 468 sa.commit()
469 469 return added, removed
470 470
471 471
472 472 # set cache regions for beaker so celery can utilise it
473 473 def add_cache(settings):
474 474 cache_settings = {'regions': None}
475 475 for key in settings.keys():
476 476 for prefix in ['beaker.cache.', 'cache.']:
477 477 if key.startswith(prefix):
478 478 name = key.split(prefix)[1].strip()
479 479 cache_settings[name] = settings[key].strip()
480 480 if cache_settings['regions']:
481 481 for region in cache_settings['regions'].split(','):
482 482 region = region.strip()
483 483 region_settings = {}
484 484 for key, value in cache_settings.items():
485 485 if key.startswith(region):
486 486 region_settings[key.split('.')[1]] = value
487 487 region_settings['expire'] = int(region_settings.get('expire',
488 488 60))
489 489 region_settings.setdefault('lock_dir',
490 490 cache_settings.get('lock_dir'))
491 491 region_settings.setdefault('data_dir',
492 492 cache_settings.get('data_dir'))
493 493
494 494 if 'type' not in region_settings:
495 495 region_settings['type'] = cache_settings.get('type',
496 496 'memory')
497 497 beaker.cache.cache_regions[region] = region_settings
498 498
499 499
500 500 def load_rcextensions(root_path):
501 501 import rhodecode
502 502 from rhodecode.config import conf
503 503
504 504 path = os.path.join(root_path, 'rcextensions', '__init__.py')
505 505 if os.path.isfile(path):
506 506 rcext = create_module('rc', path)
507 507 EXT = rhodecode.EXTENSIONS = rcext
508 508 log.debug('Found rcextensions now loading %s...' % rcext)
509 509
510 510 # Additional mappings that are not present in the pygments lexers
511 511 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
512 512
513 513 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
514 514
515 515 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
516 516 log.debug('settings custom INDEX_EXTENSIONS')
517 517 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
518 518
519 519 #ADDITIONAL MAPPINGS
520 520 log.debug('adding extra into INDEX_EXTENSIONS')
521 521 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
522 522
523 523
524 524 #==============================================================================
525 525 # TEST FUNCTIONS AND CREATORS
526 526 #==============================================================================
527 527 def create_test_index(repo_location, config, full_index):
528 528 """
529 529 Makes default test index
530 530
531 531 :param config: test config
532 532 :param full_index:
533 533 """
534 534
535 535 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
536 536 from rhodecode.lib.pidlock import DaemonLock, LockHeld
537 537
538 538 repo_location = repo_location
539 539
540 540 index_location = os.path.join(config['app_conf']['index_dir'])
541 541 if not os.path.exists(index_location):
542 542 os.makedirs(index_location)
543 543
544 544 try:
545 545 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
546 546 WhooshIndexingDaemon(index_location=index_location,
547 547 repo_location=repo_location)\
548 548 .run(full_index=full_index)
549 549 l.release()
550 550 except LockHeld:
551 551 pass
552 552
553 553
554 554 def create_test_env(repos_test_path, config):
555 555 """
556 556 Makes a fresh database and
557 557 install test repository into tmp dir
558 558 """
559 559 from rhodecode.lib.db_manage import DbManage
560 560 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
561 561
562 562 # PART ONE create db
563 563 dbconf = config['sqlalchemy.db1.url']
564 564 log.debug('making test db %s' % dbconf)
565 565
566 566 # create test dir if it doesn't exist
567 567 if not os.path.isdir(repos_test_path):
568 568 log.debug('Creating testdir %s' % repos_test_path)
569 569 os.makedirs(repos_test_path)
570 570
571 571 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
572 572 tests=True)
573 573 dbmanage.create_tables(override=True)
574 574 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
575 575 dbmanage.create_default_user()
576 576 dbmanage.admin_prompt()
577 577 dbmanage.create_permissions()
578 578 dbmanage.populate_default_permissions()
579 579 Session.commit()
580 580 # PART TWO make test repo
581 581 log.debug('making test vcs repositories')
582 582
583 583 idx_path = config['app_conf']['index_dir']
584 584 data_path = config['app_conf']['cache_dir']
585 585
586 586 #clean index and data
587 587 if idx_path and os.path.exists(idx_path):
588 588 log.debug('remove %s' % idx_path)
589 589 shutil.rmtree(idx_path)
590 590
591 591 if data_path and os.path.exists(data_path):
592 592 log.debug('remove %s' % data_path)
593 593 shutil.rmtree(data_path)
594 594
595 595 #CREATE DEFAULT HG REPOSITORY
596 596 cur_dir = dn(dn(abspath(__file__)))
597 597 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
598 598 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
599 599 tar.close()
600 600
601 601
602 602 #==============================================================================
603 603 # PASTER COMMANDS
604 604 #==============================================================================
605 605 class BasePasterCommand(Command):
606 606 """
607 607 Abstract Base Class for paster commands.
608 608
609 609 The celery commands are somewhat aggressive about loading
610 610 celery.conf, and since our module sets the `CELERY_LOADER`
611 611 environment variable to our loader, we have to bootstrap a bit and
612 612 make sure we've had a chance to load the pylons config off of the
613 613 command line, otherwise everything fails.
614 614 """
615 615 min_args = 1
616 616 min_args_error = "Please provide a paster config file as an argument."
617 617 takes_config_file = 1
618 618 requires_config_file = True
619 619
620 620 def notify_msg(self, msg, log=False):
621 621 """Make a notification to user, additionally if logger is passed
622 622 it logs this action using given logger
623 623
624 624 :param msg: message that will be printed to user
625 625 :param log: logging instance, to use to additionally log this message
626 626
627 627 """
628 628 if log and isinstance(log, logging):
629 629 log(msg)
630 630
631 631 def run(self, args):
632 632 """
633 633 Overrides Command.run
634 634
635 635 Checks for a config file argument and loads it.
636 636 """
637 637 if len(args) < self.min_args:
638 638 raise BadCommand(
639 639 self.min_args_error % {'min_args': self.min_args,
640 640 'actual_args': len(args)})
641 641
642 642 # Decrement because we're going to lob off the first argument.
643 643 # @@ This is hacky
644 644 self.min_args -= 1
645 645 self.bootstrap_config(args[0])
646 646 self.update_parser()
647 647 return super(BasePasterCommand, self).run(args[1:])
648 648
649 649 def update_parser(self):
650 650 """
651 651 Abstract method. Allows for the class's parser to be updated
652 652 before the superclass's `run` method is called. Necessary to
653 653 allow options/arguments to be passed through to the underlying
654 654 celery command.
655 655 """
656 656 raise NotImplementedError("Abstract Method.")
657 657
658 658 def bootstrap_config(self, conf):
659 659 """
660 660 Loads the pylons configuration.
661 661 """
662 662 from pylons import config as pylonsconfig
663 663
664 664 self.path_to_ini_file = os.path.realpath(conf)
665 665 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
666 666 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
General Comments 0
You need to be logged in to leave comments. Login now