##// END OF EJS Templates
accept that repos are read-only - very convenient for testing....
marcink -
r3980:3648a2b2 default
parent child Browse files
Show More
@@ -1,721 +1,729 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.db_manage
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database creation, and setup module for RhodeCode. Used for creation
7 7 of database as well as for migration operations
8 8
9 9 :created_on: Apr 10, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import sys
29 29 import time
30 30 import uuid
31 31 import logging
32 32 from os.path import dirname as dn, join as jn
33 33 import datetime
34 34
35 35 from rhodecode import __dbversion__, __py_version__
36 36
37 37 from rhodecode.model.user import UserModel
38 38 from rhodecode.lib.utils import ask_ok
39 39 from rhodecode.model import init_model
40 40 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
41 41 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \
42 42 UserRepoGroupToPerm, CacheInvalidation, UserGroup
43 43
44 44 from sqlalchemy.engine import create_engine
45 45 from rhodecode.model.repos_group import ReposGroupModel
46 46 #from rhodecode.model import meta
47 47 from rhodecode.model.meta import Session, Base
48 48 from rhodecode.model.repo import RepoModel
49 49 from rhodecode.model.permission import PermissionModel
50 50 from rhodecode.model.users_group import UserGroupModel
51 51
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 def notify(msg):
57 57 """
58 58 Notification for migrations messages
59 59 """
60 60 ml = len(msg) + (4 * 2)
61 61 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
62 62
63 63
64 64 class UpgradeSteps(object):
65 65 """
66 66 Those steps follow schema versions so for example schema
67 67 for example schema with seq 002 == step_2 and so on.
68 68 """
69 69
70 70 def __init__(self, klass):
71 71 self.klass = klass
72 72
73 73 def step_1(self):
74 74 pass
75 75
76 76 def step_2(self):
77 77 notify('Patching repo paths for newer version of RhodeCode')
78 78 self.klass.fix_repo_paths()
79 79
80 80 notify('Patching default user of RhodeCode')
81 81 self.klass.fix_default_user()
82 82
83 83 log.info('Changing ui settings')
84 84 self.klass.create_ui_settings()
85 85
86 86 def step_3(self):
87 87 notify('Adding additional settings into RhodeCode db')
88 88 self.klass.fix_settings()
89 89 notify('Adding ldap defaults')
90 90 self.klass.create_ldap_options(skip_existing=True)
91 91
92 92 def step_4(self):
93 93 notify('create permissions and fix groups')
94 94 self.klass.create_permissions()
95 95 self.klass.fixup_groups()
96 96
97 97 def step_5(self):
98 98 pass
99 99
100 100 def step_6(self):
101 101
102 102 notify('re-checking permissions')
103 103 self.klass.create_permissions()
104 104
105 105 notify('installing new UI options')
106 106 sett4 = RhodeCodeSetting('show_public_icon', True)
107 107 Session().add(sett4)
108 108 sett5 = RhodeCodeSetting('show_private_icon', True)
109 109 Session().add(sett5)
110 110 sett6 = RhodeCodeSetting('stylify_metatags', False)
111 111 Session().add(sett6)
112 112
113 113 notify('fixing old PULL hook')
114 114 _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger')
115 115 if _pull:
116 116 _pull.ui_key = RhodeCodeUi.HOOK_PULL
117 117 Session().add(_pull)
118 118
119 119 notify('fixing old PUSH hook')
120 120 _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger')
121 121 if _push:
122 122 _push.ui_key = RhodeCodeUi.HOOK_PUSH
123 123 Session().add(_push)
124 124
125 125 notify('installing new pre-push hook')
126 126 hooks4 = RhodeCodeUi()
127 127 hooks4.ui_section = 'hooks'
128 128 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
129 129 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
130 130 Session().add(hooks4)
131 131
132 132 notify('installing new pre-pull hook')
133 133 hooks6 = RhodeCodeUi()
134 134 hooks6.ui_section = 'hooks'
135 135 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
136 136 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
137 137 Session().add(hooks6)
138 138
139 139 notify('installing hgsubversion option')
140 140 # enable hgsubversion disabled by default
141 141 hgsubversion = RhodeCodeUi()
142 142 hgsubversion.ui_section = 'extensions'
143 143 hgsubversion.ui_key = 'hgsubversion'
144 144 hgsubversion.ui_value = ''
145 145 hgsubversion.ui_active = False
146 146 Session().add(hgsubversion)
147 147
148 148 notify('installing hg git option')
149 149 # enable hggit disabled by default
150 150 hggit = RhodeCodeUi()
151 151 hggit.ui_section = 'extensions'
152 152 hggit.ui_key = 'hggit'
153 153 hggit.ui_value = ''
154 154 hggit.ui_active = False
155 155 Session().add(hggit)
156 156
157 157 notify('re-check default permissions')
158 158 default_user = User.get_by_username(User.DEFAULT_USER)
159 159 perm = Permission.get_by_key('hg.fork.repository')
160 160 reg_perm = UserToPerm()
161 161 reg_perm.user = default_user
162 162 reg_perm.permission = perm
163 163 Session().add(reg_perm)
164 164
165 165 def step_7(self):
166 166 perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER)
167 167 Session().commit()
168 168 if perm_fixes:
169 169 notify('There was an inconsistent state of permissions '
170 170 'detected for default user. Permissions are now '
171 171 'reset to the default value for default user. '
172 172 'Please validate and check default permissions '
173 173 'in admin panel')
174 174
175 175 def step_8(self):
176 176 self.klass.create_permissions()
177 177 self.klass.populate_default_permissions()
178 178 self.klass.create_default_options(skip_existing=True)
179 179 Session().commit()
180 180
181 181 def step_9(self):
182 182 pass
183 183
184 184 def step_10(self):
185 185 pass
186 186
187 187 def step_11(self):
188 188 self.klass.update_repo_info()
189 189
190 190 def step_12(self):
191 191 self.klass.create_permissions()
192 192 Session().commit()
193 193
194 194 self.klass.populate_default_permissions()
195 195 Session().commit()
196 196
197 197 #fix all usergroups
198 198 ug_model = UserGroupModel()
199 199 for ug in UserGroup.get_all():
200 200 perm_obj = ug_model._create_default_perms(ug)
201 201 Session().add(perm_obj)
202 202 Session().commit()
203 203
204 204 adm = User.get_first_admin()
205 205 # fix owners of UserGroup
206 206 for ug in Session().query(UserGroup).all():
207 207 ug.user_id = adm.user_id
208 208 Session().add(ug)
209 209 Session().commit()
210 210
211 211 # fix owners of RepoGroup
212 212 for ug in Session().query(RepoGroup).all():
213 213 ug.user_id = adm.user_id
214 214 Session().add(ug)
215 215 Session().commit()
216 216
217 217 def step_13(self):
218 218 pass
219 219
220 220 def step_14(self):
221 221 # fix nullable columns on last_update
222 222 for r in RepoModel().get_all():
223 223 if r.updated_on is None:
224 224 r.updated_on = datetime.datetime.fromtimestamp(0)
225 225 Session().add(r)
226 226 Session().commit()
227 227
228 228 class DbManage(object):
229 229 def __init__(self, log_sql, dbconf, root, tests=False, cli_args={}):
230 230 self.dbname = dbconf.split('/')[-1]
231 231 self.tests = tests
232 232 self.root = root
233 233 self.dburi = dbconf
234 234 self.log_sql = log_sql
235 235 self.db_exists = False
236 236 self.cli_args = cli_args
237 237 self.init_db()
238 238
239 239 force_ask = self.cli_args.get('force_ask')
240 240 if force_ask is not None:
241 241 global ask_ok
242 242 ask_ok = lambda *args, **kwargs: force_ask
243 243
244 244 def init_db(self):
245 245 engine = create_engine(self.dburi, echo=self.log_sql)
246 246 init_model(engine)
247 247 self.sa = Session()
248 248
249 249 def create_tables(self, override=False):
250 250 """
251 251 Create a auth database
252 252 """
253 253
254 254 log.info("Any existing database is going to be destroyed")
255 255 if self.tests:
256 256 destroy = True
257 257 else:
258 258 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
259 259 if not destroy:
260 260 sys.exit('Nothing tables created')
261 261 if destroy:
262 262 Base.metadata.drop_all()
263 263
264 264 checkfirst = not override
265 265 Base.metadata.create_all(checkfirst=checkfirst)
266 266 log.info('Created tables for %s' % self.dbname)
267 267
268 268 def set_db_version(self):
269 269 ver = DbMigrateVersion()
270 270 ver.version = __dbversion__
271 271 ver.repository_id = 'rhodecode_db_migrations'
272 272 ver.repository_path = 'versions'
273 273 self.sa.add(ver)
274 274 log.info('db version set to: %s' % __dbversion__)
275 275
276 276 def upgrade(self):
277 277 """
278 278 Upgrades given database schema to given revision following
279 279 all needed steps, to perform the upgrade
280 280
281 281 """
282 282
283 283 from rhodecode.lib.dbmigrate.migrate.versioning import api
284 284 from rhodecode.lib.dbmigrate.migrate.exceptions import \
285 285 DatabaseNotControlledError
286 286
287 287 if 'sqlite' in self.dburi:
288 288 print (
289 289 '********************** WARNING **********************\n'
290 290 'Make sure your version of sqlite is at least 3.7.X. \n'
291 291 'Earlier versions are known to fail on some migrations\n'
292 292 '*****************************************************\n'
293 293 )
294 294 upgrade = ask_ok('You are about to perform database upgrade, make '
295 295 'sure You backed up your database before. '
296 296 'Continue ? [y/n]')
297 297 if not upgrade:
298 298 sys.exit('No upgrade performed')
299 299
300 300 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
301 301 'rhodecode/lib/dbmigrate')
302 302 db_uri = self.dburi
303 303
304 304 try:
305 305 curr_version = api.db_version(db_uri, repository_path)
306 306 msg = ('Found current database under version'
307 307 ' control with version %s' % curr_version)
308 308
309 309 except (RuntimeError, DatabaseNotControlledError):
310 310 curr_version = 1
311 311 msg = ('Current database is not under version control. Setting'
312 312 ' as version %s' % curr_version)
313 313 api.version_control(db_uri, repository_path, curr_version)
314 314
315 315 notify(msg)
316 316
317 317 if curr_version == __dbversion__:
318 318 sys.exit('This database is already at the newest version')
319 319
320 320 # clear cache keys
321 321 log.info("Clearing cache keys now...")
322 322 CacheInvalidation.clear_cache()
323 323
324 324 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
325 325 notify('attempting to do database upgrade from '
326 326 'version %s to version %s' % (curr_version, __dbversion__))
327 327
328 328 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
329 329 _step = None
330 330 for step in upgrade_steps:
331 331 notify('performing upgrade step %s' % step)
332 332 time.sleep(2)
333 333
334 334 api.upgrade(db_uri, repository_path, step)
335 335 notify('schema upgrade for step %s completed' % (step,))
336 336
337 337 fixture = 'step_%s' % step
338 338 notify('performing fixture step %s' % fixture)
339 339 getattr(UpgradeSteps(self), fixture)()
340 340 self.sa.commit()
341 341 notify('fixture %s completed' % (fixture,))
342 342 _step = step
343 343
344 344 notify('upgrade to version %s successful' % _step)
345 345
346 346 def fix_repo_paths(self):
347 347 """
348 348 Fixes a old rhodecode version path into new one without a '*'
349 349 """
350 350
351 351 paths = self.sa.query(RhodeCodeUi)\
352 352 .filter(RhodeCodeUi.ui_key == '/')\
353 353 .scalar()
354 354
355 355 paths.ui_value = paths.ui_value.replace('*', '')
356 356
357 357 try:
358 358 self.sa.add(paths)
359 359 self.sa.commit()
360 360 except Exception:
361 361 self.sa.rollback()
362 362 raise
363 363
364 364 def fix_default_user(self):
365 365 """
366 366 Fixes a old default user with some 'nicer' default values,
367 367 used mostly for anonymous access
368 368 """
369 369 def_user = self.sa.query(User)\
370 370 .filter(User.username == 'default')\
371 371 .one()
372 372
373 373 def_user.name = 'Anonymous'
374 374 def_user.lastname = 'User'
375 375 def_user.email = 'anonymous@rhodecode.org'
376 376
377 377 try:
378 378 self.sa.add(def_user)
379 379 self.sa.commit()
380 380 except Exception:
381 381 self.sa.rollback()
382 382 raise
383 383
384 384 def fix_settings(self):
385 385 """
386 386 Fixes rhodecode settings adds ga_code key for google analytics
387 387 """
388 388
389 389 hgsettings3 = RhodeCodeSetting('ga_code', '')
390 390
391 391 try:
392 392 self.sa.add(hgsettings3)
393 393 self.sa.commit()
394 394 except Exception:
395 395 self.sa.rollback()
396 396 raise
397 397
398 398 def admin_prompt(self, second=False):
399 399 if not self.tests:
400 400 import getpass
401 401
402 402 # defaults
403 403 defaults = self.cli_args
404 404 username = defaults.get('username')
405 405 password = defaults.get('password')
406 406 email = defaults.get('email')
407 407
408 408 def get_password():
409 409 password = getpass.getpass('Specify admin password '
410 410 '(min 6 chars):')
411 411 confirm = getpass.getpass('Confirm password:')
412 412
413 413 if password != confirm:
414 414 log.error('passwords mismatch')
415 415 return False
416 416 if len(password) < 6:
417 417 log.error('password is to short use at least 6 characters')
418 418 return False
419 419
420 420 return password
421 421 if username is None:
422 422 username = raw_input('Specify admin username:')
423 423 if password is None:
424 424 password = get_password()
425 425 if not password:
426 426 #second try
427 427 password = get_password()
428 428 if not password:
429 429 sys.exit()
430 430 if email is None:
431 431 email = raw_input('Specify admin email:')
432 432 self.create_user(username, password, email, True)
433 433 else:
434 434 log.info('creating admin and regular test users')
435 435 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
436 436 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
437 437 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
438 438 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
439 439 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
440 440
441 441 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
442 442 TEST_USER_ADMIN_EMAIL, True)
443 443
444 444 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
445 445 TEST_USER_REGULAR_EMAIL, False)
446 446
447 447 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
448 448 TEST_USER_REGULAR2_EMAIL, False)
449 449
450 450 def create_ui_settings(self):
451 451 """
452 452 Creates ui settings, fills out hooks
453 453 and disables dotencode
454 454 """
455 455
456 456 #HOOKS
457 457 hooks1_key = RhodeCodeUi.HOOK_UPDATE
458 458 hooks1_ = self.sa.query(RhodeCodeUi)\
459 459 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
460 460
461 461 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
462 462 hooks1.ui_section = 'hooks'
463 463 hooks1.ui_key = hooks1_key
464 464 hooks1.ui_value = 'hg update >&2'
465 465 hooks1.ui_active = False
466 466 self.sa.add(hooks1)
467 467
468 468 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
469 469 hooks2_ = self.sa.query(RhodeCodeUi)\
470 470 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
471 471 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
472 472 hooks2.ui_section = 'hooks'
473 473 hooks2.ui_key = hooks2_key
474 474 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
475 475 self.sa.add(hooks2)
476 476
477 477 hooks3 = RhodeCodeUi()
478 478 hooks3.ui_section = 'hooks'
479 479 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
480 480 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
481 481 self.sa.add(hooks3)
482 482
483 483 hooks4 = RhodeCodeUi()
484 484 hooks4.ui_section = 'hooks'
485 485 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
486 486 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
487 487 self.sa.add(hooks4)
488 488
489 489 hooks5 = RhodeCodeUi()
490 490 hooks5.ui_section = 'hooks'
491 491 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
492 492 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
493 493 self.sa.add(hooks5)
494 494
495 495 hooks6 = RhodeCodeUi()
496 496 hooks6.ui_section = 'hooks'
497 497 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
498 498 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
499 499 self.sa.add(hooks6)
500 500
501 501 # enable largefiles
502 502 largefiles = RhodeCodeUi()
503 503 largefiles.ui_section = 'extensions'
504 504 largefiles.ui_key = 'largefiles'
505 505 largefiles.ui_value = ''
506 506 self.sa.add(largefiles)
507 507
508 508 # enable hgsubversion disabled by default
509 509 hgsubversion = RhodeCodeUi()
510 510 hgsubversion.ui_section = 'extensions'
511 511 hgsubversion.ui_key = 'hgsubversion'
512 512 hgsubversion.ui_value = ''
513 513 hgsubversion.ui_active = False
514 514 self.sa.add(hgsubversion)
515 515
516 516 # enable hggit disabled by default
517 517 hggit = RhodeCodeUi()
518 518 hggit.ui_section = 'extensions'
519 519 hggit.ui_key = 'hggit'
520 520 hggit.ui_value = ''
521 521 hggit.ui_active = False
522 522 self.sa.add(hggit)
523 523
524 524 def create_ldap_options(self, skip_existing=False):
525 525 """Creates ldap settings"""
526 526
527 527 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
528 528 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
529 529 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
530 530 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
531 531 ('ldap_filter', ''), ('ldap_search_scope', ''),
532 532 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
533 533 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
534 534
535 535 if skip_existing and RhodeCodeSetting.get_by_name(k) is not None:
536 536 log.debug('Skipping option %s' % k)
537 537 continue
538 538 setting = RhodeCodeSetting(k, v)
539 539 self.sa.add(setting)
540 540
541 541 def create_default_options(self, skip_existing=False):
542 542 """Creates default settings"""
543 543
544 544 for k, v in [
545 545 ('default_repo_enable_locking', False),
546 546 ('default_repo_enable_downloads', False),
547 547 ('default_repo_enable_statistics', False),
548 548 ('default_repo_private', False),
549 549 ('default_repo_type', 'hg')]:
550 550
551 551 if skip_existing and RhodeCodeSetting.get_by_name(k) is not None:
552 552 log.debug('Skipping option %s' % k)
553 553 continue
554 554 setting = RhodeCodeSetting(k, v)
555 555 self.sa.add(setting)
556 556
557 557 def fixup_groups(self):
558 558 def_usr = User.get_default_user()
559 559 for g in RepoGroup.query().all():
560 560 g.group_name = g.get_new_name(g.name)
561 561 self.sa.add(g)
562 562 # get default perm
563 563 default = UserRepoGroupToPerm.query()\
564 564 .filter(UserRepoGroupToPerm.group == g)\
565 565 .filter(UserRepoGroupToPerm.user == def_usr)\
566 566 .scalar()
567 567
568 568 if default is None:
569 569 log.debug('missing default permission for group %s adding' % g)
570 570 perm_obj = ReposGroupModel()._create_default_perms(g)
571 571 self.sa.add(perm_obj)
572 572
573 573 def reset_permissions(self, username):
574 574 """
575 575 Resets permissions to default state, usefull when old systems had
576 576 bad permissions, we must clean them up
577 577
578 578 :param username:
579 579 """
580 580 default_user = User.get_by_username(username)
581 581 if not default_user:
582 582 return
583 583
584 584 u2p = UserToPerm.query()\
585 585 .filter(UserToPerm.user == default_user).all()
586 586 fixed = False
587 587 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
588 588 for p in u2p:
589 589 Session().delete(p)
590 590 fixed = True
591 591 self.populate_default_permissions()
592 592 return fixed
593 593
594 594 def update_repo_info(self):
595 595 RepoModel.update_repoinfo()
596 596
597 597 def config_prompt(self, test_repo_path='', retries=3):
598 598 defaults = self.cli_args
599 599 _path = defaults.get('repos_location')
600 600 if retries == 3:
601 601 log.info('Setting up repositories config')
602 602
603 603 if _path is not None:
604 604 path = _path
605 605 elif not self.tests and not test_repo_path:
606 606 path = raw_input(
607 607 'Enter a valid absolute path to store repositories. '
608 608 'All repositories in that path will be added automatically:'
609 609 )
610 610 else:
611 611 path = test_repo_path
612 612 path_ok = True
613 613
614 614 # check proper dir
615 615 if not os.path.isdir(path):
616 616 path_ok = False
617 log.error('Given path %s is not a valid directory' % path)
617 log.error('Given path %s is not a valid directory' % (path,))
618 618
619 619 elif not os.path.isabs(path):
620 620 path_ok = False
621 log.error('Given path %s is not an absolute path' % path)
621 log.error('Given path %s is not an absolute path' % (path,))
622
623 # check if path is at least readable.
624 if not os.access(path, os.R_OK):
625 path_ok = False
626 log.error('Given path %s is not readable' % (path,))
622 627
623 # check write access
628 # check write access, warn user about non writeable paths
624 629 elif not os.access(path, os.W_OK) and path_ok:
625 path_ok = False
626 log.error('No write permission to given path %s' % path)
630 log.warn('No write permission to given path %s' % (path,))
631 if not ask_ok('Given path %s is not writeable, do you want to '
632 'continue with read only mode ? [y/n]' % (path,)):
633 log.error('Canceled by user')
634 sys.exit(-1)
627 635
628 636 if retries == 0:
629 637 sys.exit('max retries reached')
630 638 if not path_ok:
631 639 retries -= 1
632 640 return self.config_prompt(test_repo_path, retries)
633 641
634 642 real_path = os.path.normpath(os.path.realpath(path))
635 643
636 644 if real_path != os.path.normpath(path):
637 645 if not ask_ok(('Path looks like a symlink, Rhodecode will store '
638 'given path as %s ? [y/n]') % (real_path)):
646 'given path as %s ? [y/n]') % (real_path,)):
639 647 log.error('Canceled by user')
640 648 sys.exit(-1)
641 649
642 650 return real_path
643 651
644 652 def create_settings(self, path):
645 653
646 654 self.create_ui_settings()
647 655
648 656 ui_config = [
649 657 ('web', 'push_ssl', 'false'),
650 658 ('web', 'allow_archive', 'gz zip bz2'),
651 659 ('web', 'allow_push', '*'),
652 660 ('web', 'baseurl', '/'),
653 661 ('paths', '/', path),
654 662 #('phases', 'publish', 'false')
655 663 ]
656 664 for section, key, value in ui_config:
657 665 ui_conf = RhodeCodeUi()
658 666 setattr(ui_conf, 'ui_section', section)
659 667 setattr(ui_conf, 'ui_key', key)
660 668 setattr(ui_conf, 'ui_value', value)
661 669 self.sa.add(ui_conf)
662 670
663 671 settings = [
664 672 ('realm', 'RhodeCode authentication', unicode),
665 673 ('title', 'RhodeCode', unicode),
666 674 ('ga_code', '', unicode),
667 675 ('show_public_icon', True, bool),
668 676 ('show_private_icon', True, bool),
669 677 ('stylify_metatags', False, bool),
670 678 ('dashboard_items', 100, int),
671 679 ('show_version', True, bool)
672 680 ]
673 681 for key, val, type_ in settings:
674 682 sett = RhodeCodeSetting(key, val)
675 683 self.sa.add(sett)
676 684
677 685 self.create_ldap_options()
678 686 self.create_default_options()
679 687
680 688 log.info('created ui config')
681 689
682 690 def create_user(self, username, password, email='', admin=False):
683 691 log.info('creating user %s' % username)
684 692 UserModel().create_or_update(username, password, email,
685 693 firstname='RhodeCode', lastname='Admin',
686 694 active=True, admin=admin)
687 695
688 696 def create_default_user(self):
689 697 log.info('creating default user')
690 698 # create default user for handling default permissions.
691 699 UserModel().create_or_update(username='default',
692 700 password=str(uuid.uuid1())[:8],
693 701 email='anonymous@rhodecode.org',
694 702 firstname='Anonymous', lastname='User')
695 703
696 704 def create_permissions(self):
697 705 """
698 706 Creates all permissions defined in the system
699 707 """
700 708 # module.(access|create|change|delete)_[name]
701 709 # module.(none|read|write|admin)
702 710 log.info('creating permissions')
703 711 PermissionModel(self.sa).create_permissions()
704 712
705 713 def populate_default_permissions(self):
706 714 """
707 715 Populate default permissions. It will create only the default
708 716 permissions that are missing, and not alter already defined ones
709 717 """
710 718 log.info('creating default user permissions')
711 719 PermissionModel(self.sa).create_default_permissions(user=User.DEFAULT_USER)
712 720
713 721 @staticmethod
714 722 def check_waitress():
715 723 """
716 724 Function executed at the end of setup
717 725 """
718 726 if not __py_version__ >= (2, 6):
719 727 notify('Python2.5 detected, please switch '
720 728 'egg:waitress#main -> egg:Paste#http '
721 729 'in your .ini file')
@@ -1,815 +1,817 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 webhelpers.text import collapse, remove_formatting, strip_tags
43 43
44 44 from rhodecode.lib.vcs import get_backend
45 45 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.utils.hgcompat import ui, config
48 48 from rhodecode.lib.vcs.utils.helpers import get_scm
49 49 from rhodecode.lib.vcs.exceptions import VCSError
50 50
51 51 from rhodecode.lib.caching_query import FromCache
52 52
53 53 from rhodecode.model import meta
54 54 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
55 55 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation, UserGroup
56 56 from rhodecode.model.meta import Session
57 57 from rhodecode.model.repos_group import ReposGroupModel
58 58 from rhodecode.lib.utils2 import safe_str, safe_unicode
59 59 from rhodecode.lib.vcs.utils.fakemod import create_module
60 60 from rhodecode.model.users_group import UserGroupModel
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 #==============================================================================
104 104 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
105 105 #==============================================================================
106 106 def get_repo_slug(request):
107 107 _repo = request.environ['pylons.routes_dict'].get('repo_name')
108 108 if _repo:
109 109 _repo = _repo.rstrip('/')
110 110 return _repo
111 111
112 112
113 113 def get_repos_group_slug(request):
114 114 _group = request.environ['pylons.routes_dict'].get('group_name')
115 115 if _group:
116 116 _group = _group.rstrip('/')
117 117 return _group
118 118
119 119
120 120 def get_user_group_slug(request):
121 121 _group = request.environ['pylons.routes_dict'].get('id')
122 122 try:
123 123 _group = UserGroup.get(_group)
124 124 if _group:
125 125 _group = _group.users_group_name
126 126 except Exception:
127 127 log.debug(traceback.format_exc())
128 128 #catch all failures here
129 129 pass
130 130
131 131 return _group
132 132
133 133
134 134 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
135 135 """
136 136 Action logger for various actions made by users
137 137
138 138 :param user: user that made this action, can be a unique username string or
139 139 object containing user_id attribute
140 140 :param action: action to log, should be on of predefined unique actions for
141 141 easy translations
142 142 :param repo: string name of repository or object containing repo_id,
143 143 that action was made on
144 144 :param ipaddr: optional ip address from what the action was made
145 145 :param sa: optional sqlalchemy session
146 146
147 147 """
148 148
149 149 if not sa:
150 150 sa = meta.Session()
151 151
152 152 try:
153 153 if hasattr(user, 'user_id'):
154 154 user_obj = User.get(user.user_id)
155 155 elif isinstance(user, basestring):
156 156 user_obj = User.get_by_username(user)
157 157 else:
158 158 raise Exception('You have to provide a user object or a username')
159 159
160 160 if hasattr(repo, 'repo_id'):
161 161 repo_obj = Repository.get(repo.repo_id)
162 162 repo_name = repo_obj.repo_name
163 163 elif isinstance(repo, basestring):
164 164 repo_name = repo.lstrip('/')
165 165 repo_obj = Repository.get_by_repo_name(repo_name)
166 166 else:
167 167 repo_obj = None
168 168 repo_name = ''
169 169
170 170 user_log = UserLog()
171 171 user_log.user_id = user_obj.user_id
172 172 user_log.username = user_obj.username
173 173 user_log.action = safe_unicode(action)
174 174
175 175 user_log.repository = repo_obj
176 176 user_log.repository_name = repo_name
177 177
178 178 user_log.action_date = datetime.datetime.now()
179 179 user_log.user_ip = ipaddr
180 180 sa.add(user_log)
181 181
182 182 log.info('Logging action:%s on %s by user:%s ip:%s' %
183 183 (action, safe_unicode(repo), user_obj, ipaddr))
184 184 if commit:
185 185 sa.commit()
186 186 except Exception:
187 187 log.error(traceback.format_exc())
188 188 raise
189 189
190 190
191 191 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
192 192 """
193 193 Scans given path for repos and return (name,(type,path)) tuple
194 194
195 195 :param path: path to scan for repositories
196 196 :param recursive: recursive search and return names with subdirs in front
197 197 """
198 198
199 199 # remove ending slash for better results
200 200 path = path.rstrip(os.sep)
201 201 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
202 202
203 203 def _get_repos(p):
204 if not os.access(p, os.R_OK) or not os.access(p, os.X_OK):
205 log.warn('ignoring repo path without access: %s', p)
206 return
204 207 if not os.access(p, os.W_OK):
205 log.warn('ignoring repo path without write access: %s', p)
206 return
208 log.warn('repo path without write access: %s', p)
207 209 for dirpath in os.listdir(p):
208 210 if os.path.isfile(os.path.join(p, dirpath)):
209 211 continue
210 212 cur_path = os.path.join(p, dirpath)
211 213
212 214 # skip removed repos
213 215 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
214 216 continue
215 217
216 218 #skip .<somethin> dirs
217 219 if dirpath.startswith('.'):
218 220 continue
219 221
220 222 try:
221 223 scm_info = get_scm(cur_path)
222 224 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
223 225 except VCSError:
224 226 if not recursive:
225 227 continue
226 228 #check if this dir containts other repos for recursive scan
227 229 rec_path = os.path.join(p, dirpath)
228 230 if os.path.isdir(rec_path):
229 231 for inner_scm in _get_repos(rec_path):
230 232 yield inner_scm
231 233
232 234 return _get_repos(path)
233 235
234 236
235 237 def is_valid_repo(repo_name, base_path, scm=None):
236 238 """
237 239 Returns True if given path is a valid repository False otherwise.
238 240 If scm param is given also compare if given scm is the same as expected
239 241 from scm parameter
240 242
241 243 :param repo_name:
242 244 :param base_path:
243 245 :param scm:
244 246
245 247 :return True: if given path is a valid repository
246 248 """
247 249 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
248 250
249 251 try:
250 252 scm_ = get_scm(full_path)
251 253 if scm:
252 254 return scm_[0] == scm
253 255 return True
254 256 except VCSError:
255 257 return False
256 258
257 259
258 260 def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
259 261 """
260 262 Returns True if given path is a repository group False otherwise
261 263
262 264 :param repo_name:
263 265 :param base_path:
264 266 """
265 267 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
266 268
267 269 # check if it's not a repo
268 270 if is_valid_repo(repos_group_name, base_path):
269 271 return False
270 272
271 273 try:
272 274 # we need to check bare git repos at higher level
273 275 # since we might match branches/hooks/info/objects or possible
274 276 # other things inside bare git repo
275 277 get_scm(os.path.dirname(full_path))
276 278 return False
277 279 except VCSError:
278 280 pass
279 281
280 282 # check if it's a valid path
281 283 if skip_path_check or os.path.isdir(full_path):
282 284 return True
283 285
284 286 return False
285 287
286 288
287 289 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
288 290 while True:
289 291 ok = raw_input(prompt)
290 292 if ok in ('y', 'ye', 'yes'):
291 293 return True
292 294 if ok in ('n', 'no', 'nop', 'nope'):
293 295 return False
294 296 retries = retries - 1
295 297 if retries < 0:
296 298 raise IOError
297 299 print complaint
298 300
299 301 #propagated from mercurial documentation
300 302 ui_sections = ['alias', 'auth',
301 303 'decode/encode', 'defaults',
302 304 'diff', 'email',
303 305 'extensions', 'format',
304 306 'merge-patterns', 'merge-tools',
305 307 'hooks', 'http_proxy',
306 308 'smtp', 'patch',
307 309 'paths', 'profiling',
308 310 'server', 'trusted',
309 311 'ui', 'web', ]
310 312
311 313
312 314 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
313 315 """
314 316 A function that will read python rc files or database
315 317 and make an mercurial ui object from read options
316 318
317 319 :param path: path to mercurial config file
318 320 :param checkpaths: check the path
319 321 :param read_from: read from 'file' or 'db'
320 322 """
321 323
322 324 baseui = ui.ui()
323 325
324 326 # clean the baseui object
325 327 baseui._ocfg = config.config()
326 328 baseui._ucfg = config.config()
327 329 baseui._tcfg = config.config()
328 330
329 331 if read_from == 'file':
330 332 if not os.path.isfile(path):
331 333 log.debug('hgrc file is not present at %s, skipping...' % path)
332 334 return False
333 335 log.debug('reading hgrc from %s' % path)
334 336 cfg = config.config()
335 337 cfg.read(path)
336 338 for section in ui_sections:
337 339 for k, v in cfg.items(section):
338 340 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
339 341 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
340 342
341 343 elif read_from == 'db':
342 344 sa = meta.Session()
343 345 ret = sa.query(RhodeCodeUi)\
344 346 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
345 347 .all()
346 348
347 349 hg_ui = ret
348 350 for ui_ in hg_ui:
349 351 if ui_.ui_active:
350 352 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
351 353 ui_.ui_key, ui_.ui_value)
352 354 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
353 355 safe_str(ui_.ui_value))
354 356 if ui_.ui_key == 'push_ssl':
355 357 # force set push_ssl requirement to False, rhodecode
356 358 # handles that
357 359 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
358 360 False)
359 361 if clear_session:
360 362 meta.Session.remove()
361 363 return baseui
362 364
363 365
364 366 def set_rhodecode_config(config):
365 367 """
366 368 Updates pylons config with new settings from database
367 369
368 370 :param config:
369 371 """
370 372 hgsettings = RhodeCodeSetting.get_app_settings()
371 373
372 374 for k, v in hgsettings.items():
373 375 config[k] = v
374 376
375 377
376 378 def set_vcs_config(config):
377 379 """
378 380 Patch VCS config with some RhodeCode specific stuff
379 381
380 382 :param config: rhodecode.CONFIG
381 383 """
382 384 import rhodecode
383 385 from rhodecode.lib.vcs import conf
384 386 from rhodecode.lib.utils2 import aslist
385 387 conf.settings.BACKENDS = {
386 388 'hg': 'rhodecode.lib.vcs.backends.hg.MercurialRepository',
387 389 'git': 'rhodecode.lib.vcs.backends.git.GitRepository',
388 390 }
389 391
390 392 conf.settings.GIT_EXECUTABLE_PATH = config.get('git_path', 'git')
391 393 conf.settings.GIT_REV_FILTER = config.get('git_rev_filter', '--all').strip()
392 394 conf.settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
393 395 'utf8'), sep=',')
394 396
395 397
396 398 def map_groups(path):
397 399 """
398 400 Given a full path to a repository, create all nested groups that this
399 401 repo is inside. This function creates parent-child relationships between
400 402 groups and creates default perms for all new groups.
401 403
402 404 :param paths: full path to repository
403 405 """
404 406 sa = meta.Session()
405 407 groups = path.split(Repository.url_sep())
406 408 parent = None
407 409 group = None
408 410
409 411 # last element is repo in nested groups structure
410 412 groups = groups[:-1]
411 413 rgm = ReposGroupModel(sa)
412 414 owner = User.get_first_admin()
413 415 for lvl, group_name in enumerate(groups):
414 416 group_name = '/'.join(groups[:lvl] + [group_name])
415 417 group = RepoGroup.get_by_group_name(group_name)
416 418 desc = '%s group' % group_name
417 419
418 420 # skip folders that are now removed repos
419 421 if REMOVED_REPO_PAT.match(group_name):
420 422 break
421 423
422 424 if group is None:
423 425 log.debug('creating group level: %s group_name: %s'
424 426 % (lvl, group_name))
425 427 group = RepoGroup(group_name, parent)
426 428 group.group_description = desc
427 429 group.user = owner
428 430 sa.add(group)
429 431 perm_obj = rgm._create_default_perms(group)
430 432 sa.add(perm_obj)
431 433 sa.flush()
432 434
433 435 parent = group
434 436 return group
435 437
436 438
437 439 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
438 440 install_git_hook=False):
439 441 """
440 442 maps all repos given in initial_repo_list, non existing repositories
441 443 are created, if remove_obsolete is True it also check for db entries
442 444 that are not in initial_repo_list and removes them.
443 445
444 446 :param initial_repo_list: list of repositories found by scanning methods
445 447 :param remove_obsolete: check for obsolete entries in database
446 448 :param install_git_hook: if this is True, also check and install githook
447 449 for a repo if missing
448 450 """
449 451 from rhodecode.model.repo import RepoModel
450 452 from rhodecode.model.scm import ScmModel
451 453 sa = meta.Session()
452 454 rm = RepoModel()
453 455 user = User.get_first_admin()
454 456 added = []
455 457
456 458 ##creation defaults
457 459 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
458 460 enable_statistics = defs.get('repo_enable_statistics')
459 461 enable_locking = defs.get('repo_enable_locking')
460 462 enable_downloads = defs.get('repo_enable_downloads')
461 463 private = defs.get('repo_private')
462 464
463 465 for name, repo in initial_repo_list.items():
464 466 group = map_groups(name)
465 467 db_repo = rm.get_by_repo_name(name)
466 468 # found repo that is on filesystem not in RhodeCode database
467 469 if not db_repo:
468 470 log.info('repository %s not found, creating now' % name)
469 471 added.append(name)
470 472 desc = (repo.description
471 473 if repo.description != 'unknown'
472 474 else '%s repository' % name)
473 475
474 476 new_repo = rm.create_repo(
475 477 repo_name=name,
476 478 repo_type=repo.alias,
477 479 description=desc,
478 480 repos_group=getattr(group, 'group_id', None),
479 481 owner=user,
480 482 just_db=True,
481 483 enable_locking=enable_locking,
482 484 enable_downloads=enable_downloads,
483 485 enable_statistics=enable_statistics,
484 486 private=private
485 487 )
486 488 # we added that repo just now, and make sure it has githook
487 489 # installed
488 490 if new_repo.repo_type == 'git':
489 491 ScmModel().install_git_hook(new_repo.scm_instance)
490 492 new_repo.update_changeset_cache()
491 493 elif install_git_hook:
492 494 if db_repo.repo_type == 'git':
493 495 ScmModel().install_git_hook(db_repo.scm_instance)
494 496
495 497 sa.commit()
496 498 removed = []
497 499 if remove_obsolete:
498 500 # remove from database those repositories that are not in the filesystem
499 501 for repo in sa.query(Repository).all():
500 502 if repo.repo_name not in initial_repo_list.keys():
501 503 log.debug("Removing non-existing repository found in db `%s`" %
502 504 repo.repo_name)
503 505 try:
504 506 removed.append(repo.repo_name)
505 507 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
506 508 sa.commit()
507 509 except Exception:
508 510 #don't hold further removals on error
509 511 log.error(traceback.format_exc())
510 512 sa.rollback()
511 513 return added, removed
512 514
513 515
514 516 # set cache regions for beaker so celery can utilise it
515 517 def add_cache(settings):
516 518 cache_settings = {'regions': None}
517 519 for key in settings.keys():
518 520 for prefix in ['beaker.cache.', 'cache.']:
519 521 if key.startswith(prefix):
520 522 name = key.split(prefix)[1].strip()
521 523 cache_settings[name] = settings[key].strip()
522 524 if cache_settings['regions']:
523 525 for region in cache_settings['regions'].split(','):
524 526 region = region.strip()
525 527 region_settings = {}
526 528 for key, value in cache_settings.items():
527 529 if key.startswith(region):
528 530 region_settings[key.split('.')[1]] = value
529 531 region_settings['expire'] = int(region_settings.get('expire',
530 532 60))
531 533 region_settings.setdefault('lock_dir',
532 534 cache_settings.get('lock_dir'))
533 535 region_settings.setdefault('data_dir',
534 536 cache_settings.get('data_dir'))
535 537
536 538 if 'type' not in region_settings:
537 539 region_settings['type'] = cache_settings.get('type',
538 540 'memory')
539 541 beaker.cache.cache_regions[region] = region_settings
540 542
541 543
542 544 def load_rcextensions(root_path):
543 545 import rhodecode
544 546 from rhodecode.config import conf
545 547
546 548 path = os.path.join(root_path, 'rcextensions', '__init__.py')
547 549 if os.path.isfile(path):
548 550 rcext = create_module('rc', path)
549 551 EXT = rhodecode.EXTENSIONS = rcext
550 552 log.debug('Found rcextensions now loading %s...' % rcext)
551 553
552 554 # Additional mappings that are not present in the pygments lexers
553 555 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
554 556
555 557 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
556 558
557 559 if getattr(EXT, 'INDEX_EXTENSIONS', []):
558 560 log.debug('settings custom INDEX_EXTENSIONS')
559 561 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
560 562
561 563 #ADDITIONAL MAPPINGS
562 564 log.debug('adding extra into INDEX_EXTENSIONS')
563 565 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
564 566
565 567 # auto check if the module is not missing any data, set to default if is
566 568 # this will help autoupdate new feature of rcext module
567 569 from rhodecode.config import rcextensions
568 570 for k in dir(rcextensions):
569 571 if not k.startswith('_') and not hasattr(EXT, k):
570 572 setattr(EXT, k, getattr(rcextensions, k))
571 573
572 574
573 575 def get_custom_lexer(extension):
574 576 """
575 577 returns a custom lexer if it's defined in rcextensions module, or None
576 578 if there's no custom lexer defined
577 579 """
578 580 import rhodecode
579 581 from pygments import lexers
580 582 #check if we didn't define this extension as other lexer
581 583 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
582 584 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
583 585 return lexers.get_lexer_by_name(_lexer_name)
584 586
585 587
586 588 #==============================================================================
587 589 # TEST FUNCTIONS AND CREATORS
588 590 #==============================================================================
589 591 def create_test_index(repo_location, config, full_index):
590 592 """
591 593 Makes default test index
592 594
593 595 :param config: test config
594 596 :param full_index:
595 597 """
596 598
597 599 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
598 600 from rhodecode.lib.pidlock import DaemonLock, LockHeld
599 601
600 602 repo_location = repo_location
601 603
602 604 index_location = os.path.join(config['app_conf']['index_dir'])
603 605 if not os.path.exists(index_location):
604 606 os.makedirs(index_location)
605 607
606 608 try:
607 609 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
608 610 WhooshIndexingDaemon(index_location=index_location,
609 611 repo_location=repo_location)\
610 612 .run(full_index=full_index)
611 613 l.release()
612 614 except LockHeld:
613 615 pass
614 616
615 617
616 618 def create_test_env(repos_test_path, config):
617 619 """
618 620 Makes a fresh database and
619 621 install test repository into tmp dir
620 622 """
621 623 from rhodecode.lib.db_manage import DbManage
622 624 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
623 625
624 626 # PART ONE create db
625 627 dbconf = config['sqlalchemy.db1.url']
626 628 log.debug('making test db %s' % dbconf)
627 629
628 630 # create test dir if it doesn't exist
629 631 if not os.path.isdir(repos_test_path):
630 632 log.debug('Creating testdir %s' % repos_test_path)
631 633 os.makedirs(repos_test_path)
632 634
633 635 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
634 636 tests=True)
635 637 dbmanage.create_tables(override=True)
636 638 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
637 639 dbmanage.create_default_user()
638 640 dbmanage.admin_prompt()
639 641 dbmanage.create_permissions()
640 642 dbmanage.populate_default_permissions()
641 643 Session().commit()
642 644 # PART TWO make test repo
643 645 log.debug('making test vcs repositories')
644 646
645 647 idx_path = config['app_conf']['index_dir']
646 648 data_path = config['app_conf']['cache_dir']
647 649
648 650 #clean index and data
649 651 if idx_path and os.path.exists(idx_path):
650 652 log.debug('remove %s' % idx_path)
651 653 shutil.rmtree(idx_path)
652 654
653 655 if data_path and os.path.exists(data_path):
654 656 log.debug('remove %s' % data_path)
655 657 shutil.rmtree(data_path)
656 658
657 659 #CREATE DEFAULT TEST REPOS
658 660 cur_dir = dn(dn(abspath(__file__)))
659 661 tar = tarfile.open(jn(cur_dir, 'tests', 'fixtures', "vcs_test_hg.tar.gz"))
660 662 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
661 663 tar.close()
662 664
663 665 cur_dir = dn(dn(abspath(__file__)))
664 666 tar = tarfile.open(jn(cur_dir, 'tests', 'fixtures', "vcs_test_git.tar.gz"))
665 667 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
666 668 tar.close()
667 669
668 670 #LOAD VCS test stuff
669 671 from rhodecode.tests.vcs import setup_package
670 672 setup_package()
671 673
672 674
673 675 #==============================================================================
674 676 # PASTER COMMANDS
675 677 #==============================================================================
676 678 class BasePasterCommand(Command):
677 679 """
678 680 Abstract Base Class for paster commands.
679 681
680 682 The celery commands are somewhat aggressive about loading
681 683 celery.conf, and since our module sets the `CELERY_LOADER`
682 684 environment variable to our loader, we have to bootstrap a bit and
683 685 make sure we've had a chance to load the pylons config off of the
684 686 command line, otherwise everything fails.
685 687 """
686 688 min_args = 1
687 689 min_args_error = "Please provide a paster config file as an argument."
688 690 takes_config_file = 1
689 691 requires_config_file = True
690 692
691 693 def notify_msg(self, msg, log=False):
692 694 """Make a notification to user, additionally if logger is passed
693 695 it logs this action using given logger
694 696
695 697 :param msg: message that will be printed to user
696 698 :param log: logging instance, to use to additionally log this message
697 699
698 700 """
699 701 if log and isinstance(log, logging):
700 702 log(msg)
701 703
702 704 def run(self, args):
703 705 """
704 706 Overrides Command.run
705 707
706 708 Checks for a config file argument and loads it.
707 709 """
708 710 if len(args) < self.min_args:
709 711 raise BadCommand(
710 712 self.min_args_error % {'min_args': self.min_args,
711 713 'actual_args': len(args)})
712 714
713 715 # Decrement because we're going to lob off the first argument.
714 716 # @@ This is hacky
715 717 self.min_args -= 1
716 718 self.bootstrap_config(args[0])
717 719 self.update_parser()
718 720 return super(BasePasterCommand, self).run(args[1:])
719 721
720 722 def update_parser(self):
721 723 """
722 724 Abstract method. Allows for the class's parser to be updated
723 725 before the superclass's `run` method is called. Necessary to
724 726 allow options/arguments to be passed through to the underlying
725 727 celery command.
726 728 """
727 729 raise NotImplementedError("Abstract Method.")
728 730
729 731 def bootstrap_config(self, conf):
730 732 """
731 733 Loads the pylons configuration.
732 734 """
733 735 from pylons import config as pylonsconfig
734 736
735 737 self.path_to_ini_file = os.path.realpath(conf)
736 738 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
737 739 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
738 740
739 741 def _init_session(self):
740 742 """
741 743 Inits SqlAlchemy Session
742 744 """
743 745 logging.config.fileConfig(self.path_to_ini_file)
744 746 from pylons import config
745 747 from rhodecode.model import init_model
746 748 from rhodecode.lib.utils2 import engine_from_config
747 749
748 750 #get to remove repos !!
749 751 add_cache(config)
750 752 engine = engine_from_config(config, 'sqlalchemy.db1.')
751 753 init_model(engine)
752 754
753 755
754 756 def check_git_version():
755 757 """
756 758 Checks what version of git is installed in system, and issues a warning
757 759 if it's too old for RhodeCode to properly work.
758 760 """
759 761 from rhodecode import BACKENDS
760 762 from rhodecode.lib.vcs.backends.git.repository import GitRepository
761 763 from rhodecode.lib.vcs.conf import settings
762 764 from distutils.version import StrictVersion
763 765
764 766 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
765 767 _safe=True)
766 768
767 769 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
768 770 if len(ver.split('.')) > 3:
769 771 #StrictVersion needs to be only 3 element type
770 772 ver = '.'.join(ver.split('.')[:3])
771 773 try:
772 774 _ver = StrictVersion(ver)
773 775 except Exception:
774 776 _ver = StrictVersion('0.0.0')
775 777 stderr = traceback.format_exc()
776 778
777 779 req_ver = '1.7.4'
778 780 to_old_git = False
779 781 if _ver < StrictVersion(req_ver):
780 782 to_old_git = True
781 783
782 784 if 'git' in BACKENDS:
783 785 log.debug('GIT executable: "%s" version detected: %s'
784 786 % (settings.GIT_EXECUTABLE_PATH, stdout))
785 787 if stderr:
786 788 log.warning('Unable to detect git version, org error was: %r' % stderr)
787 789 elif to_old_git:
788 790 log.warning('RhodeCode detected git version %s, which is too old '
789 791 'for the system to function properly. Make sure '
790 792 'its version is at least %s' % (ver, req_ver))
791 793 return _ver
792 794
793 795
794 796 @decorator.decorator
795 797 def jsonify(func, *args, **kwargs):
796 798 """Action decorator that formats output for JSON
797 799
798 800 Given a function that will return content, this decorator will turn
799 801 the result into JSON, with a content-type of 'application/json' and
800 802 output it.
801 803
802 804 """
803 805 from pylons.decorators.util import get_pylons
804 806 from rhodecode.lib.compat import json
805 807 pylons = get_pylons(args)
806 808 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
807 809 data = func(*args, **kwargs)
808 810 if isinstance(data, (list, tuple)):
809 811 msg = "JSON responses with Array envelopes are susceptible to " \
810 812 "cross-site data leak attacks, see " \
811 813 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
812 814 warnings.warn(msg, Warning, 2)
813 815 log.warning(msg)
814 816 log.debug("Returning JSON wrapped action output")
815 817 return json.dumps(data, encoding='utf-8')
@@ -1,752 +1,755 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.scm
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Scm model for RhodeCode
7 7
8 8 :created_on: Apr 9, 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 from __future__ import with_statement
26 26 import os
27 27 import re
28 28 import time
29 29 import traceback
30 30 import logging
31 31 import cStringIO
32 32 import pkg_resources
33 33 from os.path import join as jn
34 34
35 35 from sqlalchemy import func
36 36 from pylons.i18n.translation import _
37 37
38 38 import rhodecode
39 39 from rhodecode.lib.vcs import get_backend
40 40 from rhodecode.lib.vcs.exceptions import RepositoryError
41 41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
42 42 from rhodecode.lib.vcs.nodes import FileNode
43 43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44 44
45 45 from rhodecode import BACKENDS
46 46 from rhodecode.lib import helpers as h
47 47 from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url,\
48 48 _set_extras
49 49 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny,\
50 50 HasUserGroupPermissionAny
51 51 from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
52 52 action_logger
53 53 from rhodecode.model import BaseModel
54 54 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
55 55 UserFollowing, UserLog, User, RepoGroup, PullRequest
56 56 from rhodecode.lib.hooks import log_push_action
57 57 from rhodecode.lib.exceptions import NonRelativePathError
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 class UserTemp(object):
63 63 def __init__(self, user_id):
64 64 self.user_id = user_id
65 65
66 66 def __repr__(self):
67 67 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
68 68
69 69
70 70 class RepoTemp(object):
71 71 def __init__(self, repo_id):
72 72 self.repo_id = repo_id
73 73
74 74 def __repr__(self):
75 75 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
76 76
77 77
78 78 class CachedRepoList(object):
79 79 """
80 80 Cached repo list, uses in-memory cache after initialization, that is
81 81 super fast
82 82 """
83 83
84 84 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
85 85 self.db_repo_list = db_repo_list
86 86 self.repos_path = repos_path
87 87 self.order_by = order_by
88 88 self.reversed = (order_by or '').startswith('-')
89 89 if not perm_set:
90 90 perm_set = ['repository.read', 'repository.write',
91 91 'repository.admin']
92 92 self.perm_set = perm_set
93 93
94 94 def __len__(self):
95 95 return len(self.db_repo_list)
96 96
97 97 def __repr__(self):
98 98 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
99 99
100 100 def __iter__(self):
101 101 # pre-propagated valid_cache_keys to save executing select statements
102 102 # for each repo
103 103 valid_cache_keys = CacheInvalidation.get_valid_cache_keys()
104 104
105 105 for dbr in self.db_repo_list:
106 106 scmr = dbr.scm_instance_cached(valid_cache_keys)
107 107 # check permission at this level
108 108 if not HasRepoPermissionAny(
109 109 *self.perm_set)(dbr.repo_name, 'get repo check'):
110 110 continue
111 111
112 112 try:
113 113 last_change = scmr.last_change
114 114 tip = h.get_changeset_safe(scmr, 'tip')
115 115 except Exception:
116 116 log.error(
117 117 '%s this repository is present in database but it '
118 118 'cannot be created as an scm instance, org_exc:%s'
119 119 % (dbr.repo_name, traceback.format_exc())
120 120 )
121 121 continue
122 122
123 123 tmp_d = {}
124 124 tmp_d['name'] = dbr.repo_name
125 125 tmp_d['name_sort'] = tmp_d['name'].lower()
126 126 tmp_d['raw_name'] = tmp_d['name'].lower()
127 127 tmp_d['description'] = dbr.description
128 128 tmp_d['description_sort'] = tmp_d['description'].lower()
129 129 tmp_d['last_change'] = last_change
130 130 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
131 131 tmp_d['tip'] = tip.raw_id
132 132 tmp_d['tip_sort'] = tip.revision
133 133 tmp_d['rev'] = tip.revision
134 134 tmp_d['contact'] = dbr.user.full_contact
135 135 tmp_d['contact_sort'] = tmp_d['contact']
136 136 tmp_d['owner_sort'] = tmp_d['contact']
137 137 tmp_d['repo_archives'] = list(scmr._get_archives())
138 138 tmp_d['last_msg'] = tip.message
139 139 tmp_d['author'] = tip.author
140 140 tmp_d['dbrepo'] = dbr.get_dict()
141 141 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
142 142 yield tmp_d
143 143
144 144
145 145 class SimpleCachedRepoList(CachedRepoList):
146 146 """
147 147 Lighter version of CachedRepoList without the scm initialisation
148 148 """
149 149
150 150 def __iter__(self):
151 151 for dbr in self.db_repo_list:
152 152 # check permission at this level
153 153 if not HasRepoPermissionAny(
154 154 *self.perm_set)(dbr.repo_name, 'get repo check'):
155 155 continue
156 156
157 157 tmp_d = {}
158 158 tmp_d['name'] = dbr.repo_name
159 159 tmp_d['name_sort'] = tmp_d['name'].lower()
160 160 tmp_d['raw_name'] = tmp_d['name'].lower()
161 161 tmp_d['description'] = dbr.description
162 162 tmp_d['description_sort'] = tmp_d['description'].lower()
163 163 tmp_d['dbrepo'] = dbr.get_dict()
164 164 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
165 165 yield tmp_d
166 166
167 167
168 168 class _PermCheckIterator(object):
169 169 def __init__(self, obj_list, obj_attr, perm_set, perm_checker):
170 170 """
171 171 Creates iterator from given list of objects, additionally
172 172 checking permission for them from perm_set var
173 173
174 174 :param obj_list: list of db objects
175 175 :param obj_attr: attribute of object to pass into perm_checker
176 176 :param perm_set: list of permissions to check
177 177 :param perm_checker: callable to check permissions against
178 178 """
179 179 self.obj_list = obj_list
180 180 self.obj_attr = obj_attr
181 181 self.perm_set = perm_set
182 182 self.perm_checker = perm_checker
183 183
184 184 def __len__(self):
185 185 return len(self.obj_list)
186 186
187 187 def __repr__(self):
188 188 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
189 189
190 190 def __iter__(self):
191 191 for db_obj in self.obj_list:
192 192 # check permission at this level
193 193 name = getattr(db_obj, self.obj_attr, None)
194 194 if not self.perm_checker(*self.perm_set)(name, self.__class__.__name__):
195 195 continue
196 196
197 197 yield db_obj
198 198
199 199
200 200 class RepoList(_PermCheckIterator):
201 201
202 202 def __init__(self, db_repo_list, perm_set=None):
203 203 if not perm_set:
204 204 perm_set = ['repository.read', 'repository.write', 'repository.admin']
205 205
206 206 super(RepoList, self).__init__(obj_list=db_repo_list,
207 207 obj_attr='repo_name', perm_set=perm_set,
208 208 perm_checker=HasRepoPermissionAny)
209 209
210 210
211 211 class RepoGroupList(_PermCheckIterator):
212 212
213 213 def __init__(self, db_repo_group_list, perm_set=None):
214 214 if not perm_set:
215 215 perm_set = ['group.read', 'group.write', 'group.admin']
216 216
217 217 super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
218 218 obj_attr='group_name', perm_set=perm_set,
219 219 perm_checker=HasReposGroupPermissionAny)
220 220
221 221
222 222 class UserGroupList(_PermCheckIterator):
223 223
224 224 def __init__(self, db_user_group_list, perm_set=None):
225 225 if not perm_set:
226 226 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
227 227
228 228 super(UserGroupList, self).__init__(obj_list=db_user_group_list,
229 229 obj_attr='users_group_name', perm_set=perm_set,
230 230 perm_checker=HasUserGroupPermissionAny)
231 231
232 232
233 233 class ScmModel(BaseModel):
234 234 """
235 235 Generic Scm Model
236 236 """
237 237
238 238 def __get_repo(self, instance):
239 239 cls = Repository
240 240 if isinstance(instance, cls):
241 241 return instance
242 242 elif isinstance(instance, int) or safe_str(instance).isdigit():
243 243 return cls.get(instance)
244 244 elif isinstance(instance, basestring):
245 245 return cls.get_by_repo_name(instance)
246 246 elif instance:
247 247 raise Exception('given object must be int, basestr or Instance'
248 248 ' of %s got %s' % (type(cls), type(instance)))
249 249
250 250 @LazyProperty
251 251 def repos_path(self):
252 252 """
253 253 Get's the repositories root path from database
254 254 """
255 255
256 256 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
257 257
258 258 return q.ui_value
259 259
260 260 def repo_scan(self, repos_path=None):
261 261 """
262 262 Listing of repositories in given path. This path should not be a
263 263 repository itself. Return a dictionary of repository objects
264 264
265 265 :param repos_path: path to directory containing repositories
266 266 """
267 267
268 268 if repos_path is None:
269 269 repos_path = self.repos_path
270 270
271 271 log.info('scanning for repositories in %s' % repos_path)
272 272
273 273 baseui = make_ui('db')
274 274 repos = {}
275 275
276 276 for name, path in get_filesystem_repos(repos_path, recursive=True):
277 277 # name need to be decomposed and put back together using the /
278 278 # since this is internal storage separator for rhodecode
279 279 name = Repository.normalize_repo_name(name)
280 280
281 281 try:
282 282 if name in repos:
283 283 raise RepositoryError('Duplicate repository name %s '
284 284 'found in %s' % (name, path))
285 285 else:
286 286
287 287 klass = get_backend(path[0])
288 288
289 289 if path[0] == 'hg' and path[0] in BACKENDS.keys():
290 290 repos[name] = klass(safe_str(path[1]), baseui=baseui)
291 291
292 292 if path[0] == 'git' and path[0] in BACKENDS.keys():
293 293 repos[name] = klass(path[1])
294 294 except OSError:
295 295 continue
296 296 log.debug('found %s paths with repositories' % (len(repos)))
297 297 return repos
298 298
299 299 def get_repos(self, all_repos=None, sort_key=None, simple=False):
300 300 """
301 301 Get all repos from db and for each repo create it's
302 302 backend instance and fill that backed with information from database
303 303
304 304 :param all_repos: list of repository names as strings
305 305 give specific repositories list, good for filtering
306 306
307 307 :param sort_key: initial sorting of repos
308 308 :param simple: use SimpleCachedList - one without the SCM info
309 309 """
310 310 if all_repos is None:
311 311 all_repos = self.sa.query(Repository)\
312 312 .filter(Repository.group_id == None)\
313 313 .order_by(func.lower(Repository.repo_name)).all()
314 314 if simple:
315 315 repo_iter = SimpleCachedRepoList(all_repos,
316 316 repos_path=self.repos_path,
317 317 order_by=sort_key)
318 318 else:
319 319 repo_iter = CachedRepoList(all_repos,
320 320 repos_path=self.repos_path,
321 321 order_by=sort_key)
322 322
323 323 return repo_iter
324 324
325 325 def get_repos_groups(self, all_groups=None):
326 326 if all_groups is None:
327 327 all_groups = RepoGroup.query()\
328 328 .filter(RepoGroup.group_parent_id == None).all()
329 329 return [x for x in RepoGroupList(all_groups)]
330 330
331 331 def mark_for_invalidation(self, repo_name):
332 332 """
333 333 Mark caches of this repo invalid in the database.
334 334
335 335 :param repo_name: the repo for which caches should be marked invalid
336 336 """
337 337 CacheInvalidation.set_invalidate(repo_name)
338 338 repo = Repository.get_by_repo_name(repo_name)
339 339 if repo:
340 340 repo.update_changeset_cache()
341 341
342 342 def toggle_following_repo(self, follow_repo_id, user_id):
343 343
344 344 f = self.sa.query(UserFollowing)\
345 345 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
346 346 .filter(UserFollowing.user_id == user_id).scalar()
347 347
348 348 if f is not None:
349 349 try:
350 350 self.sa.delete(f)
351 351 action_logger(UserTemp(user_id),
352 352 'stopped_following_repo',
353 353 RepoTemp(follow_repo_id))
354 354 return
355 355 except Exception:
356 356 log.error(traceback.format_exc())
357 357 raise
358 358
359 359 try:
360 360 f = UserFollowing()
361 361 f.user_id = user_id
362 362 f.follows_repo_id = follow_repo_id
363 363 self.sa.add(f)
364 364
365 365 action_logger(UserTemp(user_id),
366 366 'started_following_repo',
367 367 RepoTemp(follow_repo_id))
368 368 except Exception:
369 369 log.error(traceback.format_exc())
370 370 raise
371 371
372 372 def toggle_following_user(self, follow_user_id, user_id):
373 373 f = self.sa.query(UserFollowing)\
374 374 .filter(UserFollowing.follows_user_id == follow_user_id)\
375 375 .filter(UserFollowing.user_id == user_id).scalar()
376 376
377 377 if f is not None:
378 378 try:
379 379 self.sa.delete(f)
380 380 return
381 381 except Exception:
382 382 log.error(traceback.format_exc())
383 383 raise
384 384
385 385 try:
386 386 f = UserFollowing()
387 387 f.user_id = user_id
388 388 f.follows_user_id = follow_user_id
389 389 self.sa.add(f)
390 390 except Exception:
391 391 log.error(traceback.format_exc())
392 392 raise
393 393
394 394 def is_following_repo(self, repo_name, user_id, cache=False):
395 395 r = self.sa.query(Repository)\
396 396 .filter(Repository.repo_name == repo_name).scalar()
397 397
398 398 f = self.sa.query(UserFollowing)\
399 399 .filter(UserFollowing.follows_repository == r)\
400 400 .filter(UserFollowing.user_id == user_id).scalar()
401 401
402 402 return f is not None
403 403
404 404 def is_following_user(self, username, user_id, cache=False):
405 405 u = User.get_by_username(username)
406 406
407 407 f = self.sa.query(UserFollowing)\
408 408 .filter(UserFollowing.follows_user == u)\
409 409 .filter(UserFollowing.user_id == user_id).scalar()
410 410
411 411 return f is not None
412 412
413 413 def get_followers(self, repo):
414 414 repo = self._get_repo(repo)
415 415
416 416 return self.sa.query(UserFollowing)\
417 417 .filter(UserFollowing.follows_repository == repo).count()
418 418
419 419 def get_forks(self, repo):
420 420 repo = self._get_repo(repo)
421 421 return self.sa.query(Repository)\
422 422 .filter(Repository.fork == repo).count()
423 423
424 424 def get_pull_requests(self, repo):
425 425 repo = self._get_repo(repo)
426 426 return self.sa.query(PullRequest)\
427 427 .filter(PullRequest.other_repo == repo)\
428 428 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
429 429
430 430 def mark_as_fork(self, repo, fork, user):
431 431 repo = self.__get_repo(repo)
432 432 fork = self.__get_repo(fork)
433 433 if fork and repo.repo_id == fork.repo_id:
434 434 raise Exception("Cannot set repository as fork of itself")
435 435 repo.fork = fork
436 436 self.sa.add(repo)
437 437 return repo
438 438
439 439 def _handle_rc_scm_extras(self, username, repo_name, repo_alias,
440 440 action=None):
441 441 from rhodecode import CONFIG
442 442 from rhodecode.lib.base import _get_ip_addr
443 443 try:
444 444 from pylons import request
445 445 environ = request.environ
446 446 except TypeError:
447 447 # we might use this outside of request context, let's fake the
448 448 # environ data
449 449 from webob import Request
450 450 environ = Request.blank('').environ
451 451 extras = {
452 452 'ip': _get_ip_addr(environ),
453 453 'username': username,
454 454 'action': action or 'push_local',
455 455 'repository': repo_name,
456 456 'scm': repo_alias,
457 457 'config': CONFIG['__file__'],
458 458 'server_url': get_server_url(environ),
459 459 'make_lock': None,
460 460 'locked_by': [None, None]
461 461 }
462 462 _set_extras(extras)
463 463
464 464 def _handle_push(self, repo, username, action, repo_name, revisions):
465 465 """
466 466 Triggers push action hooks
467 467
468 468 :param repo: SCM repo
469 469 :param username: username who pushes
470 470 :param action: push/push_loca/push_remote
471 471 :param repo_name: name of repo
472 472 :param revisions: list of revisions that we pushed
473 473 """
474 474 self._handle_rc_scm_extras(username, repo_name, repo_alias=repo.alias)
475 475 _scm_repo = repo._repo
476 476 # trigger push hook
477 477 if repo.alias == 'hg':
478 478 log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
479 479 elif repo.alias == 'git':
480 480 log_push_action(None, _scm_repo, _git_revs=revisions)
481 481
482 482 def _get_IMC_module(self, scm_type):
483 483 """
484 484 Returns InMemoryCommit class based on scm_type
485 485
486 486 :param scm_type:
487 487 """
488 488 if scm_type == 'hg':
489 489 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset
490 490 return MercurialInMemoryChangeset
491 491
492 492 if scm_type == 'git':
493 493 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset
494 494 return GitInMemoryChangeset
495 495
496 496 raise Exception('Invalid scm_type, must be one of hg,git got %s'
497 497 % (scm_type,))
498 498
499 499 def pull_changes(self, repo, username):
500 500 dbrepo = self.__get_repo(repo)
501 501 clone_uri = dbrepo.clone_uri
502 502 if not clone_uri:
503 503 raise Exception("This repository doesn't have a clone uri")
504 504
505 505 repo = dbrepo.scm_instance
506 506 repo_name = dbrepo.repo_name
507 507 try:
508 508 if repo.alias == 'git':
509 509 repo.fetch(clone_uri)
510 510 # git doesn't really have something like post-fetch action
511 511 # we fake that now. #TODO: extract fetched revisions somehow
512 512 # here
513 513 self._handle_push(repo,
514 514 username=username,
515 515 action='push_remote',
516 516 repo_name=repo_name,
517 517 revisions=[])
518 518 else:
519 519 self._handle_rc_scm_extras(username, dbrepo.repo_name,
520 520 repo.alias, action='push_remote')
521 521 repo.pull(clone_uri)
522 522
523 523 self.mark_for_invalidation(repo_name)
524 524 except Exception:
525 525 log.error(traceback.format_exc())
526 526 raise
527 527
528 528 def commit_change(self, repo, repo_name, cs, user, author, message,
529 529 content, f_path):
530 530 """
531 531 Commits changes
532 532
533 533 :param repo: SCM instance
534 534
535 535 """
536 536 user = self._get_user(user)
537 537 IMC = self._get_IMC_module(repo.alias)
538 538
539 539 # decoding here will force that we have proper encoded values
540 540 # in any other case this will throw exceptions and deny commit
541 541 content = safe_str(content)
542 542 path = safe_str(f_path)
543 543 # message and author needs to be unicode
544 544 # proper backend should then translate that into required type
545 545 message = safe_unicode(message)
546 546 author = safe_unicode(author)
547 547 imc = IMC(repo)
548 548 imc.change(FileNode(path, content, mode=cs.get_file_mode(f_path)))
549 549 tip = imc.commit(message=message,
550 550 author=author,
551 551 parents=[cs], branch=cs.branch)
552 552
553 553 self.mark_for_invalidation(repo_name)
554 554 self._handle_push(repo,
555 555 username=user.username,
556 556 action='push_local',
557 557 repo_name=repo_name,
558 558 revisions=[tip.raw_id])
559 559 return tip
560 560
561 561 def create_nodes(self, user, repo, message, nodes, parent_cs=None,
562 562 author=None, trigger_push_hook=True):
563 563 """
564 564 Commits given multiple nodes into repo
565 565
566 566 :param user: RhodeCode User object or user_id, the commiter
567 567 :param repo: RhodeCode Repository object
568 568 :param message: commit message
569 569 :param nodes: mapping {filename:{'content':content},...}
570 570 :param parent_cs: parent changeset, can be empty than it's initial commit
571 571 :param author: author of commit, cna be different that commiter only for git
572 572 :param trigger_push_hook: trigger push hooks
573 573
574 574 :returns: new commited changeset
575 575 """
576 576
577 577 user = self._get_user(user)
578 578 scm_instance = repo.scm_instance_no_cache()
579 579
580 580 processed_nodes = []
581 581 for f_path in nodes:
582 582 if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path:
583 583 raise NonRelativePathError('%s is not an relative path' % f_path)
584 584 if f_path:
585 585 f_path = os.path.normpath(f_path)
586 586 content = nodes[f_path]['content']
587 587 f_path = safe_str(f_path)
588 588 # decoding here will force that we have proper encoded values
589 589 # in any other case this will throw exceptions and deny commit
590 590 if isinstance(content, (basestring,)):
591 591 content = safe_str(content)
592 592 elif isinstance(content, (file, cStringIO.OutputType,)):
593 593 content = content.read()
594 594 else:
595 595 raise Exception('Content is of unrecognized type %s' % (
596 596 type(content)
597 597 ))
598 598 processed_nodes.append((f_path, content))
599 599
600 600 message = safe_unicode(message)
601 601 commiter = user.full_contact
602 602 author = safe_unicode(author) if author else commiter
603 603
604 604 IMC = self._get_IMC_module(scm_instance.alias)
605 605 imc = IMC(scm_instance)
606 606
607 607 if not parent_cs:
608 608 parent_cs = EmptyChangeset(alias=scm_instance.alias)
609 609
610 610 if isinstance(parent_cs, EmptyChangeset):
611 611 # EmptyChangeset means we we're editing empty repository
612 612 parents = None
613 613 else:
614 614 parents = [parent_cs]
615 615 # add multiple nodes
616 616 for path, content in processed_nodes:
617 617 imc.add(FileNode(path, content=content))
618 618
619 619 tip = imc.commit(message=message,
620 620 author=author,
621 621 parents=parents,
622 622 branch=parent_cs.branch)
623 623
624 624 self.mark_for_invalidation(repo.repo_name)
625 625 if trigger_push_hook:
626 626 self._handle_push(scm_instance,
627 627 username=user.username,
628 628 action='push_local',
629 629 repo_name=repo.repo_name,
630 630 revisions=[tip.raw_id])
631 631 return tip
632 632
633 633 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
634 634 """
635 635 recursive walk in root dir and return a set of all path in that dir
636 636 based on repository walk function
637 637
638 638 :param repo_name: name of repository
639 639 :param revision: revision for which to list nodes
640 640 :param root_path: root path to list
641 641 :param flat: return as a list, if False returns a dict with decription
642 642
643 643 """
644 644 _files = list()
645 645 _dirs = list()
646 646 try:
647 647 _repo = self.__get_repo(repo_name)
648 648 changeset = _repo.scm_instance.get_changeset(revision)
649 649 root_path = root_path.lstrip('/')
650 650 for topnode, dirs, files in changeset.walk(root_path):
651 651 for f in files:
652 652 _files.append(f.path if flat else {"name": f.path,
653 653 "type": "file"})
654 654 for d in dirs:
655 655 _dirs.append(d.path if flat else {"name": d.path,
656 656 "type": "dir"})
657 657 except RepositoryError:
658 658 log.debug(traceback.format_exc())
659 659 raise
660 660
661 661 return _dirs, _files
662 662
663 663 def get_unread_journal(self):
664 664 return self.sa.query(UserLog).count()
665 665
666 666 def get_repo_landing_revs(self, repo=None):
667 667 """
668 668 Generates select option with tags branches and bookmarks (for hg only)
669 669 grouped by type
670 670
671 671 :param repo:
672 672 """
673 673
674 674 hist_l = []
675 675 choices = []
676 676 repo = self.__get_repo(repo)
677 677 hist_l.append(['tip', _('latest tip')])
678 678 choices.append('tip')
679 679 if not repo:
680 680 return choices, hist_l
681 681
682 682 repo = repo.scm_instance
683 683
684 684 branches_group = ([(k, k) for k, v in
685 685 repo.branches.iteritems()], _("Branches"))
686 686 hist_l.append(branches_group)
687 687 choices.extend([x[0] for x in branches_group[0]])
688 688
689 689 if repo.alias == 'hg':
690 690 bookmarks_group = ([(k, k) for k, v in
691 691 repo.bookmarks.iteritems()], _("Bookmarks"))
692 692 hist_l.append(bookmarks_group)
693 693 choices.extend([x[0] for x in bookmarks_group[0]])
694 694
695 695 tags_group = ([(k, k) for k, v in
696 696 repo.tags.iteritems()], _("Tags"))
697 697 hist_l.append(tags_group)
698 698 choices.extend([x[0] for x in tags_group[0]])
699 699
700 700 return choices, hist_l
701 701
702 702 def install_git_hook(self, repo, force_create=False):
703 703 """
704 704 Creates a rhodecode hook inside a git repository
705 705
706 706 :param repo: Instance of VCS repo
707 707 :param force_create: Create even if same name hook exists
708 708 """
709 709
710 710 loc = jn(repo.path, 'hooks')
711 711 if not repo.bare:
712 712 loc = jn(repo.path, '.git', 'hooks')
713 713 if not os.path.isdir(loc):
714 714 os.makedirs(loc)
715 715
716 716 tmpl_post = pkg_resources.resource_string(
717 717 'rhodecode', jn('config', 'post_receive_tmpl.py')
718 718 )
719 719 tmpl_pre = pkg_resources.resource_string(
720 720 'rhodecode', jn('config', 'pre_receive_tmpl.py')
721 721 )
722 722
723 723 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
724 724 _hook_file = jn(loc, '%s-receive' % h_type)
725 725 _rhodecode_hook = False
726 726 log.debug('Installing git hook in repo %s' % repo)
727 727 if os.path.exists(_hook_file):
728 728 # let's take a look at this hook, maybe it's rhodecode ?
729 729 log.debug('hook exists, checking if it is from rhodecode')
730 730 with open(_hook_file, 'rb') as f:
731 731 data = f.read()
732 732 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
733 733 % 'RC_HOOK_VER').search(data)
734 734 if matches:
735 735 try:
736 736 ver = matches.groups()[0]
737 737 log.debug('got %s it is rhodecode' % (ver))
738 738 _rhodecode_hook = True
739 739 except Exception:
740 740 log.error(traceback.format_exc())
741 741 else:
742 742 # there is no hook in this dir, so we want to create one
743 743 _rhodecode_hook = True
744 744
745 745 if _rhodecode_hook or force_create:
746 746 log.debug('writing %s hook file !' % (h_type,))
747 with open(_hook_file, 'wb') as f:
748 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
749 f.write(tmpl)
750 os.chmod(_hook_file, 0755)
747 try:
748 with open(_hook_file, 'wb') as f:
749 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
750 f.write(tmpl)
751 os.chmod(_hook_file, 0755)
752 except IOError, e:
753 log.error('error writing %s: %s' % (_hook_file, e))
751 754 else:
752 755 log.debug('skipping writing hook file')
General Comments 0
You need to be logged in to leave comments. Login now