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