##// END OF EJS Templates
invalidation: some documentation and refactoring
Mads Kiilerich -
r3606:c8ecfe42 beta
parent child Browse files
Show More
@@ -1,800 +1,794 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 import decorator
36 36 import warnings
37 37 from os.path import abspath
38 38 from os.path import dirname as dn, join as jn
39 39
40 40 from paste.script.command import Command, BadCommand
41 41
42 42 from mercurial import ui, config
43 43
44 44 from webhelpers.text import collapse, remove_formatting, strip_tags
45 45
46 46 from rhodecode.lib.vcs import get_backend
47 47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
49 49 from rhodecode.lib.vcs.utils.helpers import get_scm
50 50 from rhodecode.lib.vcs.exceptions import VCSError
51 51
52 52 from rhodecode.lib.caching_query import FromCache
53 53
54 54 from rhodecode.model import meta
55 55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
56 56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
57 57 from rhodecode.model.meta import Session
58 58 from rhodecode.model.repos_group import ReposGroupModel
59 59 from rhodecode.lib.utils2 import safe_str, safe_unicode
60 60 from rhodecode.lib.vcs.utils.fakemod import create_module
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
65 65
66 66
67 67 def recursive_replace(str_, replace=' '):
68 68 """
69 69 Recursive replace of given sign to just one instance
70 70
71 71 :param str_: given string
72 72 :param replace: char to find and replace multiple instances
73 73
74 74 Examples::
75 75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
76 76 'Mighty-Mighty-Bo-sstones'
77 77 """
78 78
79 79 if str_.find(replace * 2) == -1:
80 80 return str_
81 81 else:
82 82 str_ = str_.replace(replace * 2, replace)
83 83 return recursive_replace(str_, replace)
84 84
85 85
86 86 def repo_name_slug(value):
87 87 """
88 88 Return slug of name of repository
89 89 This function is called on each creation/modification
90 90 of repository to prevent bad names in repo
91 91 """
92 92
93 93 slug = remove_formatting(value)
94 94 slug = strip_tags(slug)
95 95
96 96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
97 97 slug = slug.replace(c, '-')
98 98 slug = recursive_replace(slug, '-')
99 99 slug = collapse(slug, '-')
100 100 return slug
101 101
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_repos_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 action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
118 118 """
119 119 Action logger for various actions made by users
120 120
121 121 :param user: user that made this action, can be a unique username string or
122 122 object containing user_id attribute
123 123 :param action: action to log, should be on of predefined unique actions for
124 124 easy translations
125 125 :param repo: string name of repository or object containing repo_id,
126 126 that action was made on
127 127 :param ipaddr: optional ip address from what the action was made
128 128 :param sa: optional sqlalchemy session
129 129
130 130 """
131 131
132 132 if not sa:
133 133 sa = meta.Session()
134 134
135 135 try:
136 136 if hasattr(user, 'user_id'):
137 137 user_obj = User.get(user.user_id)
138 138 elif isinstance(user, basestring):
139 139 user_obj = User.get_by_username(user)
140 140 else:
141 141 raise Exception('You have to provide a user object or a username')
142 142
143 143 if hasattr(repo, 'repo_id'):
144 144 repo_obj = Repository.get(repo.repo_id)
145 145 repo_name = repo_obj.repo_name
146 146 elif isinstance(repo, basestring):
147 147 repo_name = repo.lstrip('/')
148 148 repo_obj = Repository.get_by_repo_name(repo_name)
149 149 else:
150 150 repo_obj = None
151 151 repo_name = ''
152 152
153 153 user_log = UserLog()
154 154 user_log.user_id = user_obj.user_id
155 155 user_log.username = user_obj.username
156 156 user_log.action = safe_unicode(action)
157 157
158 158 user_log.repository = repo_obj
159 159 user_log.repository_name = repo_name
160 160
161 161 user_log.action_date = datetime.datetime.now()
162 162 user_log.user_ip = ipaddr
163 163 sa.add(user_log)
164 164
165 165 log.info('Logging action:%s on %s by user:%s ip:%s' %
166 166 (action, safe_unicode(repo), user_obj, ipaddr))
167 167 if commit:
168 168 sa.commit()
169 169 except:
170 170 log.error(traceback.format_exc())
171 171 raise
172 172
173 173
174 174 def get_repos(path, recursive=False, skip_removed_repos=True):
175 175 """
176 176 Scans given path for repos and return (name,(type,path)) tuple
177 177
178 178 :param path: path to scan for repositories
179 179 :param recursive: recursive search and return names with subdirs in front
180 180 """
181 181
182 182 # remove ending slash for better results
183 183 path = path.rstrip(os.sep)
184 184 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
185 185
186 186 def _get_repos(p):
187 187 if not os.access(p, os.W_OK):
188 188 return
189 189 for dirpath in os.listdir(p):
190 190 if os.path.isfile(os.path.join(p, dirpath)):
191 191 continue
192 192 cur_path = os.path.join(p, dirpath)
193 193
194 194 # skip removed repos
195 195 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
196 196 continue
197 197
198 198 #skip .<somethin> dirs
199 199 if dirpath.startswith('.'):
200 200 continue
201 201
202 202 try:
203 203 scm_info = get_scm(cur_path)
204 204 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
205 205 except VCSError:
206 206 if not recursive:
207 207 continue
208 208 #check if this dir containts other repos for recursive scan
209 209 rec_path = os.path.join(p, dirpath)
210 210 if os.path.isdir(rec_path):
211 211 for inner_scm in _get_repos(rec_path):
212 212 yield inner_scm
213 213
214 214 return _get_repos(path)
215 215
216 216 #alias for backward compat
217 217 get_filesystem_repos = get_repos
218 218
219 219
220 220 def is_valid_repo(repo_name, base_path, scm=None):
221 221 """
222 222 Returns True if given path is a valid repository False otherwise.
223 223 If scm param is given also compare if given scm is the same as expected
224 224 from scm parameter
225 225
226 226 :param repo_name:
227 227 :param base_path:
228 228 :param scm:
229 229
230 230 :return True: if given path is a valid repository
231 231 """
232 232 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
233 233
234 234 try:
235 235 scm_ = get_scm(full_path)
236 236 if scm:
237 237 return scm_[0] == scm
238 238 return True
239 239 except VCSError:
240 240 return False
241 241
242 242
243 243 def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
244 244 """
245 245 Returns True if given path is a repos group False otherwise
246 246
247 247 :param repo_name:
248 248 :param base_path:
249 249 """
250 250 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
251 251
252 252 # check if it's not a repo
253 253 if is_valid_repo(repos_group_name, base_path):
254 254 return False
255 255
256 256 try:
257 257 # we need to check bare git repos at higher level
258 258 # since we might match branches/hooks/info/objects or possible
259 259 # other things inside bare git repo
260 260 get_scm(os.path.dirname(full_path))
261 261 return False
262 262 except VCSError:
263 263 pass
264 264
265 265 # check if it's a valid path
266 266 if skip_path_check or os.path.isdir(full_path):
267 267 return True
268 268
269 269 return False
270 270
271 271
272 272 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
273 273 while True:
274 274 ok = raw_input(prompt)
275 275 if ok in ('y', 'ye', 'yes'):
276 276 return True
277 277 if ok in ('n', 'no', 'nop', 'nope'):
278 278 return False
279 279 retries = retries - 1
280 280 if retries < 0:
281 281 raise IOError
282 282 print complaint
283 283
284 284 #propagated from mercurial documentation
285 285 ui_sections = ['alias', 'auth',
286 286 'decode/encode', 'defaults',
287 287 'diff', 'email',
288 288 'extensions', 'format',
289 289 'merge-patterns', 'merge-tools',
290 290 'hooks', 'http_proxy',
291 291 'smtp', 'patch',
292 292 'paths', 'profiling',
293 293 'server', 'trusted',
294 294 'ui', 'web', ]
295 295
296 296
297 297 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
298 298 """
299 299 A function that will read python rc files or database
300 300 and make an mercurial ui object from read options
301 301
302 302 :param path: path to mercurial config file
303 303 :param checkpaths: check the path
304 304 :param read_from: read from 'file' or 'db'
305 305 """
306 306
307 307 baseui = ui.ui()
308 308
309 309 # clean the baseui object
310 310 baseui._ocfg = config.config()
311 311 baseui._ucfg = config.config()
312 312 baseui._tcfg = config.config()
313 313
314 314 if read_from == 'file':
315 315 if not os.path.isfile(path):
316 316 log.debug('hgrc file is not present at %s, skipping...' % path)
317 317 return False
318 318 log.debug('reading hgrc from %s' % path)
319 319 cfg = config.config()
320 320 cfg.read(path)
321 321 for section in ui_sections:
322 322 for k, v in cfg.items(section):
323 323 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
324 324 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
325 325
326 326 elif read_from == 'db':
327 327 sa = meta.Session()
328 328 ret = sa.query(RhodeCodeUi)\
329 329 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
330 330 .all()
331 331
332 332 hg_ui = ret
333 333 for ui_ in hg_ui:
334 334 if ui_.ui_active:
335 335 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
336 336 ui_.ui_key, ui_.ui_value)
337 337 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
338 338 safe_str(ui_.ui_value))
339 339 if ui_.ui_key == 'push_ssl':
340 340 # force set push_ssl requirement to False, rhodecode
341 341 # handles that
342 342 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
343 343 False)
344 344 if clear_session:
345 345 meta.Session.remove()
346 346 return baseui
347 347
348 348
349 349 def set_rhodecode_config(config):
350 350 """
351 351 Updates pylons config with new settings from database
352 352
353 353 :param config:
354 354 """
355 355 hgsettings = RhodeCodeSetting.get_app_settings()
356 356
357 357 for k, v in hgsettings.items():
358 358 config[k] = v
359 359
360 360
361 361 def invalidate_cache(cache_key, *args):
362 362 """
363 363 Puts cache invalidation task into db for
364 364 further global cache invalidation
365 365 """
366 366
367 367 from rhodecode.model.scm import ScmModel
368 368
369 369 if cache_key.startswith('get_repo_cached_'):
370 370 name = cache_key.split('get_repo_cached_')[-1]
371 371 ScmModel().mark_for_invalidation(name)
372 372
373 373
374 374 def map_groups(path):
375 375 """
376 376 Given a full path to a repository, create all nested groups that this
377 377 repo is inside. This function creates parent-child relationships between
378 378 groups and creates default perms for all new groups.
379 379
380 380 :param paths: full path to repository
381 381 """
382 382 sa = meta.Session()
383 383 groups = path.split(Repository.url_sep())
384 384 parent = None
385 385 group = None
386 386
387 387 # last element is repo in nested groups structure
388 388 groups = groups[:-1]
389 389 rgm = ReposGroupModel(sa)
390 390 for lvl, group_name in enumerate(groups):
391 391 group_name = '/'.join(groups[:lvl] + [group_name])
392 392 group = RepoGroup.get_by_group_name(group_name)
393 393 desc = '%s group' % group_name
394 394
395 395 # skip folders that are now removed repos
396 396 if REMOVED_REPO_PAT.match(group_name):
397 397 break
398 398
399 399 if group is None:
400 400 log.debug('creating group level: %s group_name: %s' % (lvl,
401 401 group_name))
402 402 group = RepoGroup(group_name, parent)
403 403 group.group_description = desc
404 404 sa.add(group)
405 405 rgm._create_default_perms(group)
406 406 sa.flush()
407 407 parent = group
408 408 return group
409 409
410 410
411 411 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
412 412 install_git_hook=False):
413 413 """
414 414 maps all repos given in initial_repo_list, non existing repositories
415 415 are created, if remove_obsolete is True it also check for db entries
416 416 that are not in initial_repo_list and removes them.
417 417
418 418 :param initial_repo_list: list of repositories found by scanning methods
419 419 :param remove_obsolete: check for obsolete entries in database
420 420 :param install_git_hook: if this is True, also check and install githook
421 421 for a repo if missing
422 422 """
423 423 from rhodecode.model.repo import RepoModel
424 424 from rhodecode.model.scm import ScmModel
425 425 sa = meta.Session()
426 426 rm = RepoModel()
427 427 user = sa.query(User).filter(User.admin == True).first()
428 428 if user is None:
429 429 raise Exception('Missing administrative account!')
430 430 added = []
431 431
432 # # clear cache keys
433 # log.debug("Clearing cache keys now...")
434 # CacheInvalidation.clear_cache()
435 # sa.commit()
436
437 432 ##creation defaults
438 433 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
439 434 enable_statistics = defs.get('repo_enable_statistics')
440 435 enable_locking = defs.get('repo_enable_locking')
441 436 enable_downloads = defs.get('repo_enable_downloads')
442 437 private = defs.get('repo_private')
443 438
444 439 for name, repo in initial_repo_list.items():
445 440 group = map_groups(name)
446 441 db_repo = rm.get_by_repo_name(name)
447 442 # found repo that is on filesystem not in RhodeCode database
448 443 if not db_repo:
449 444 log.info('repository %s not found, creating now' % name)
450 445 added.append(name)
451 446 desc = (repo.description
452 447 if repo.description != 'unknown'
453 448 else '%s repository' % name)
454 449
455 450 new_repo = rm.create_repo(
456 451 repo_name=name,
457 452 repo_type=repo.alias,
458 453 description=desc,
459 454 repos_group=getattr(group, 'group_id', None),
460 455 owner=user,
461 456 just_db=True,
462 457 enable_locking=enable_locking,
463 458 enable_downloads=enable_downloads,
464 459 enable_statistics=enable_statistics,
465 460 private=private
466 461 )
467 462 # we added that repo just now, and make sure it has githook
468 463 # installed
469 464 if new_repo.repo_type == 'git':
470 465 ScmModel().install_git_hook(new_repo.scm_instance)
471 466 new_repo.update_changeset_cache()
472 467 elif install_git_hook:
473 468 if db_repo.repo_type == 'git':
474 469 ScmModel().install_git_hook(db_repo.scm_instance)
475 470 # during starting install all cache keys for all repositories in the
476 471 # system, this will register all repos and multiple instances
477 key, _prefix, _org_key = CacheInvalidation._get_key(name)
472 cache_key = CacheInvalidation._get_cache_key(name)
473 log.debug("Creating invalidation cache key for %s: %s", name, cache_key)
478 474 CacheInvalidation.invalidate(name)
479 log.debug("Creating a cache key for %s, instance_id %s"
480 % (name, _prefix or 'unknown'))
481 475
482 476 sa.commit()
483 477 removed = []
484 478 if remove_obsolete:
485 479 # remove from database those repositories that are not in the filesystem
486 480 for repo in sa.query(Repository).all():
487 481 if repo.repo_name not in initial_repo_list.keys():
488 482 log.debug("Removing non-existing repository found in db `%s`" %
489 483 repo.repo_name)
490 484 try:
491 485 sa.delete(repo)
492 486 sa.commit()
493 487 removed.append(repo.repo_name)
494 488 except:
495 489 #don't hold further removals on error
496 490 log.error(traceback.format_exc())
497 491 sa.rollback()
498 492 return added, removed
499 493
500 494
501 495 # set cache regions for beaker so celery can utilise it
502 496 def add_cache(settings):
503 497 cache_settings = {'regions': None}
504 498 for key in settings.keys():
505 499 for prefix in ['beaker.cache.', 'cache.']:
506 500 if key.startswith(prefix):
507 501 name = key.split(prefix)[1].strip()
508 502 cache_settings[name] = settings[key].strip()
509 503 if cache_settings['regions']:
510 504 for region in cache_settings['regions'].split(','):
511 505 region = region.strip()
512 506 region_settings = {}
513 507 for key, value in cache_settings.items():
514 508 if key.startswith(region):
515 509 region_settings[key.split('.')[1]] = value
516 510 region_settings['expire'] = int(region_settings.get('expire',
517 511 60))
518 512 region_settings.setdefault('lock_dir',
519 513 cache_settings.get('lock_dir'))
520 514 region_settings.setdefault('data_dir',
521 515 cache_settings.get('data_dir'))
522 516
523 517 if 'type' not in region_settings:
524 518 region_settings['type'] = cache_settings.get('type',
525 519 'memory')
526 520 beaker.cache.cache_regions[region] = region_settings
527 521
528 522
529 523 def load_rcextensions(root_path):
530 524 import rhodecode
531 525 from rhodecode.config import conf
532 526
533 527 path = os.path.join(root_path, 'rcextensions', '__init__.py')
534 528 if os.path.isfile(path):
535 529 rcext = create_module('rc', path)
536 530 EXT = rhodecode.EXTENSIONS = rcext
537 531 log.debug('Found rcextensions now loading %s...' % rcext)
538 532
539 533 # Additional mappings that are not present in the pygments lexers
540 534 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
541 535
542 536 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
543 537
544 538 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
545 539 log.debug('settings custom INDEX_EXTENSIONS')
546 540 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
547 541
548 542 #ADDITIONAL MAPPINGS
549 543 log.debug('adding extra into INDEX_EXTENSIONS')
550 544 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
551 545
552 546 # auto check if the module is not missing any data, set to default if is
553 547 # this will help autoupdate new feature of rcext module
554 548 from rhodecode.config import rcextensions
555 549 for k in dir(rcextensions):
556 550 if not k.startswith('_') and not hasattr(EXT, k):
557 551 setattr(EXT, k, getattr(rcextensions, k))
558 552
559 553
560 554 def get_custom_lexer(extension):
561 555 """
562 556 returns a custom lexer if it's defined in rcextensions module, or None
563 557 if there's no custom lexer defined
564 558 """
565 559 import rhodecode
566 560 from pygments import lexers
567 561 #check if we didn't define this extension as other lexer
568 562 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
569 563 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
570 564 return lexers.get_lexer_by_name(_lexer_name)
571 565
572 566
573 567 #==============================================================================
574 568 # TEST FUNCTIONS AND CREATORS
575 569 #==============================================================================
576 570 def create_test_index(repo_location, config, full_index):
577 571 """
578 572 Makes default test index
579 573
580 574 :param config: test config
581 575 :param full_index:
582 576 """
583 577
584 578 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
585 579 from rhodecode.lib.pidlock import DaemonLock, LockHeld
586 580
587 581 repo_location = repo_location
588 582
589 583 index_location = os.path.join(config['app_conf']['index_dir'])
590 584 if not os.path.exists(index_location):
591 585 os.makedirs(index_location)
592 586
593 587 try:
594 588 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
595 589 WhooshIndexingDaemon(index_location=index_location,
596 590 repo_location=repo_location)\
597 591 .run(full_index=full_index)
598 592 l.release()
599 593 except LockHeld:
600 594 pass
601 595
602 596
603 597 def create_test_env(repos_test_path, config):
604 598 """
605 599 Makes a fresh database and
606 600 install test repository into tmp dir
607 601 """
608 602 from rhodecode.lib.db_manage import DbManage
609 603 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
610 604
611 605 # PART ONE create db
612 606 dbconf = config['sqlalchemy.db1.url']
613 607 log.debug('making test db %s' % dbconf)
614 608
615 609 # create test dir if it doesn't exist
616 610 if not os.path.isdir(repos_test_path):
617 611 log.debug('Creating testdir %s' % repos_test_path)
618 612 os.makedirs(repos_test_path)
619 613
620 614 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
621 615 tests=True)
622 616 dbmanage.create_tables(override=True)
623 617 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
624 618 dbmanage.create_default_user()
625 619 dbmanage.admin_prompt()
626 620 dbmanage.create_permissions()
627 621 dbmanage.populate_default_permissions()
628 622 Session().commit()
629 623 # PART TWO make test repo
630 624 log.debug('making test vcs repositories')
631 625
632 626 idx_path = config['app_conf']['index_dir']
633 627 data_path = config['app_conf']['cache_dir']
634 628
635 629 #clean index and data
636 630 if idx_path and os.path.exists(idx_path):
637 631 log.debug('remove %s' % idx_path)
638 632 shutil.rmtree(idx_path)
639 633
640 634 if data_path and os.path.exists(data_path):
641 635 log.debug('remove %s' % data_path)
642 636 shutil.rmtree(data_path)
643 637
644 638 #CREATE DEFAULT TEST REPOS
645 639 cur_dir = dn(dn(abspath(__file__)))
646 640 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
647 641 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
648 642 tar.close()
649 643
650 644 cur_dir = dn(dn(abspath(__file__)))
651 645 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
652 646 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
653 647 tar.close()
654 648
655 649 #LOAD VCS test stuff
656 650 from rhodecode.tests.vcs import setup_package
657 651 setup_package()
658 652
659 653
660 654 #==============================================================================
661 655 # PASTER COMMANDS
662 656 #==============================================================================
663 657 class BasePasterCommand(Command):
664 658 """
665 659 Abstract Base Class for paster commands.
666 660
667 661 The celery commands are somewhat aggressive about loading
668 662 celery.conf, and since our module sets the `CELERY_LOADER`
669 663 environment variable to our loader, we have to bootstrap a bit and
670 664 make sure we've had a chance to load the pylons config off of the
671 665 command line, otherwise everything fails.
672 666 """
673 667 min_args = 1
674 668 min_args_error = "Please provide a paster config file as an argument."
675 669 takes_config_file = 1
676 670 requires_config_file = True
677 671
678 672 def notify_msg(self, msg, log=False):
679 673 """Make a notification to user, additionally if logger is passed
680 674 it logs this action using given logger
681 675
682 676 :param msg: message that will be printed to user
683 677 :param log: logging instance, to use to additionally log this message
684 678
685 679 """
686 680 if log and isinstance(log, logging):
687 681 log(msg)
688 682
689 683 def run(self, args):
690 684 """
691 685 Overrides Command.run
692 686
693 687 Checks for a config file argument and loads it.
694 688 """
695 689 if len(args) < self.min_args:
696 690 raise BadCommand(
697 691 self.min_args_error % {'min_args': self.min_args,
698 692 'actual_args': len(args)})
699 693
700 694 # Decrement because we're going to lob off the first argument.
701 695 # @@ This is hacky
702 696 self.min_args -= 1
703 697 self.bootstrap_config(args[0])
704 698 self.update_parser()
705 699 return super(BasePasterCommand, self).run(args[1:])
706 700
707 701 def update_parser(self):
708 702 """
709 703 Abstract method. Allows for the class's parser to be updated
710 704 before the superclass's `run` method is called. Necessary to
711 705 allow options/arguments to be passed through to the underlying
712 706 celery command.
713 707 """
714 708 raise NotImplementedError("Abstract Method.")
715 709
716 710 def bootstrap_config(self, conf):
717 711 """
718 712 Loads the pylons configuration.
719 713 """
720 714 from pylons import config as pylonsconfig
721 715
722 716 self.path_to_ini_file = os.path.realpath(conf)
723 717 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
724 718 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
725 719
726 720 def _init_session(self):
727 721 """
728 722 Inits SqlAlchemy Session
729 723 """
730 724 logging.config.fileConfig(self.path_to_ini_file)
731 725 from pylons import config
732 726 from rhodecode.model import init_model
733 727 from rhodecode.lib.utils2 import engine_from_config
734 728
735 729 #get to remove repos !!
736 730 add_cache(config)
737 731 engine = engine_from_config(config, 'sqlalchemy.db1.')
738 732 init_model(engine)
739 733
740 734
741 735 def check_git_version():
742 736 """
743 737 Checks what version of git is installed in system, and issues a warning
744 738 if it's too old for RhodeCode to properly work.
745 739 """
746 740 from rhodecode import BACKENDS
747 741 from rhodecode.lib.vcs.backends.git.repository import GitRepository
748 742 from distutils.version import StrictVersion
749 743
750 744 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
751 745 _safe=True)
752 746
753 747 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
754 748 if len(ver.split('.')) > 3:
755 749 #StrictVersion needs to be only 3 element type
756 750 ver = '.'.join(ver.split('.')[:3])
757 751 try:
758 752 _ver = StrictVersion(ver)
759 753 except:
760 754 _ver = StrictVersion('0.0.0')
761 755 stderr = traceback.format_exc()
762 756
763 757 req_ver = '1.7.4'
764 758 to_old_git = False
765 759 if _ver < StrictVersion(req_ver):
766 760 to_old_git = True
767 761
768 762 if 'git' in BACKENDS:
769 763 log.debug('GIT version detected: %s' % stdout)
770 764 if stderr:
771 765 log.warning('Unable to detect git version, org error was: %r' % stderr)
772 766 elif to_old_git:
773 767 log.warning('RhodeCode detected git version %s, which is too old '
774 768 'for the system to function properly. Make sure '
775 769 'its version is at least %s' % (ver, req_ver))
776 770 return _ver
777 771
778 772
779 773 @decorator.decorator
780 774 def jsonify(func, *args, **kwargs):
781 775 """Action decorator that formats output for JSON
782 776
783 777 Given a function that will return content, this decorator will turn
784 778 the result into JSON, with a content-type of 'application/json' and
785 779 output it.
786 780
787 781 """
788 782 from pylons.decorators.util import get_pylons
789 783 from rhodecode.lib.ext_json import json
790 784 pylons = get_pylons(args)
791 785 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
792 786 data = func(*args, **kwargs)
793 787 if isinstance(data, (list, tuple)):
794 788 msg = "JSON responses with Array envelopes are susceptible to " \
795 789 "cross-site data leak attacks, see " \
796 790 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
797 791 warnings.warn(msg, Warning, 2)
798 792 log.warning(msg)
799 793 log.debug("Returning JSON wrapped action output")
800 794 return json.dumps(data, encoding='utf-8')
@@ -1,2052 +1,2039 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 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 hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 48
49 49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
51 51 from rhodecode.lib.compat import json
52 52 from rhodecode.lib.caching_query import FromCache
53 53
54 54 from rhodecode.model.meta import Base, Session
55 55
56 56 URL_SEP = '/'
57 57 log = logging.getLogger(__name__)
58 58
59 59 #==============================================================================
60 60 # BASE CLASSES
61 61 #==============================================================================
62 62
63 63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64 64
65 65
66 66 class BaseModel(object):
67 67 """
68 68 Base Model for all classess
69 69 """
70 70
71 71 @classmethod
72 72 def _get_keys(cls):
73 73 """return column names for this model """
74 74 return class_mapper(cls).c.keys()
75 75
76 76 def get_dict(self):
77 77 """
78 78 return dict with keys and values corresponding
79 79 to this model data """
80 80
81 81 d = {}
82 82 for k in self._get_keys():
83 83 d[k] = getattr(self, k)
84 84
85 85 # also use __json__() if present to get additional fields
86 86 _json_attr = getattr(self, '__json__', None)
87 87 if _json_attr:
88 88 # update with attributes from __json__
89 89 if callable(_json_attr):
90 90 _json_attr = _json_attr()
91 91 for k, val in _json_attr.iteritems():
92 92 d[k] = val
93 93 return d
94 94
95 95 def get_appstruct(self):
96 96 """return list with keys and values tupples corresponding
97 97 to this model data """
98 98
99 99 l = []
100 100 for k in self._get_keys():
101 101 l.append((k, getattr(self, k),))
102 102 return l
103 103
104 104 def populate_obj(self, populate_dict):
105 105 """populate model with data from given populate_dict"""
106 106
107 107 for k in self._get_keys():
108 108 if k in populate_dict:
109 109 setattr(self, k, populate_dict[k])
110 110
111 111 @classmethod
112 112 def query(cls):
113 113 return Session().query(cls)
114 114
115 115 @classmethod
116 116 def get(cls, id_):
117 117 if id_:
118 118 return cls.query().get(id_)
119 119
120 120 @classmethod
121 121 def get_or_404(cls, id_):
122 122 try:
123 123 id_ = int(id_)
124 124 except (TypeError, ValueError):
125 125 raise HTTPNotFound
126 126
127 127 res = cls.query().get(id_)
128 128 if not res:
129 129 raise HTTPNotFound
130 130 return res
131 131
132 132 @classmethod
133 133 def getAll(cls):
134 134 return cls.query().all()
135 135
136 136 @classmethod
137 137 def delete(cls, id_):
138 138 obj = cls.query().get(id_)
139 139 Session().delete(obj)
140 140
141 141 def __repr__(self):
142 142 if hasattr(self, '__unicode__'):
143 143 # python repr needs to return str
144 144 return safe_str(self.__unicode__())
145 145 return '<DB:%s>' % (self.__class__.__name__)
146 146
147 147
148 148 class RhodeCodeSetting(Base, BaseModel):
149 149 __tablename__ = 'rhodecode_settings'
150 150 __table_args__ = (
151 151 UniqueConstraint('app_settings_name'),
152 152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 153 'mysql_charset': 'utf8'}
154 154 )
155 155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 156 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 157 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
158 158
159 159 def __init__(self, k='', v=''):
160 160 self.app_settings_name = k
161 161 self.app_settings_value = v
162 162
163 163 @validates('_app_settings_value')
164 164 def validate_settings_value(self, key, val):
165 165 assert type(val) == unicode
166 166 return val
167 167
168 168 @hybrid_property
169 169 def app_settings_value(self):
170 170 v = self._app_settings_value
171 171 if self.app_settings_name in ["ldap_active",
172 172 "default_repo_enable_statistics",
173 173 "default_repo_enable_locking",
174 174 "default_repo_private",
175 175 "default_repo_enable_downloads"]:
176 176 v = str2bool(v)
177 177 return v
178 178
179 179 @app_settings_value.setter
180 180 def app_settings_value(self, val):
181 181 """
182 182 Setter that will always make sure we use unicode in app_settings_value
183 183
184 184 :param val:
185 185 """
186 186 self._app_settings_value = safe_unicode(val)
187 187
188 188 def __unicode__(self):
189 189 return u"<%s('%s:%s')>" % (
190 190 self.__class__.__name__,
191 191 self.app_settings_name, self.app_settings_value
192 192 )
193 193
194 194 @classmethod
195 195 def get_by_name(cls, key):
196 196 return cls.query()\
197 197 .filter(cls.app_settings_name == key).scalar()
198 198
199 199 @classmethod
200 200 def get_by_name_or_create(cls, key):
201 201 res = cls.get_by_name(key)
202 202 if not res:
203 203 res = cls(key)
204 204 return res
205 205
206 206 @classmethod
207 207 def get_app_settings(cls, cache=False):
208 208
209 209 ret = cls.query()
210 210
211 211 if cache:
212 212 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
213 213
214 214 if not ret:
215 215 raise Exception('Could not get application settings !')
216 216 settings = {}
217 217 for each in ret:
218 218 settings['rhodecode_' + each.app_settings_name] = \
219 219 each.app_settings_value
220 220
221 221 return settings
222 222
223 223 @classmethod
224 224 def get_ldap_settings(cls, cache=False):
225 225 ret = cls.query()\
226 226 .filter(cls.app_settings_name.startswith('ldap_')).all()
227 227 fd = {}
228 228 for row in ret:
229 229 fd.update({row.app_settings_name: row.app_settings_value})
230 230
231 231 return fd
232 232
233 233 @classmethod
234 234 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
235 235 ret = cls.query()\
236 236 .filter(cls.app_settings_name.startswith('default_')).all()
237 237 fd = {}
238 238 for row in ret:
239 239 key = row.app_settings_name
240 240 if strip_prefix:
241 241 key = remove_prefix(key, prefix='default_')
242 242 fd.update({key: row.app_settings_value})
243 243
244 244 return fd
245 245
246 246
247 247 class RhodeCodeUi(Base, BaseModel):
248 248 __tablename__ = 'rhodecode_ui'
249 249 __table_args__ = (
250 250 UniqueConstraint('ui_key'),
251 251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
252 252 'mysql_charset': 'utf8'}
253 253 )
254 254
255 255 HOOK_UPDATE = 'changegroup.update'
256 256 HOOK_REPO_SIZE = 'changegroup.repo_size'
257 257 HOOK_PUSH = 'changegroup.push_logger'
258 258 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
259 259 HOOK_PULL = 'outgoing.pull_logger'
260 260 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
261 261
262 262 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
263 263 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 264 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 265 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
266 266 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
267 267
268 268 @classmethod
269 269 def get_by_key(cls, key):
270 270 return cls.query().filter(cls.ui_key == key).scalar()
271 271
272 272 @classmethod
273 273 def get_builtin_hooks(cls):
274 274 q = cls.query()
275 275 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
276 276 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
277 277 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
278 278 return q.all()
279 279
280 280 @classmethod
281 281 def get_custom_hooks(cls):
282 282 q = cls.query()
283 283 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
284 284 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
285 285 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
286 286 q = q.filter(cls.ui_section == 'hooks')
287 287 return q.all()
288 288
289 289 @classmethod
290 290 def get_repos_location(cls):
291 291 return cls.get_by_key('/').ui_value
292 292
293 293 @classmethod
294 294 def create_or_update_hook(cls, key, val):
295 295 new_ui = cls.get_by_key(key) or cls()
296 296 new_ui.ui_section = 'hooks'
297 297 new_ui.ui_active = True
298 298 new_ui.ui_key = key
299 299 new_ui.ui_value = val
300 300
301 301 Session().add(new_ui)
302 302
303 303 def __repr__(self):
304 304 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
305 305 self.ui_value)
306 306
307 307
308 308 class User(Base, BaseModel):
309 309 __tablename__ = 'users'
310 310 __table_args__ = (
311 311 UniqueConstraint('username'), UniqueConstraint('email'),
312 312 Index('u_username_idx', 'username'),
313 313 Index('u_email_idx', 'email'),
314 314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
315 315 'mysql_charset': 'utf8'}
316 316 )
317 317 DEFAULT_USER = 'default'
318 318 DEFAULT_PERMISSIONS = [
319 319 'hg.register.manual_activate', 'hg.create.repository',
320 320 'hg.fork.repository', 'repository.read', 'group.read'
321 321 ]
322 322 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
323 323 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 324 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
325 325 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
326 326 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
327 327 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 328 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 329 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 330 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
331 331 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 332 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
333 333 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
334 334
335 335 user_log = relationship('UserLog')
336 336 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
337 337
338 338 repositories = relationship('Repository')
339 339 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
340 340 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
341 341
342 342 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
343 343 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
344 344
345 345 group_member = relationship('UserGroupMember', cascade='all')
346 346
347 347 notifications = relationship('UserNotification', cascade='all')
348 348 # notifications assigned to this user
349 349 user_created_notifications = relationship('Notification', cascade='all')
350 350 # comments created by this user
351 351 user_comments = relationship('ChangesetComment', cascade='all')
352 352 #extra emails for this user
353 353 user_emails = relationship('UserEmailMap', cascade='all')
354 354
355 355 @hybrid_property
356 356 def email(self):
357 357 return self._email
358 358
359 359 @email.setter
360 360 def email(self, val):
361 361 self._email = val.lower() if val else None
362 362
363 363 @property
364 364 def firstname(self):
365 365 # alias for future
366 366 return self.name
367 367
368 368 @property
369 369 def emails(self):
370 370 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
371 371 return [self.email] + [x.email for x in other]
372 372
373 373 @property
374 374 def ip_addresses(self):
375 375 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
376 376 return [x.ip_addr for x in ret]
377 377
378 378 @property
379 379 def username_and_name(self):
380 380 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
381 381
382 382 @property
383 383 def full_name(self):
384 384 return '%s %s' % (self.firstname, self.lastname)
385 385
386 386 @property
387 387 def full_name_or_username(self):
388 388 return ('%s %s' % (self.firstname, self.lastname)
389 389 if (self.firstname and self.lastname) else self.username)
390 390
391 391 @property
392 392 def full_contact(self):
393 393 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
394 394
395 395 @property
396 396 def short_contact(self):
397 397 return '%s %s' % (self.firstname, self.lastname)
398 398
399 399 @property
400 400 def is_admin(self):
401 401 return self.admin
402 402
403 403 @property
404 404 def AuthUser(self):
405 405 """
406 406 Returns instance of AuthUser for this user
407 407 """
408 408 from rhodecode.lib.auth import AuthUser
409 409 return AuthUser(user_id=self.user_id, api_key=self.api_key,
410 410 username=self.username)
411 411
412 412 def __unicode__(self):
413 413 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
414 414 self.user_id, self.username)
415 415
416 416 @classmethod
417 417 def get_by_username(cls, username, case_insensitive=False, cache=False):
418 418 if case_insensitive:
419 419 q = cls.query().filter(cls.username.ilike(username))
420 420 else:
421 421 q = cls.query().filter(cls.username == username)
422 422
423 423 if cache:
424 424 q = q.options(FromCache(
425 425 "sql_cache_short",
426 426 "get_user_%s" % _hash_key(username)
427 427 )
428 428 )
429 429 return q.scalar()
430 430
431 431 @classmethod
432 432 def get_by_api_key(cls, api_key, cache=False):
433 433 q = cls.query().filter(cls.api_key == api_key)
434 434
435 435 if cache:
436 436 q = q.options(FromCache("sql_cache_short",
437 437 "get_api_key_%s" % api_key))
438 438 return q.scalar()
439 439
440 440 @classmethod
441 441 def get_by_email(cls, email, case_insensitive=False, cache=False):
442 442 if case_insensitive:
443 443 q = cls.query().filter(cls.email.ilike(email))
444 444 else:
445 445 q = cls.query().filter(cls.email == email)
446 446
447 447 if cache:
448 448 q = q.options(FromCache("sql_cache_short",
449 449 "get_email_key_%s" % email))
450 450
451 451 ret = q.scalar()
452 452 if ret is None:
453 453 q = UserEmailMap.query()
454 454 # try fetching in alternate email map
455 455 if case_insensitive:
456 456 q = q.filter(UserEmailMap.email.ilike(email))
457 457 else:
458 458 q = q.filter(UserEmailMap.email == email)
459 459 q = q.options(joinedload(UserEmailMap.user))
460 460 if cache:
461 461 q = q.options(FromCache("sql_cache_short",
462 462 "get_email_map_key_%s" % email))
463 463 ret = getattr(q.scalar(), 'user', None)
464 464
465 465 return ret
466 466
467 467 @classmethod
468 468 def get_from_cs_author(cls, author):
469 469 """
470 470 Tries to get User objects out of commit author string
471 471
472 472 :param author:
473 473 """
474 474 from rhodecode.lib.helpers import email, author_name
475 475 # Valid email in the attribute passed, see if they're in the system
476 476 _email = email(author)
477 477 if _email:
478 478 user = cls.get_by_email(_email, case_insensitive=True)
479 479 if user:
480 480 return user
481 481 # Maybe we can match by username?
482 482 _author = author_name(author)
483 483 user = cls.get_by_username(_author, case_insensitive=True)
484 484 if user:
485 485 return user
486 486
487 487 def update_lastlogin(self):
488 488 """Update user lastlogin"""
489 489 self.last_login = datetime.datetime.now()
490 490 Session().add(self)
491 491 log.debug('updated user %s lastlogin' % self.username)
492 492
493 493 def get_api_data(self):
494 494 """
495 495 Common function for generating user related data for API
496 496 """
497 497 user = self
498 498 data = dict(
499 499 user_id=user.user_id,
500 500 username=user.username,
501 501 firstname=user.name,
502 502 lastname=user.lastname,
503 503 email=user.email,
504 504 emails=user.emails,
505 505 api_key=user.api_key,
506 506 active=user.active,
507 507 admin=user.admin,
508 508 ldap_dn=user.ldap_dn,
509 509 last_login=user.last_login,
510 510 ip_addresses=user.ip_addresses
511 511 )
512 512 return data
513 513
514 514 def __json__(self):
515 515 data = dict(
516 516 full_name=self.full_name,
517 517 full_name_or_username=self.full_name_or_username,
518 518 short_contact=self.short_contact,
519 519 full_contact=self.full_contact
520 520 )
521 521 data.update(self.get_api_data())
522 522 return data
523 523
524 524
525 525 class UserEmailMap(Base, BaseModel):
526 526 __tablename__ = 'user_email_map'
527 527 __table_args__ = (
528 528 Index('uem_email_idx', 'email'),
529 529 UniqueConstraint('email'),
530 530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
531 531 'mysql_charset': 'utf8'}
532 532 )
533 533 __mapper_args__ = {}
534 534
535 535 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
536 536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
537 537 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
538 538 user = relationship('User', lazy='joined')
539 539
540 540 @validates('_email')
541 541 def validate_email(self, key, email):
542 542 # check if this email is not main one
543 543 main_email = Session().query(User).filter(User.email == email).scalar()
544 544 if main_email is not None:
545 545 raise AttributeError('email %s is present is user table' % email)
546 546 return email
547 547
548 548 @hybrid_property
549 549 def email(self):
550 550 return self._email
551 551
552 552 @email.setter
553 553 def email(self, val):
554 554 self._email = val.lower() if val else None
555 555
556 556
557 557 class UserIpMap(Base, BaseModel):
558 558 __tablename__ = 'user_ip_map'
559 559 __table_args__ = (
560 560 UniqueConstraint('user_id', 'ip_addr'),
561 561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
562 562 'mysql_charset': 'utf8'}
563 563 )
564 564 __mapper_args__ = {}
565 565
566 566 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
567 567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
568 568 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
569 569 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
570 570 user = relationship('User', lazy='joined')
571 571
572 572 @classmethod
573 573 def _get_ip_range(cls, ip_addr):
574 574 from rhodecode.lib import ipaddr
575 575 net = ipaddr.IPNetwork(address=ip_addr)
576 576 return [str(net.network), str(net.broadcast)]
577 577
578 578 def __json__(self):
579 579 return dict(
580 580 ip_addr=self.ip_addr,
581 581 ip_range=self._get_ip_range(self.ip_addr)
582 582 )
583 583
584 584
585 585 class UserLog(Base, BaseModel):
586 586 __tablename__ = 'user_logs'
587 587 __table_args__ = (
588 588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
589 589 'mysql_charset': 'utf8'},
590 590 )
591 591 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
592 592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
593 593 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
594 594 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
595 595 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
596 596 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
597 597 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
598 598 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
599 599
600 600 @property
601 601 def action_as_day(self):
602 602 return datetime.date(*self.action_date.timetuple()[:3])
603 603
604 604 user = relationship('User')
605 605 repository = relationship('Repository', cascade='')
606 606
607 607
608 608 class UserGroup(Base, BaseModel):
609 609 __tablename__ = 'users_groups'
610 610 __table_args__ = (
611 611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
612 612 'mysql_charset': 'utf8'},
613 613 )
614 614
615 615 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
616 616 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
617 617 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
618 618 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
619 619
620 620 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
621 621 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
622 622 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
623 623
624 624 def __unicode__(self):
625 625 return u'<userGroup(%s)>' % (self.users_group_name)
626 626
627 627 @classmethod
628 628 def get_by_group_name(cls, group_name, cache=False,
629 629 case_insensitive=False):
630 630 if case_insensitive:
631 631 q = cls.query().filter(cls.users_group_name.ilike(group_name))
632 632 else:
633 633 q = cls.query().filter(cls.users_group_name == group_name)
634 634 if cache:
635 635 q = q.options(FromCache(
636 636 "sql_cache_short",
637 637 "get_user_%s" % _hash_key(group_name)
638 638 )
639 639 )
640 640 return q.scalar()
641 641
642 642 @classmethod
643 643 def get(cls, users_group_id, cache=False):
644 644 users_group = cls.query()
645 645 if cache:
646 646 users_group = users_group.options(FromCache("sql_cache_short",
647 647 "get_users_group_%s" % users_group_id))
648 648 return users_group.get(users_group_id)
649 649
650 650 def get_api_data(self):
651 651 users_group = self
652 652
653 653 data = dict(
654 654 users_group_id=users_group.users_group_id,
655 655 group_name=users_group.users_group_name,
656 656 active=users_group.users_group_active,
657 657 )
658 658
659 659 return data
660 660
661 661
662 662 class UserGroupMember(Base, BaseModel):
663 663 __tablename__ = 'users_groups_members'
664 664 __table_args__ = (
665 665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
666 666 'mysql_charset': 'utf8'},
667 667 )
668 668
669 669 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
670 670 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
671 671 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
672 672
673 673 user = relationship('User', lazy='joined')
674 674 users_group = relationship('UserGroup')
675 675
676 676 def __init__(self, gr_id='', u_id=''):
677 677 self.users_group_id = gr_id
678 678 self.user_id = u_id
679 679
680 680
681 681 class RepositoryField(Base, BaseModel):
682 682 __tablename__ = 'repositories_fields'
683 683 __table_args__ = (
684 684 UniqueConstraint('repository_id', 'field_key'), # no-multi field
685 685 {'extend_existing': True, 'mysql_engine': 'InnoDB',
686 686 'mysql_charset': 'utf8'},
687 687 )
688 688 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
689 689
690 690 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
691 691 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
692 692 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
693 693 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
694 694 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
695 695 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
696 696 field_type = Column("field_type", String(256), nullable=False, unique=None)
697 697 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
698 698
699 699 repository = relationship('Repository')
700 700
701 701 @property
702 702 def field_key_prefixed(self):
703 703 return 'ex_%s' % self.field_key
704 704
705 705 @classmethod
706 706 def un_prefix_key(cls, key):
707 707 if key.startswith(cls.PREFIX):
708 708 return key[len(cls.PREFIX):]
709 709 return key
710 710
711 711 @classmethod
712 712 def get_by_key_name(cls, key, repo):
713 713 row = cls.query()\
714 714 .filter(cls.repository == repo)\
715 715 .filter(cls.field_key == key).scalar()
716 716 return row
717 717
718 718
719 719 class Repository(Base, BaseModel):
720 720 __tablename__ = 'repositories'
721 721 __table_args__ = (
722 722 UniqueConstraint('repo_name'),
723 723 Index('r_repo_name_idx', 'repo_name'),
724 724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
725 725 'mysql_charset': 'utf8'},
726 726 )
727 727
728 728 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
729 729 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
730 730 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
731 731 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
732 732 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
733 733 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
734 734 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
735 735 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
736 736 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
737 737 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
738 738 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
739 739 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
740 740 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
741 741 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
742 742 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
743 743
744 744 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
745 745 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
746 746
747 747 user = relationship('User')
748 748 fork = relationship('Repository', remote_side=repo_id)
749 749 group = relationship('RepoGroup')
750 750 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
751 751 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
752 752 stats = relationship('Statistics', cascade='all', uselist=False)
753 753
754 754 followers = relationship('UserFollowing',
755 755 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
756 756 cascade='all')
757 757 extra_fields = relationship('RepositoryField',
758 758 cascade="all, delete, delete-orphan")
759 759
760 760 logs = relationship('UserLog')
761 761 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
762 762
763 763 pull_requests_org = relationship('PullRequest',
764 764 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
765 765 cascade="all, delete, delete-orphan")
766 766
767 767 pull_requests_other = relationship('PullRequest',
768 768 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
769 769 cascade="all, delete, delete-orphan")
770 770
771 771 def __unicode__(self):
772 772 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
773 773 self.repo_name)
774 774
775 775 @hybrid_property
776 776 def locked(self):
777 777 # always should return [user_id, timelocked]
778 778 if self._locked:
779 779 _lock_info = self._locked.split(':')
780 780 return int(_lock_info[0]), _lock_info[1]
781 781 return [None, None]
782 782
783 783 @locked.setter
784 784 def locked(self, val):
785 785 if val and isinstance(val, (list, tuple)):
786 786 self._locked = ':'.join(map(str, val))
787 787 else:
788 788 self._locked = None
789 789
790 790 @hybrid_property
791 791 def changeset_cache(self):
792 792 from rhodecode.lib.vcs.backends.base import EmptyChangeset
793 793 dummy = EmptyChangeset().__json__()
794 794 if not self._changeset_cache:
795 795 return dummy
796 796 try:
797 797 return json.loads(self._changeset_cache)
798 798 except TypeError:
799 799 return dummy
800 800
801 801 @changeset_cache.setter
802 802 def changeset_cache(self, val):
803 803 try:
804 804 self._changeset_cache = json.dumps(val)
805 805 except:
806 806 log.error(traceback.format_exc())
807 807
808 808 @classmethod
809 809 def url_sep(cls):
810 810 return URL_SEP
811 811
812 812 @classmethod
813 813 def normalize_repo_name(cls, repo_name):
814 814 """
815 815 Normalizes os specific repo_name to the format internally stored inside
816 816 dabatabase using URL_SEP
817 817
818 818 :param cls:
819 819 :param repo_name:
820 820 """
821 821 return cls.url_sep().join(repo_name.split(os.sep))
822 822
823 823 @classmethod
824 824 def get_by_repo_name(cls, repo_name):
825 825 q = Session().query(cls).filter(cls.repo_name == repo_name)
826 826 q = q.options(joinedload(Repository.fork))\
827 827 .options(joinedload(Repository.user))\
828 828 .options(joinedload(Repository.group))
829 829 return q.scalar()
830 830
831 831 @classmethod
832 832 def get_by_full_path(cls, repo_full_path):
833 833 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
834 834 repo_name = cls.normalize_repo_name(repo_name)
835 835 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
836 836
837 837 @classmethod
838 838 def get_repo_forks(cls, repo_id):
839 839 return cls.query().filter(Repository.fork_id == repo_id)
840 840
841 841 @classmethod
842 842 def base_path(cls):
843 843 """
844 844 Returns base path when all repos are stored
845 845
846 846 :param cls:
847 847 """
848 848 q = Session().query(RhodeCodeUi)\
849 849 .filter(RhodeCodeUi.ui_key == cls.url_sep())
850 850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
851 851 return q.one().ui_value
852 852
853 853 @property
854 854 def forks(self):
855 855 """
856 856 Return forks of this repo
857 857 """
858 858 return Repository.get_repo_forks(self.repo_id)
859 859
860 860 @property
861 861 def parent(self):
862 862 """
863 863 Returns fork parent
864 864 """
865 865 return self.fork
866 866
867 867 @property
868 868 def just_name(self):
869 869 return self.repo_name.split(Repository.url_sep())[-1]
870 870
871 871 @property
872 872 def groups_with_parents(self):
873 873 groups = []
874 874 if self.group is None:
875 875 return groups
876 876
877 877 cur_gr = self.group
878 878 groups.insert(0, cur_gr)
879 879 while 1:
880 880 gr = getattr(cur_gr, 'parent_group', None)
881 881 cur_gr = cur_gr.parent_group
882 882 if gr is None:
883 883 break
884 884 groups.insert(0, gr)
885 885
886 886 return groups
887 887
888 888 @property
889 889 def groups_and_repo(self):
890 890 return self.groups_with_parents, self.just_name, self.repo_name
891 891
892 892 @LazyProperty
893 893 def repo_path(self):
894 894 """
895 895 Returns base full path for that repository means where it actually
896 896 exists on a filesystem
897 897 """
898 898 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
899 899 Repository.url_sep())
900 900 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
901 901 return q.one().ui_value
902 902
903 903 @property
904 904 def repo_full_path(self):
905 905 p = [self.repo_path]
906 906 # we need to split the name by / since this is how we store the
907 907 # names in the database, but that eventually needs to be converted
908 908 # into a valid system path
909 909 p += self.repo_name.split(Repository.url_sep())
910 910 return os.path.join(*p)
911 911
912 912 @property
913 913 def cache_keys(self):
914 914 """
915 915 Returns associated cache keys for that repo
916 916 """
917 917 return CacheInvalidation.query()\
918 918 .filter(CacheInvalidation.cache_args == self.repo_name)\
919 919 .order_by(CacheInvalidation.cache_key)\
920 920 .all()
921 921
922 922 def get_new_name(self, repo_name):
923 923 """
924 924 returns new full repository name based on assigned group and new new
925 925
926 926 :param group_name:
927 927 """
928 928 path_prefix = self.group.full_path_splitted if self.group else []
929 929 return Repository.url_sep().join(path_prefix + [repo_name])
930 930
931 931 @property
932 932 def _ui(self):
933 933 """
934 934 Creates an db based ui object for this repository
935 935 """
936 936 from rhodecode.lib.utils import make_ui
937 937 return make_ui('db', clear_session=False)
938 938
939 939 @classmethod
940 940 def is_valid(cls, repo_name):
941 941 """
942 942 returns True if given repo name is a valid filesystem repository
943 943
944 944 :param cls:
945 945 :param repo_name:
946 946 """
947 947 from rhodecode.lib.utils import is_valid_repo
948 948
949 949 return is_valid_repo(repo_name, cls.base_path())
950 950
951 951 def get_api_data(self):
952 952 """
953 953 Common function for generating repo api data
954 954
955 955 """
956 956 repo = self
957 957 data = dict(
958 958 repo_id=repo.repo_id,
959 959 repo_name=repo.repo_name,
960 960 repo_type=repo.repo_type,
961 961 clone_uri=repo.clone_uri,
962 962 private=repo.private,
963 963 created_on=repo.created_on,
964 964 description=repo.description,
965 965 landing_rev=repo.landing_rev,
966 966 owner=repo.user.username,
967 967 fork_of=repo.fork.repo_name if repo.fork else None,
968 968 enable_statistics=repo.enable_statistics,
969 969 enable_locking=repo.enable_locking,
970 970 enable_downloads=repo.enable_downloads,
971 971 last_changeset=repo.changeset_cache,
972 972 locked_by=User.get(self.locked[0]).get_api_data() \
973 973 if self.locked[0] else None,
974 974 locked_date=time_to_datetime(self.locked[1]) \
975 975 if self.locked[1] else None
976 976 )
977 977 rc_config = RhodeCodeSetting.get_app_settings()
978 978 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
979 979 if repository_fields:
980 980 for f in self.extra_fields:
981 981 data[f.field_key_prefixed] = f.field_value
982 982
983 983 return data
984 984
985 985 @classmethod
986 986 def lock(cls, repo, user_id):
987 987 repo.locked = [user_id, time.time()]
988 988 Session().add(repo)
989 989 Session().commit()
990 990
991 991 @classmethod
992 992 def unlock(cls, repo):
993 993 repo.locked = None
994 994 Session().add(repo)
995 995 Session().commit()
996 996
997 997 @classmethod
998 998 def getlock(cls, repo):
999 999 return repo.locked
1000 1000
1001 1001 @property
1002 1002 def last_db_change(self):
1003 1003 return self.updated_on
1004 1004
1005 1005 def clone_url(self, **override):
1006 1006 from pylons import url
1007 1007 from urlparse import urlparse
1008 1008 import urllib
1009 1009 parsed_url = urlparse(url('home', qualified=True))
1010 1010 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1011 1011 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1012 1012 args = {
1013 1013 'user': '',
1014 1014 'pass': '',
1015 1015 'scheme': parsed_url.scheme,
1016 1016 'netloc': parsed_url.netloc,
1017 1017 'prefix': decoded_path,
1018 1018 'path': self.repo_name
1019 1019 }
1020 1020
1021 1021 args.update(override)
1022 1022 return default_clone_uri % args
1023 1023
1024 1024 #==========================================================================
1025 1025 # SCM PROPERTIES
1026 1026 #==========================================================================
1027 1027
1028 1028 def get_changeset(self, rev=None):
1029 1029 return get_changeset_safe(self.scm_instance, rev)
1030 1030
1031 1031 def get_landing_changeset(self):
1032 1032 """
1033 1033 Returns landing changeset, or if that doesn't exist returns the tip
1034 1034 """
1035 1035 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1036 1036 return cs
1037 1037
1038 1038 def update_changeset_cache(self, cs_cache=None):
1039 1039 """
1040 1040 Update cache of last changeset for repository, keys should be::
1041 1041
1042 1042 short_id
1043 1043 raw_id
1044 1044 revision
1045 1045 message
1046 1046 date
1047 1047 author
1048 1048
1049 1049 :param cs_cache:
1050 1050 """
1051 1051 from rhodecode.lib.vcs.backends.base import BaseChangeset
1052 1052 if cs_cache is None:
1053 1053 cs_cache = EmptyChangeset()
1054 1054 # use no-cache version here
1055 1055 scm_repo = self.scm_instance_no_cache()
1056 1056 if scm_repo:
1057 1057 cs_cache = scm_repo.get_changeset()
1058 1058
1059 1059 if isinstance(cs_cache, BaseChangeset):
1060 1060 cs_cache = cs_cache.__json__()
1061 1061
1062 1062 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1063 1063 _default = datetime.datetime.fromtimestamp(0)
1064 1064 last_change = cs_cache.get('date') or _default
1065 1065 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1066 1066 self.updated_on = last_change
1067 1067 self.changeset_cache = cs_cache
1068 1068 Session().add(self)
1069 1069 Session().commit()
1070 1070 else:
1071 1071 log.debug('Skipping repo:%s already with latest changes' % self)
1072 1072
1073 1073 @property
1074 1074 def tip(self):
1075 1075 return self.get_changeset('tip')
1076 1076
1077 1077 @property
1078 1078 def author(self):
1079 1079 return self.tip.author
1080 1080
1081 1081 @property
1082 1082 def last_change(self):
1083 1083 return self.scm_instance.last_change
1084 1084
1085 1085 def get_comments(self, revisions=None):
1086 1086 """
1087 1087 Returns comments for this repository grouped by revisions
1088 1088
1089 1089 :param revisions: filter query by revisions only
1090 1090 """
1091 1091 cmts = ChangesetComment.query()\
1092 1092 .filter(ChangesetComment.repo == self)
1093 1093 if revisions:
1094 1094 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1095 1095 grouped = defaultdict(list)
1096 1096 for cmt in cmts.all():
1097 1097 grouped[cmt.revision].append(cmt)
1098 1098 return grouped
1099 1099
1100 1100 def statuses(self, revisions=None):
1101 1101 """
1102 1102 Returns statuses for this repository
1103 1103
1104 1104 :param revisions: list of revisions to get statuses for
1105 1105 :type revisions: list
1106 1106 """
1107 1107
1108 1108 statuses = ChangesetStatus.query()\
1109 1109 .filter(ChangesetStatus.repo == self)\
1110 1110 .filter(ChangesetStatus.version == 0)
1111 1111 if revisions:
1112 1112 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1113 1113 grouped = {}
1114 1114
1115 1115 #maybe we have open new pullrequest without a status ?
1116 1116 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1117 1117 status_lbl = ChangesetStatus.get_status_lbl(stat)
1118 1118 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1119 1119 for rev in pr.revisions:
1120 1120 pr_id = pr.pull_request_id
1121 1121 pr_repo = pr.other_repo.repo_name
1122 1122 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1123 1123
1124 1124 for stat in statuses.all():
1125 1125 pr_id = pr_repo = None
1126 1126 if stat.pull_request:
1127 1127 pr_id = stat.pull_request.pull_request_id
1128 1128 pr_repo = stat.pull_request.other_repo.repo_name
1129 1129 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1130 1130 pr_id, pr_repo]
1131 1131 return grouped
1132 1132
1133 1133 def _repo_size(self):
1134 1134 from rhodecode.lib import helpers as h
1135 1135 log.debug('calculating repository size...')
1136 1136 return h.format_byte_size(self.scm_instance.size)
1137 1137
1138 1138 #==========================================================================
1139 1139 # SCM CACHE INSTANCE
1140 1140 #==========================================================================
1141 1141
1142 1142 @property
1143 1143 def invalidate(self):
1144 1144 return CacheInvalidation.invalidate(self.repo_name)
1145 1145
1146 1146 def set_invalidate(self):
1147 1147 """
1148 1148 set a cache for invalidation for this instance
1149 1149 """
1150 1150 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1151 1151
1152 1152 def scm_instance_no_cache(self):
1153 1153 return self.__get_instance()
1154 1154
1155 1155 @LazyProperty
1156 1156 def scm_instance(self):
1157 1157 import rhodecode
1158 1158 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1159 1159 if full_cache:
1160 1160 return self.scm_instance_cached()
1161 1161 return self.__get_instance()
1162 1162
1163 1163 def scm_instance_cached(self, cache_map=None):
1164 1164 @cache_region('long_term')
1165 1165 def _c(repo_name):
1166 1166 return self.__get_instance()
1167 1167 rn = self.repo_name
1168 1168 log.debug('Getting cached instance of repo')
1169 1169
1170 1170 if cache_map:
1171 1171 # get using prefilled cache_map
1172 1172 invalidate_repo = cache_map[self.repo_name]
1173 1173 if invalidate_repo:
1174 1174 invalidate_repo = (None if invalidate_repo.cache_active
1175 1175 else invalidate_repo)
1176 1176 else:
1177 1177 # get from invalidate
1178 1178 invalidate_repo = self.invalidate
1179 1179
1180 1180 if invalidate_repo is not None:
1181 1181 region_invalidate(_c, None, rn)
1182 1182 # update our cache
1183 1183 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1184 1184 return _c(rn)
1185 1185
1186 1186 def __get_instance(self):
1187 1187 repo_full_path = self.repo_full_path
1188 1188 try:
1189 1189 alias = get_scm(repo_full_path)[0]
1190 1190 log.debug('Creating instance of %s repository from %s'
1191 1191 % (alias, repo_full_path))
1192 1192 backend = get_backend(alias)
1193 1193 except VCSError:
1194 1194 log.error(traceback.format_exc())
1195 1195 log.error('Perhaps this repository is in db and not in '
1196 1196 'filesystem run rescan repositories with '
1197 1197 '"destroy old data " option from admin panel')
1198 1198 return
1199 1199
1200 1200 if alias == 'hg':
1201 1201
1202 1202 repo = backend(safe_str(repo_full_path), create=False,
1203 1203 baseui=self._ui)
1204 1204 # skip hidden web repository
1205 1205 if repo._get_hidden():
1206 1206 return
1207 1207 else:
1208 1208 repo = backend(repo_full_path, create=False)
1209 1209
1210 1210 return repo
1211 1211
1212 1212
1213 1213 class RepoGroup(Base, BaseModel):
1214 1214 __tablename__ = 'groups'
1215 1215 __table_args__ = (
1216 1216 UniqueConstraint('group_name', 'group_parent_id'),
1217 1217 CheckConstraint('group_id != group_parent_id'),
1218 1218 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1219 1219 'mysql_charset': 'utf8'},
1220 1220 )
1221 1221 __mapper_args__ = {'order_by': 'group_name'}
1222 1222
1223 1223 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1224 1224 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1225 1225 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1226 1226 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1227 1227 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1228 1228
1229 1229 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1230 1230 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1231 1231
1232 1232 parent_group = relationship('RepoGroup', remote_side=group_id)
1233 1233
1234 1234 def __init__(self, group_name='', parent_group=None):
1235 1235 self.group_name = group_name
1236 1236 self.parent_group = parent_group
1237 1237
1238 1238 def __unicode__(self):
1239 1239 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1240 1240 self.group_name)
1241 1241
1242 1242 @classmethod
1243 1243 def groups_choices(cls, groups=None, show_empty_group=True):
1244 1244 from webhelpers.html import literal as _literal
1245 1245 if not groups:
1246 1246 groups = cls.query().all()
1247 1247
1248 1248 repo_groups = []
1249 1249 if show_empty_group:
1250 1250 repo_groups = [('-1', '-- %s --' % _('top level'))]
1251 1251 sep = ' &raquo; '
1252 1252 _name = lambda k: _literal(sep.join(k))
1253 1253
1254 1254 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1255 1255 for x in groups])
1256 1256
1257 1257 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1258 1258 return repo_groups
1259 1259
1260 1260 @classmethod
1261 1261 def url_sep(cls):
1262 1262 return URL_SEP
1263 1263
1264 1264 @classmethod
1265 1265 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1266 1266 if case_insensitive:
1267 1267 gr = cls.query()\
1268 1268 .filter(cls.group_name.ilike(group_name))
1269 1269 else:
1270 1270 gr = cls.query()\
1271 1271 .filter(cls.group_name == group_name)
1272 1272 if cache:
1273 1273 gr = gr.options(FromCache(
1274 1274 "sql_cache_short",
1275 1275 "get_group_%s" % _hash_key(group_name)
1276 1276 )
1277 1277 )
1278 1278 return gr.scalar()
1279 1279
1280 1280 @property
1281 1281 def parents(self):
1282 1282 parents_recursion_limit = 5
1283 1283 groups = []
1284 1284 if self.parent_group is None:
1285 1285 return groups
1286 1286 cur_gr = self.parent_group
1287 1287 groups.insert(0, cur_gr)
1288 1288 cnt = 0
1289 1289 while 1:
1290 1290 cnt += 1
1291 1291 gr = getattr(cur_gr, 'parent_group', None)
1292 1292 cur_gr = cur_gr.parent_group
1293 1293 if gr is None:
1294 1294 break
1295 1295 if cnt == parents_recursion_limit:
1296 1296 # this will prevent accidental infinit loops
1297 1297 log.error('group nested more than %s' %
1298 1298 parents_recursion_limit)
1299 1299 break
1300 1300
1301 1301 groups.insert(0, gr)
1302 1302 return groups
1303 1303
1304 1304 @property
1305 1305 def children(self):
1306 1306 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1307 1307
1308 1308 @property
1309 1309 def name(self):
1310 1310 return self.group_name.split(RepoGroup.url_sep())[-1]
1311 1311
1312 1312 @property
1313 1313 def full_path(self):
1314 1314 return self.group_name
1315 1315
1316 1316 @property
1317 1317 def full_path_splitted(self):
1318 1318 return self.group_name.split(RepoGroup.url_sep())
1319 1319
1320 1320 @property
1321 1321 def repositories(self):
1322 1322 return Repository.query()\
1323 1323 .filter(Repository.group == self)\
1324 1324 .order_by(Repository.repo_name)
1325 1325
1326 1326 @property
1327 1327 def repositories_recursive_count(self):
1328 1328 cnt = self.repositories.count()
1329 1329
1330 1330 def children_count(group):
1331 1331 cnt = 0
1332 1332 for child in group.children:
1333 1333 cnt += child.repositories.count()
1334 1334 cnt += children_count(child)
1335 1335 return cnt
1336 1336
1337 1337 return cnt + children_count(self)
1338 1338
1339 1339 def _recursive_objects(self, include_repos=True):
1340 1340 all_ = []
1341 1341
1342 1342 def _get_members(root_gr):
1343 1343 if include_repos:
1344 1344 for r in root_gr.repositories:
1345 1345 all_.append(r)
1346 1346 childs = root_gr.children.all()
1347 1347 if childs:
1348 1348 for gr in childs:
1349 1349 all_.append(gr)
1350 1350 _get_members(gr)
1351 1351
1352 1352 _get_members(self)
1353 1353 return [self] + all_
1354 1354
1355 1355 def recursive_groups_and_repos(self):
1356 1356 """
1357 1357 Recursive return all groups, with repositories in those groups
1358 1358 """
1359 1359 return self._recursive_objects()
1360 1360
1361 1361 def recursive_groups(self):
1362 1362 """
1363 1363 Returns all children groups for this group including children of children
1364 1364 """
1365 1365 return self._recursive_objects(include_repos=False)
1366 1366
1367 1367 def get_new_name(self, group_name):
1368 1368 """
1369 1369 returns new full group name based on parent and new name
1370 1370
1371 1371 :param group_name:
1372 1372 """
1373 1373 path_prefix = (self.parent_group.full_path_splitted if
1374 1374 self.parent_group else [])
1375 1375 return RepoGroup.url_sep().join(path_prefix + [group_name])
1376 1376
1377 1377
1378 1378 class Permission(Base, BaseModel):
1379 1379 __tablename__ = 'permissions'
1380 1380 __table_args__ = (
1381 1381 Index('p_perm_name_idx', 'permission_name'),
1382 1382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1383 1383 'mysql_charset': 'utf8'},
1384 1384 )
1385 1385 PERMS = [
1386 1386 ('repository.none', _('Repository no access')),
1387 1387 ('repository.read', _('Repository read access')),
1388 1388 ('repository.write', _('Repository write access')),
1389 1389 ('repository.admin', _('Repository admin access')),
1390 1390
1391 1391 ('group.none', _('Repository group no access')),
1392 1392 ('group.read', _('Repository group read access')),
1393 1393 ('group.write', _('Repository group write access')),
1394 1394 ('group.admin', _('Repository group admin access')),
1395 1395
1396 1396 ('hg.admin', _('RhodeCode Administrator')),
1397 1397 ('hg.create.none', _('Repository creation disabled')),
1398 1398 ('hg.create.repository', _('Repository creation enabled')),
1399 1399 ('hg.fork.none', _('Repository forking disabled')),
1400 1400 ('hg.fork.repository', _('Repository forking enabled')),
1401 1401 ('hg.register.none', _('Register disabled')),
1402 1402 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1403 1403 'with manual activation')),
1404 1404
1405 1405 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1406 1406 'with auto activation')),
1407 1407 ]
1408 1408
1409 1409 # defines which permissions are more important higher the more important
1410 1410 PERM_WEIGHTS = {
1411 1411 'repository.none': 0,
1412 1412 'repository.read': 1,
1413 1413 'repository.write': 3,
1414 1414 'repository.admin': 4,
1415 1415
1416 1416 'group.none': 0,
1417 1417 'group.read': 1,
1418 1418 'group.write': 3,
1419 1419 'group.admin': 4,
1420 1420
1421 1421 'hg.fork.none': 0,
1422 1422 'hg.fork.repository': 1,
1423 1423 'hg.create.none': 0,
1424 1424 'hg.create.repository':1
1425 1425 }
1426 1426
1427 1427 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1428 1428 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1429 1429 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1430 1430
1431 1431 def __unicode__(self):
1432 1432 return u"<%s('%s:%s')>" % (
1433 1433 self.__class__.__name__, self.permission_id, self.permission_name
1434 1434 )
1435 1435
1436 1436 @classmethod
1437 1437 def get_by_key(cls, key):
1438 1438 return cls.query().filter(cls.permission_name == key).scalar()
1439 1439
1440 1440 @classmethod
1441 1441 def get_default_perms(cls, default_user_id):
1442 1442 q = Session().query(UserRepoToPerm, Repository, cls)\
1443 1443 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1444 1444 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1445 1445 .filter(UserRepoToPerm.user_id == default_user_id)
1446 1446
1447 1447 return q.all()
1448 1448
1449 1449 @classmethod
1450 1450 def get_default_group_perms(cls, default_user_id):
1451 1451 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1452 1452 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1453 1453 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1454 1454 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1455 1455
1456 1456 return q.all()
1457 1457
1458 1458
1459 1459 class UserRepoToPerm(Base, BaseModel):
1460 1460 __tablename__ = 'repo_to_perm'
1461 1461 __table_args__ = (
1462 1462 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1463 1463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1464 1464 'mysql_charset': 'utf8'}
1465 1465 )
1466 1466 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1467 1467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1468 1468 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1469 1469 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1470 1470
1471 1471 user = relationship('User')
1472 1472 repository = relationship('Repository')
1473 1473 permission = relationship('Permission')
1474 1474
1475 1475 @classmethod
1476 1476 def create(cls, user, repository, permission):
1477 1477 n = cls()
1478 1478 n.user = user
1479 1479 n.repository = repository
1480 1480 n.permission = permission
1481 1481 Session().add(n)
1482 1482 return n
1483 1483
1484 1484 def __unicode__(self):
1485 1485 return u'<user:%s => %s >' % (self.user, self.repository)
1486 1486
1487 1487
1488 1488 class UserToPerm(Base, BaseModel):
1489 1489 __tablename__ = 'user_to_perm'
1490 1490 __table_args__ = (
1491 1491 UniqueConstraint('user_id', 'permission_id'),
1492 1492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1493 1493 'mysql_charset': 'utf8'}
1494 1494 )
1495 1495 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1496 1496 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1497 1497 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1498 1498
1499 1499 user = relationship('User')
1500 1500 permission = relationship('Permission', lazy='joined')
1501 1501
1502 1502
1503 1503 class UserGroupRepoToPerm(Base, BaseModel):
1504 1504 __tablename__ = 'users_group_repo_to_perm'
1505 1505 __table_args__ = (
1506 1506 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1507 1507 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1508 1508 'mysql_charset': 'utf8'}
1509 1509 )
1510 1510 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1511 1511 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1512 1512 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1513 1513 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1514 1514
1515 1515 users_group = relationship('UserGroup')
1516 1516 permission = relationship('Permission')
1517 1517 repository = relationship('Repository')
1518 1518
1519 1519 @classmethod
1520 1520 def create(cls, users_group, repository, permission):
1521 1521 n = cls()
1522 1522 n.users_group = users_group
1523 1523 n.repository = repository
1524 1524 n.permission = permission
1525 1525 Session().add(n)
1526 1526 return n
1527 1527
1528 1528 def __unicode__(self):
1529 1529 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1530 1530
1531 1531
1532 1532 class UserGroupToPerm(Base, BaseModel):
1533 1533 __tablename__ = 'users_group_to_perm'
1534 1534 __table_args__ = (
1535 1535 UniqueConstraint('users_group_id', 'permission_id',),
1536 1536 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1537 1537 'mysql_charset': 'utf8'}
1538 1538 )
1539 1539 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1540 1540 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1541 1541 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1542 1542
1543 1543 users_group = relationship('UserGroup')
1544 1544 permission = relationship('Permission')
1545 1545
1546 1546
1547 1547 class UserRepoGroupToPerm(Base, BaseModel):
1548 1548 __tablename__ = 'user_repo_group_to_perm'
1549 1549 __table_args__ = (
1550 1550 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1551 1551 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1552 1552 'mysql_charset': 'utf8'}
1553 1553 )
1554 1554
1555 1555 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1556 1556 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1557 1557 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1558 1558 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1559 1559
1560 1560 user = relationship('User')
1561 1561 group = relationship('RepoGroup')
1562 1562 permission = relationship('Permission')
1563 1563
1564 1564
1565 1565 class UserGroupRepoGroupToPerm(Base, BaseModel):
1566 1566 __tablename__ = 'users_group_repo_group_to_perm'
1567 1567 __table_args__ = (
1568 1568 UniqueConstraint('users_group_id', 'group_id'),
1569 1569 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1570 1570 'mysql_charset': 'utf8'}
1571 1571 )
1572 1572
1573 1573 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1574 1574 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1575 1575 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1576 1576 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1577 1577
1578 1578 users_group = relationship('UserGroup')
1579 1579 permission = relationship('Permission')
1580 1580 group = relationship('RepoGroup')
1581 1581
1582 1582
1583 1583 class Statistics(Base, BaseModel):
1584 1584 __tablename__ = 'statistics'
1585 1585 __table_args__ = (
1586 1586 UniqueConstraint('repository_id'),
1587 1587 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1588 1588 'mysql_charset': 'utf8'}
1589 1589 )
1590 1590 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1591 1591 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1592 1592 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1593 1593 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1594 1594 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1595 1595 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1596 1596
1597 1597 repository = relationship('Repository', single_parent=True)
1598 1598
1599 1599
1600 1600 class UserFollowing(Base, BaseModel):
1601 1601 __tablename__ = 'user_followings'
1602 1602 __table_args__ = (
1603 1603 UniqueConstraint('user_id', 'follows_repository_id'),
1604 1604 UniqueConstraint('user_id', 'follows_user_id'),
1605 1605 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1606 1606 'mysql_charset': 'utf8'}
1607 1607 )
1608 1608
1609 1609 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1610 1610 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1611 1611 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1612 1612 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1613 1613 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1614 1614
1615 1615 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1616 1616
1617 1617 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1618 1618 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1619 1619
1620 1620 @classmethod
1621 1621 def get_repo_followers(cls, repo_id):
1622 1622 return cls.query().filter(cls.follows_repo_id == repo_id)
1623 1623
1624 1624
1625 1625 class CacheInvalidation(Base, BaseModel):
1626 1626 __tablename__ = 'cache_invalidation'
1627 1627 __table_args__ = (
1628 1628 UniqueConstraint('cache_key'),
1629 1629 Index('key_idx', 'cache_key'),
1630 1630 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1631 1631 'mysql_charset': 'utf8'},
1632 1632 )
1633 # cache_id, not used
1633 1634 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1635 # cache_key as created by _get_cache_key
1634 1636 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1637 # cache_args is usually a repo_name, possibly with _README/_RSS/_ATOM suffix
1635 1638 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1639 # instance sets cache_active True when it is caching, other instances set cache_active to False to invalidate
1636 1640 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1637 1641
1638 1642 def __init__(self, cache_key, cache_args=''):
1639 1643 self.cache_key = cache_key
1640 1644 self.cache_args = cache_args
1641 1645 self.cache_active = False
1642 1646
1643 1647 def __unicode__(self):
1644 1648 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1645 1649 self.cache_id, self.cache_key)
1646 1650
1647 @property
1648 def prefix(self):
1651 def get_prefix(self):
1652 """
1653 Guess prefix that might have been used in _get_cache_key to generate self.cache_key .
1654 Only used for informational purposes in repo_edit.html .
1655 """
1649 1656 _split = self.cache_key.split(self.cache_args, 1)
1650 if _split and len(_split) == 2:
1657 if len(_split) == 2:
1651 1658 return _split[0]
1652 1659 return ''
1653 1660
1654 1661 @classmethod
1655 def clear_cache(cls):
1656 cls.query().delete()
1657
1658 @classmethod
1659 def _get_key(cls, key):
1662 def _get_cache_key(cls, key):
1660 1663 """
1661 Wrapper for generating a key, together with a prefix
1662
1663 :param key:
1664 Wrapper for generating a unique cache key for this instance and "key".
1664 1665 """
1665 1666 import rhodecode
1666 prefix = ''
1667 org_key = key
1668 iid = rhodecode.CONFIG.get('instance_id')
1669 if iid:
1670 prefix = iid
1671
1672 return "%s%s" % (prefix, key), prefix, org_key
1667 prefix = rhodecode.CONFIG.get('instance_id', '')
1668 return "%s%s" % (prefix, key)
1673 1669
1674 1670 @classmethod
1675 def get_by_key(cls, key):
1676 return cls.query().filter(cls.cache_key == key).scalar()
1677
1678 @classmethod
1679 def get_by_repo_name(cls, repo_name):
1680 return cls.query().filter(cls.cache_args == repo_name).all()
1681
1682 @classmethod
1683 def _get_or_create_key(cls, key, repo_name, commit=True):
1671 def _get_or_create_inv_obj(cls, key, repo_name, commit=True):
1684 1672 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1685 1673 if not inv_obj:
1686 1674 try:
1687 1675 inv_obj = CacheInvalidation(key, repo_name)
1688 1676 Session().add(inv_obj)
1689 1677 if commit:
1690 1678 Session().commit()
1691 1679 except Exception:
1692 1680 log.error(traceback.format_exc())
1693 1681 Session().rollback()
1694 1682 return inv_obj
1695 1683
1696 1684 @classmethod
1697 1685 def invalidate(cls, key):
1698 1686 """
1699 1687 Returns Invalidation object if this given key should be invalidated
1700 1688 None otherwise. `cache_active = False` means that this cache
1701 1689 state is not valid and needs to be invalidated
1702 1690
1703 1691 :param key:
1704 1692 """
1705 1693 repo_name = key
1706 1694 repo_name = remove_suffix(repo_name, '_README')
1707 1695 repo_name = remove_suffix(repo_name, '_RSS')
1708 1696 repo_name = remove_suffix(repo_name, '_ATOM')
1709 1697
1710 # adds instance prefix
1711 key, _prefix, _org_key = cls._get_key(key)
1712 inv = cls._get_or_create_key(key, repo_name)
1698 cache_key = cls._get_cache_key(key)
1699 inv = cls._get_or_create_inv_obj(cache_key, repo_name)
1713 1700
1714 1701 if inv and inv.cache_active is False:
1715 1702 return inv
1716 1703
1717 1704 @classmethod
1718 1705 def set_invalidate(cls, key=None, repo_name=None):
1719 1706 """
1720 1707 Mark this Cache key for invalidation, either by key or whole
1721 1708 cache sets based on repo_name
1722 1709
1723 1710 :param key:
1724 1711 """
1725 1712 invalidated_keys = []
1726 1713 if key:
1727 key, _prefix, _org_key = cls._get_key(key)
1728 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1729 elif repo_name:
1714 assert not repo_name
1715 cache_key = cls._get_cache_key(key)
1716 inv_objs = Session().query(cls).filter(cls.cache_key == cache_key).all()
1717 else:
1718 assert repo_name
1730 1719 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1731 1720
1732 1721 try:
1733 1722 for inv_obj in inv_objs:
1734 1723 inv_obj.cache_active = False
1735 1724 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1736 1725 % (inv_obj, key, safe_str(repo_name)))
1737 1726 invalidated_keys.append(inv_obj.cache_key)
1738 1727 Session().add(inv_obj)
1739 1728 Session().commit()
1740 1729 except Exception:
1741 1730 log.error(traceback.format_exc())
1742 1731 Session().rollback()
1743 1732 return invalidated_keys
1744 1733
1745 1734 @classmethod
1746 1735 def set_valid(cls, key):
1747 1736 """
1748 1737 Mark this cache key as active and currently cached
1749 1738
1750 1739 :param key:
1751 1740 """
1752 inv_obj = cls.get_by_key(key)
1741 inv_obj = cls.query().filter(cls.cache_key == key).scalar()
1753 1742 inv_obj.cache_active = True
1754 1743 Session().add(inv_obj)
1755 1744 Session().commit()
1756 1745
1757 1746 @classmethod
1758 1747 def get_cache_map(cls):
1759 1748
1760 1749 class cachemapdict(dict):
1761 1750
1762 1751 def __init__(self, *args, **kwargs):
1763 fixkey = kwargs.get('fixkey')
1764 if fixkey:
1765 del kwargs['fixkey']
1766 self.fixkey = fixkey
1752 self.fixkey = kwargs.pop('fixkey', False)
1767 1753 super(cachemapdict, self).__init__(*args, **kwargs)
1768 1754
1769 1755 def __getattr__(self, name):
1770 key = name
1756 cache_key = name
1771 1757 if self.fixkey:
1772 key, _prefix, _org_key = cls._get_key(key)
1773 if key in self.__dict__:
1774 return self.__dict__[key]
1758 cache_key = cls._get_cache_key(name)
1759 if cache_key in self.__dict__:
1760 return self.__dict__[cache_key]
1775 1761 else:
1776 return self[key]
1762 return self[cache_key]
1777 1763
1778 def __getitem__(self, key):
1764 def __getitem__(self, name):
1765 cache_key = name
1779 1766 if self.fixkey:
1780 key, _prefix, _org_key = cls._get_key(key)
1767 cache_key = cls._get_cache_key(name)
1781 1768 try:
1782 return super(cachemapdict, self).__getitem__(key)
1769 return super(cachemapdict, self).__getitem__(cache_key)
1783 1770 except KeyError:
1784 return
1771 return None
1785 1772
1786 1773 cache_map = cachemapdict(fixkey=True)
1787 1774 for obj in cls.query().all():
1788 1775 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1789 1776 return cache_map
1790 1777
1791 1778
1792 1779 class ChangesetComment(Base, BaseModel):
1793 1780 __tablename__ = 'changeset_comments'
1794 1781 __table_args__ = (
1795 1782 Index('cc_revision_idx', 'revision'),
1796 1783 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1797 1784 'mysql_charset': 'utf8'},
1798 1785 )
1799 1786 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1800 1787 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1801 1788 revision = Column('revision', String(40), nullable=True)
1802 1789 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1803 1790 line_no = Column('line_no', Unicode(10), nullable=True)
1804 1791 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1805 1792 f_path = Column('f_path', Unicode(1000), nullable=True)
1806 1793 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1807 1794 text = Column('text', UnicodeText(25000), nullable=False)
1808 1795 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1809 1796 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1810 1797
1811 1798 author = relationship('User', lazy='joined')
1812 1799 repo = relationship('Repository')
1813 1800 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1814 1801 pull_request = relationship('PullRequest', lazy='joined')
1815 1802
1816 1803 @classmethod
1817 1804 def get_users(cls, revision=None, pull_request_id=None):
1818 1805 """
1819 1806 Returns user associated with this ChangesetComment. ie those
1820 1807 who actually commented
1821 1808
1822 1809 :param cls:
1823 1810 :param revision:
1824 1811 """
1825 1812 q = Session().query(User)\
1826 1813 .join(ChangesetComment.author)
1827 1814 if revision:
1828 1815 q = q.filter(cls.revision == revision)
1829 1816 elif pull_request_id:
1830 1817 q = q.filter(cls.pull_request_id == pull_request_id)
1831 1818 return q.all()
1832 1819
1833 1820
1834 1821 class ChangesetStatus(Base, BaseModel):
1835 1822 __tablename__ = 'changeset_statuses'
1836 1823 __table_args__ = (
1837 1824 Index('cs_revision_idx', 'revision'),
1838 1825 Index('cs_version_idx', 'version'),
1839 1826 UniqueConstraint('repo_id', 'revision', 'version'),
1840 1827 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1841 1828 'mysql_charset': 'utf8'}
1842 1829 )
1843 1830 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1844 1831 STATUS_APPROVED = 'approved'
1845 1832 STATUS_REJECTED = 'rejected'
1846 1833 STATUS_UNDER_REVIEW = 'under_review'
1847 1834
1848 1835 STATUSES = [
1849 1836 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1850 1837 (STATUS_APPROVED, _("Approved")),
1851 1838 (STATUS_REJECTED, _("Rejected")),
1852 1839 (STATUS_UNDER_REVIEW, _("Under Review")),
1853 1840 ]
1854 1841
1855 1842 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1856 1843 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1857 1844 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1858 1845 revision = Column('revision', String(40), nullable=False)
1859 1846 status = Column('status', String(128), nullable=False, default=DEFAULT)
1860 1847 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1861 1848 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1862 1849 version = Column('version', Integer(), nullable=False, default=0)
1863 1850 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1864 1851
1865 1852 author = relationship('User', lazy='joined')
1866 1853 repo = relationship('Repository')
1867 1854 comment = relationship('ChangesetComment', lazy='joined')
1868 1855 pull_request = relationship('PullRequest', lazy='joined')
1869 1856
1870 1857 def __unicode__(self):
1871 1858 return u"<%s('%s:%s')>" % (
1872 1859 self.__class__.__name__,
1873 1860 self.status, self.author
1874 1861 )
1875 1862
1876 1863 @classmethod
1877 1864 def get_status_lbl(cls, value):
1878 1865 return dict(cls.STATUSES).get(value)
1879 1866
1880 1867 @property
1881 1868 def status_lbl(self):
1882 1869 return ChangesetStatus.get_status_lbl(self.status)
1883 1870
1884 1871
1885 1872 class PullRequest(Base, BaseModel):
1886 1873 __tablename__ = 'pull_requests'
1887 1874 __table_args__ = (
1888 1875 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1889 1876 'mysql_charset': 'utf8'},
1890 1877 )
1891 1878
1892 1879 STATUS_NEW = u'new'
1893 1880 STATUS_OPEN = u'open'
1894 1881 STATUS_CLOSED = u'closed'
1895 1882
1896 1883 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1897 1884 title = Column('title', Unicode(256), nullable=True)
1898 1885 description = Column('description', UnicodeText(10240), nullable=True)
1899 1886 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1900 1887 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1901 1888 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1902 1889 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1903 1890 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1904 1891 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1905 1892 org_ref = Column('org_ref', Unicode(256), nullable=False)
1906 1893 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1907 1894 other_ref = Column('other_ref', Unicode(256), nullable=False)
1908 1895
1909 1896 @hybrid_property
1910 1897 def revisions(self):
1911 1898 return self._revisions.split(':')
1912 1899
1913 1900 @revisions.setter
1914 1901 def revisions(self, val):
1915 1902 self._revisions = ':'.join(val)
1916 1903
1917 1904 @property
1918 1905 def org_ref_parts(self):
1919 1906 return self.org_ref.split(':')
1920 1907
1921 1908 @property
1922 1909 def other_ref_parts(self):
1923 1910 return self.other_ref.split(':')
1924 1911
1925 1912 author = relationship('User', lazy='joined')
1926 1913 reviewers = relationship('PullRequestReviewers',
1927 1914 cascade="all, delete, delete-orphan")
1928 1915 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1929 1916 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1930 1917 statuses = relationship('ChangesetStatus')
1931 1918 comments = relationship('ChangesetComment',
1932 1919 cascade="all, delete, delete-orphan")
1933 1920
1934 1921 def is_closed(self):
1935 1922 return self.status == self.STATUS_CLOSED
1936 1923
1937 1924 @property
1938 1925 def last_review_status(self):
1939 1926 return self.statuses[-1].status if self.statuses else ''
1940 1927
1941 1928 def __json__(self):
1942 1929 return dict(
1943 1930 revisions=self.revisions
1944 1931 )
1945 1932
1946 1933
1947 1934 class PullRequestReviewers(Base, BaseModel):
1948 1935 __tablename__ = 'pull_request_reviewers'
1949 1936 __table_args__ = (
1950 1937 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1951 1938 'mysql_charset': 'utf8'},
1952 1939 )
1953 1940
1954 1941 def __init__(self, user=None, pull_request=None):
1955 1942 self.user = user
1956 1943 self.pull_request = pull_request
1957 1944
1958 1945 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1959 1946 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1960 1947 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1961 1948
1962 1949 user = relationship('User')
1963 1950 pull_request = relationship('PullRequest')
1964 1951
1965 1952
1966 1953 class Notification(Base, BaseModel):
1967 1954 __tablename__ = 'notifications'
1968 1955 __table_args__ = (
1969 1956 Index('notification_type_idx', 'type'),
1970 1957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1971 1958 'mysql_charset': 'utf8'},
1972 1959 )
1973 1960
1974 1961 TYPE_CHANGESET_COMMENT = u'cs_comment'
1975 1962 TYPE_MESSAGE = u'message'
1976 1963 TYPE_MENTION = u'mention'
1977 1964 TYPE_REGISTRATION = u'registration'
1978 1965 TYPE_PULL_REQUEST = u'pull_request'
1979 1966 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1980 1967
1981 1968 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1982 1969 subject = Column('subject', Unicode(512), nullable=True)
1983 1970 body = Column('body', UnicodeText(50000), nullable=True)
1984 1971 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1985 1972 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1986 1973 type_ = Column('type', Unicode(256))
1987 1974
1988 1975 created_by_user = relationship('User')
1989 1976 notifications_to_users = relationship('UserNotification', lazy='joined',
1990 1977 cascade="all, delete, delete-orphan")
1991 1978
1992 1979 @property
1993 1980 def recipients(self):
1994 1981 return [x.user for x in UserNotification.query()\
1995 1982 .filter(UserNotification.notification == self)\
1996 1983 .order_by(UserNotification.user_id.asc()).all()]
1997 1984
1998 1985 @classmethod
1999 1986 def create(cls, created_by, subject, body, recipients, type_=None):
2000 1987 if type_ is None:
2001 1988 type_ = Notification.TYPE_MESSAGE
2002 1989
2003 1990 notification = cls()
2004 1991 notification.created_by_user = created_by
2005 1992 notification.subject = subject
2006 1993 notification.body = body
2007 1994 notification.type_ = type_
2008 1995 notification.created_on = datetime.datetime.now()
2009 1996
2010 1997 for u in recipients:
2011 1998 assoc = UserNotification()
2012 1999 assoc.notification = notification
2013 2000 u.notifications.append(assoc)
2014 2001 Session().add(notification)
2015 2002 return notification
2016 2003
2017 2004 @property
2018 2005 def description(self):
2019 2006 from rhodecode.model.notification import NotificationModel
2020 2007 return NotificationModel().make_description(self)
2021 2008
2022 2009
2023 2010 class UserNotification(Base, BaseModel):
2024 2011 __tablename__ = 'user_to_notification'
2025 2012 __table_args__ = (
2026 2013 UniqueConstraint('user_id', 'notification_id'),
2027 2014 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2028 2015 'mysql_charset': 'utf8'}
2029 2016 )
2030 2017 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2031 2018 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2032 2019 read = Column('read', Boolean, default=False)
2033 2020 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2034 2021
2035 2022 user = relationship('User', lazy="joined")
2036 2023 notification = relationship('Notification', lazy="joined",
2037 2024 order_by=lambda: Notification.created_on.desc(),)
2038 2025
2039 2026 def mark_as_read(self):
2040 2027 self.read = True
2041 2028 Session().add(self)
2042 2029
2043 2030
2044 2031 class DbMigrateVersion(Base, BaseModel):
2045 2032 __tablename__ = 'db_migrate_version'
2046 2033 __table_args__ = (
2047 2034 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2048 2035 'mysql_charset': 'utf8'},
2049 2036 )
2050 2037 repository_id = Column('repository_id', String(250), primary_key=True)
2051 2038 repository_path = Column('repository_path', Text)
2052 2039 version = Column('version', Integer)
@@ -1,378 +1,378 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ##
3 3 ## See also repo_settings.html
4 4 ##
5 5 <%inherit file="/base/base.html"/>
6 6
7 7 <%def name="title()">
8 8 ${_('Edit repository')} ${c.repo_info.repo_name} &middot; ${c.rhodecode_name}
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${_('Settings')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('admin')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 ${self.context_bar('options')}
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 32 <label for="repo_name">${_('Name')}:</label>
33 33 </div>
34 34 <div class="input">
35 35 ${h.text('repo_name',class_="medium")}
36 36 </div>
37 37 </div>
38 38 <div class="field">
39 39 <div class="label">
40 40 <label for="clone_uri">${_('Clone uri')}:</label>
41 41 </div>
42 42 <div class="input">
43 43 ${h.text('clone_uri',class_="medium")}
44 44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
45 45 </div>
46 46 </div>
47 47 <div class="field">
48 48 <div class="label">
49 49 <label for="repo_group">${_('Repository group')}:</label>
50 50 </div>
51 51 <div class="input">
52 52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
53 53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
54 54 </div>
55 55 </div>
56 56 <div class="field">
57 57 <div class="label">
58 58 <label for="repo_type">${_('Type')}:</label>
59 59 </div>
60 60 <div class="input">
61 61 ${h.select('repo_type','hg',c.backends,class_="medium")}
62 62 </div>
63 63 </div>
64 64 <div class="field">
65 65 <div class="label">
66 66 <label for="repo_landing_rev">${_('Landing revision')}:</label>
67 67 </div>
68 68 <div class="input">
69 69 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
70 70 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
71 71 </div>
72 72 </div>
73 73 <div class="field">
74 74 <div class="label label-textarea">
75 75 <label for="repo_description">${_('Description')}:</label>
76 76 </div>
77 77 <div class="textarea text-area editor">
78 78 ${h.textarea('repo_description')}
79 79 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
80 80 </div>
81 81 </div>
82 82
83 83 <div class="field">
84 84 <div class="label label-checkbox">
85 85 <label for="repo_private">${_('Private repository')}:</label>
86 86 </div>
87 87 <div class="checkboxes">
88 88 ${h.checkbox('repo_private',value="True")}
89 89 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
90 90 </div>
91 91 </div>
92 92 <div class="field">
93 93 <div class="label label-checkbox">
94 94 <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
95 95 </div>
96 96 <div class="checkboxes">
97 97 ${h.checkbox('repo_enable_statistics',value="True")}
98 98 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
99 99 </div>
100 100 </div>
101 101 <div class="field">
102 102 <div class="label label-checkbox">
103 103 <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
104 104 </div>
105 105 <div class="checkboxes">
106 106 ${h.checkbox('repo_enable_downloads',value="True")}
107 107 <span class="help-block">${_('Enable download menu on summary page.')}</span>
108 108 </div>
109 109 </div>
110 110 <div class="field">
111 111 <div class="label label-checkbox">
112 112 <label for="repo_enable_locking">${_('Enable locking')}:</label>
113 113 </div>
114 114 <div class="checkboxes">
115 115 ${h.checkbox('repo_enable_locking',value="True")}
116 116 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
117 117 </div>
118 118 </div>
119 119 <div class="field">
120 120 <div class="label">
121 121 <label for="user">${_('Owner')}:</label>
122 122 </div>
123 123 <div class="input input-medium ac">
124 124 <div class="perm_ac">
125 125 ${h.text('user',class_='yui-ac-input')}
126 126 <span class="help-block">${_('Change owner of this repository.')}</span>
127 127 <div id="owner_container"></div>
128 128 </div>
129 129 </div>
130 130 </div>
131 131 %if c.visual.repository_fields:
132 132 ## EXTRA FIELDS
133 133 %for field in c.repo_fields:
134 134 <div class="field">
135 135 <div class="label">
136 136 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
137 137 </div>
138 138 <div class="input input-medium">
139 139 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
140 140 %if field.field_desc:
141 141 <span class="help-block">${field.field_desc}</span>
142 142 %endif
143 143 </div>
144 144 </div>
145 145 %endfor
146 146 %endif
147 147 <div class="field">
148 148 <div class="label">
149 149 <label for="input">${_('Permissions')}:</label>
150 150 </div>
151 151 <div class="input">
152 152 <%include file="repo_edit_perms.html"/>
153 153 </div>
154 154 </div>
155 155
156 156 <div class="buttons">
157 157 ${h.submit('save',_('Save'),class_="ui-btn large")}
158 158 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
159 159 </div>
160 160 </div>
161 161 </div>
162 162 ${h.end_form()}
163 163 </div>
164 164
165 165 <div class="box box-right">
166 166 <div class="title">
167 167 <h5>${_('Advanced settings')}</h5>
168 168 </div>
169 169
170 170 <h3>${_('Statistics')}</h3>
171 171 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
172 172 <div class="form">
173 173 <div class="fields">
174 174 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
175 175 <div class="field" style="border:none;color:#888">
176 176 <ul>
177 177 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
178 178 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
179 179 </ul>
180 180 </div>
181 181 </div>
182 182 </div>
183 183 ${h.end_form()}
184 184
185 185 %if c.repo_info.clone_uri:
186 186 <h3>${_('Remote')}</h3>
187 187 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
188 188 <div class="form">
189 189 <div class="fields">
190 190 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
191 191 <div class="field" style="border:none">
192 192 <ul>
193 193 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
194 194 </ul>
195 195 </div>
196 196 </div>
197 197 </div>
198 198 ${h.end_form()}
199 199 %endif
200 200
201 201 <h3>${_('Cache')}</h3>
202 202 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
203 203 <div class="form">
204 204 <div class="fields">
205 205 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
206 206 <div class="field" style="border:none;color:#888">
207 207 <ul>
208 208 <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')}
209 209 </li>
210 210 </ul>
211 211 </div>
212 212 <div class="field" style="border:none;">
213 213 ${_('List of cached values')}
214 214 <table>
215 215 <tr>
216 216 <th>${_('Prefix')}</th>
217 217 <th>${_('Key')}</th>
218 218 <th>${_('Active')}</th>
219 219 </tr>
220 220 %for cache in c.repo_info.cache_keys:
221 221 <tr>
222 <td>${cache.prefix or '-'}</td>
222 <td>${cache.get_prefix() or '-'}</td>
223 223 <td>${cache.cache_key}</td>
224 224 <td>${h.bool2icon(cache.cache_active)}</td>
225 225 </tr>
226 226 %endfor
227 227 </table>
228 228 </div>
229 229 </div>
230 230 </div>
231 231 ${h.end_form()}
232 232
233 233 <h3>${_('Public journal')}</h3>
234 234 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
235 235 <div class="form">
236 236 ${h.hidden('auth_token',str(h.get_token()))}
237 237 <div class="field">
238 238 %if c.in_public_journal:
239 239 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
240 240 %else:
241 241 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
242 242 %endif
243 243 </div>
244 244 <div class="field" style="border:none;color:#888">
245 245 <ul>
246 246 <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
247 247 </li>
248 248 </ul>
249 249 </div>
250 250 </div>
251 251 ${h.end_form()}
252 252
253 253 <h3>${_('Locking')}</h3>
254 254 ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')}
255 255 <div class="form">
256 256 <div class="fields">
257 257 %if c.repo_info.locked[0]:
258 258 ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")}
259 259 ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
260 260 %else:
261 261 ${h.submit('set_lock',_('lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")}
262 262 ${_('Repository is not locked')}
263 263 %endif
264 264 </div>
265 265 <div class="field" style="border:none;color:#888">
266 266 <ul>
267 267 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
268 268 </li>
269 269 </ul>
270 270 </div>
271 271 </div>
272 272 ${h.end_form()}
273 273
274 274 <h3>${_('Set as fork of')}</h3>
275 275 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
276 276 <div class="form">
277 277 <div class="fields">
278 278 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
279 279 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
280 280 </div>
281 281 <div class="field" style="border:none;color:#888">
282 282 <ul>
283 283 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
284 284 </ul>
285 285 </div>
286 286 </div>
287 287 ${h.end_form()}
288 288
289 289 <h3>${_('Delete')}</h3>
290 290 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
291 291 <div class="form">
292 292 <div class="fields">
293 293 <div class="field" style="border:none;color:#888">
294 294 ## <div class="label">
295 295 ## <label for="">${_('Remove repository')}:</label>
296 296 ## </div>
297 297 <div class="checkboxes">
298 298 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
299 299 %if c.repo_info.forks.count():
300 300 - ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()}
301 301 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
302 302 <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label>
303 303 %endif
304 304 <ul>
305 305 <li>${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need to fully delete it from file system please do it manually')}</li>
306 306 </ul>
307 307 </div>
308 308 </div>
309 309 </div>
310 310 </div>
311 311 ${h.end_form()}
312 312 </div>
313 313
314 314 ##TODO: this should be controlled by the VISUAL setting
315 315 %if c.visual.repository_fields:
316 316 <div class="box box-left" style="clear:left">
317 317 <!-- box / title -->
318 318 <div class="title">
319 319 <h5>${_('Extra fields')}</h5>
320 320 </div>
321 321
322 322 <div class="emails_wrap">
323 323 <table class="noborder">
324 324 %for field in c.repo_fields:
325 325 <tr>
326 326 <td>${field.field_label} (${field.field_key})</td>
327 327 <td>${field.field_type}</td>
328 328 <td>
329 329 ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')}
330 330 ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id,
331 331 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")}
332 332 ${h.end_form()}
333 333 </td>
334 334 </tr>
335 335 %endfor
336 336 </table>
337 337 </div>
338 338
339 339 ${h.form(url('create_repo_fields', repo_name=c.repo_info.repo_name),method='put')}
340 340 <div class="form">
341 341 <!-- fields -->
342 342 <div class="fields">
343 343 <div class="field">
344 344 <div class="label">
345 345 <label for="new_field_key">${_('New field key')}:</label>
346 346 </div>
347 347 <div class="input">
348 348 ${h.text('new_field_key', class_='small')}
349 349 </div>
350 350 </div>
351 351 <div class="field">
352 352 <div class="label">
353 353 <label for="new_field_label">${_('New field label')}:</label>
354 354 </div>
355 355 <div class="input">
356 356 ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))}
357 357 </div>
358 358 </div>
359 359
360 360 <div class="field">
361 361 <div class="label">
362 362 <label for="new_field_desc">${_('New field description')}:</label>
363 363 </div>
364 364 <div class="input">
365 365 ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))}
366 366 </div>
367 367 </div>
368 368
369 369 <div class="buttons">
370 370 ${h.submit('save',_('Add'),class_="ui-btn large")}
371 371 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
372 372 </div>
373 373 </div>
374 374 </div>
375 375 ${h.end_form()}
376 376 </div>
377 377 %endif
378 378 </%def>
General Comments 0
You need to be logged in to leave comments. Login now