##// END OF EJS Templates
mercurial: enabled full evolve+topic extensions
marcink -
r3625:4c188069 default
parent child Browse files
Show More
@@ -1,624 +1,645 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 23 of database as well as for migration operations
24 24 """
25 25
26 26 import os
27 27 import sys
28 28 import time
29 29 import uuid
30 30 import logging
31 31 import getpass
32 32 from os.path import dirname as dn, join as jn
33 33
34 34 from sqlalchemy.engine import create_engine
35 35
36 36 from rhodecode import __dbversion__
37 37 from rhodecode.model import init_model
38 38 from rhodecode.model.user import UserModel
39 39 from rhodecode.model.db import (
40 40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 42 from rhodecode.model.meta import Session, Base
43 43 from rhodecode.model.permission import PermissionModel
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.repo_group import RepoGroupModel
46 46 from rhodecode.model.settings import SettingsModel
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 def notify(msg):
53 53 """
54 54 Notification for migrations messages
55 55 """
56 56 ml = len(msg) + (4 * 2)
57 57 print(('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper())
58 58
59 59
60 60 class DbManage(object):
61 61
62 62 def __init__(self, log_sql, dbconf, root, tests=False,
63 63 SESSION=None, cli_args=None):
64 64 self.dbname = dbconf.split('/')[-1]
65 65 self.tests = tests
66 66 self.root = root
67 67 self.dburi = dbconf
68 68 self.log_sql = log_sql
69 69 self.db_exists = False
70 70 self.cli_args = cli_args or {}
71 71 self.init_db(SESSION=SESSION)
72 72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
73 73
74 74 def get_ask_ok_func(self, param):
75 75 if param not in [None]:
76 76 # return a function lambda that has a default set to param
77 77 return lambda *args, **kwargs: param
78 78 else:
79 79 from rhodecode.lib.utils import ask_ok
80 80 return ask_ok
81 81
82 82 def init_db(self, SESSION=None):
83 83 if SESSION:
84 84 self.sa = SESSION
85 85 else:
86 86 # init new sessions
87 87 engine = create_engine(self.dburi, echo=self.log_sql)
88 88 init_model(engine)
89 89 self.sa = Session()
90 90
91 91 def create_tables(self, override=False):
92 92 """
93 93 Create a auth database
94 94 """
95 95
96 96 log.info("Existing database with the same name is going to be destroyed.")
97 97 log.info("Setup command will run DROP ALL command on that database.")
98 98 if self.tests:
99 99 destroy = True
100 100 else:
101 101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
102 102 if not destroy:
103 103 log.info('Nothing done.')
104 104 sys.exit(0)
105 105 if destroy:
106 106 Base.metadata.drop_all()
107 107
108 108 checkfirst = not override
109 109 Base.metadata.create_all(checkfirst=checkfirst)
110 110 log.info('Created tables for %s', self.dbname)
111 111
112 112 def set_db_version(self):
113 113 ver = DbMigrateVersion()
114 114 ver.version = __dbversion__
115 115 ver.repository_id = 'rhodecode_db_migrations'
116 116 ver.repository_path = 'versions'
117 117 self.sa.add(ver)
118 118 log.info('db version set to: %s', __dbversion__)
119 119
120 120 def run_pre_migration_tasks(self):
121 121 """
122 122 Run various tasks before actually doing migrations
123 123 """
124 124 # delete cache keys on each upgrade
125 125 total = CacheKey.query().count()
126 126 log.info("Deleting (%s) cache keys now...", total)
127 127 CacheKey.delete_all_cache()
128 128
129 129 def upgrade(self, version=None):
130 130 """
131 131 Upgrades given database schema to given revision following
132 132 all needed steps, to perform the upgrade
133 133
134 134 """
135 135
136 136 from rhodecode.lib.dbmigrate.migrate.versioning import api
137 137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
138 138 DatabaseNotControlledError
139 139
140 140 if 'sqlite' in self.dburi:
141 141 print(
142 142 '********************** WARNING **********************\n'
143 143 'Make sure your version of sqlite is at least 3.7.X. \n'
144 144 'Earlier versions are known to fail on some migrations\n'
145 145 '*****************************************************\n')
146 146
147 147 upgrade = self.ask_ok(
148 148 'You are about to perform a database upgrade. Make '
149 149 'sure you have backed up your database. '
150 150 'Continue ? [y/n]')
151 151 if not upgrade:
152 152 log.info('No upgrade performed')
153 153 sys.exit(0)
154 154
155 155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
156 156 'rhodecode/lib/dbmigrate')
157 157 db_uri = self.dburi
158 158
159 159 if version:
160 160 DbMigrateVersion.set_version(version)
161 161
162 162 try:
163 163 curr_version = api.db_version(db_uri, repository_path)
164 164 msg = ('Found current database db_uri under version '
165 165 'control with version {}'.format(curr_version))
166 166
167 167 except (RuntimeError, DatabaseNotControlledError):
168 168 curr_version = 1
169 169 msg = ('Current database is not under version control. Setting '
170 170 'as version %s' % curr_version)
171 171 api.version_control(db_uri, repository_path, curr_version)
172 172
173 173 notify(msg)
174 174
175 175 self.run_pre_migration_tasks()
176 176
177 177 if curr_version == __dbversion__:
178 178 log.info('This database is already at the newest version')
179 179 sys.exit(0)
180 180
181 181 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
182 182 notify('attempting to upgrade database from '
183 183 'version %s to version %s' % (curr_version, __dbversion__))
184 184
185 185 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
186 186 _step = None
187 187 for step in upgrade_steps:
188 188 notify('performing upgrade step %s' % step)
189 189 time.sleep(0.5)
190 190
191 191 api.upgrade(db_uri, repository_path, step)
192 192 self.sa.rollback()
193 193 notify('schema upgrade for step %s completed' % (step,))
194 194
195 195 _step = step
196 196
197 197 notify('upgrade to version %s successful' % _step)
198 198
199 199 def fix_repo_paths(self):
200 200 """
201 201 Fixes an old RhodeCode version path into new one without a '*'
202 202 """
203 203
204 204 paths = self.sa.query(RhodeCodeUi)\
205 205 .filter(RhodeCodeUi.ui_key == '/')\
206 206 .scalar()
207 207
208 208 paths.ui_value = paths.ui_value.replace('*', '')
209 209
210 210 try:
211 211 self.sa.add(paths)
212 212 self.sa.commit()
213 213 except Exception:
214 214 self.sa.rollback()
215 215 raise
216 216
217 217 def fix_default_user(self):
218 218 """
219 219 Fixes an old default user with some 'nicer' default values,
220 220 used mostly for anonymous access
221 221 """
222 222 def_user = self.sa.query(User)\
223 223 .filter(User.username == User.DEFAULT_USER)\
224 224 .one()
225 225
226 226 def_user.name = 'Anonymous'
227 227 def_user.lastname = 'User'
228 228 def_user.email = User.DEFAULT_USER_EMAIL
229 229
230 230 try:
231 231 self.sa.add(def_user)
232 232 self.sa.commit()
233 233 except Exception:
234 234 self.sa.rollback()
235 235 raise
236 236
237 237 def fix_settings(self):
238 238 """
239 239 Fixes rhodecode settings and adds ga_code key for google analytics
240 240 """
241 241
242 242 hgsettings3 = RhodeCodeSetting('ga_code', '')
243 243
244 244 try:
245 245 self.sa.add(hgsettings3)
246 246 self.sa.commit()
247 247 except Exception:
248 248 self.sa.rollback()
249 249 raise
250 250
251 251 def create_admin_and_prompt(self):
252 252
253 253 # defaults
254 254 defaults = self.cli_args
255 255 username = defaults.get('username')
256 256 password = defaults.get('password')
257 257 email = defaults.get('email')
258 258
259 259 if username is None:
260 260 username = raw_input('Specify admin username:')
261 261 if password is None:
262 262 password = self._get_admin_password()
263 263 if not password:
264 264 # second try
265 265 password = self._get_admin_password()
266 266 if not password:
267 267 sys.exit()
268 268 if email is None:
269 269 email = raw_input('Specify admin email:')
270 270 api_key = self.cli_args.get('api_key')
271 271 self.create_user(username, password, email, True,
272 272 strict_creation_check=False,
273 273 api_key=api_key)
274 274
275 275 def _get_admin_password(self):
276 276 password = getpass.getpass('Specify admin password '
277 277 '(min 6 chars):')
278 278 confirm = getpass.getpass('Confirm password:')
279 279
280 280 if password != confirm:
281 281 log.error('passwords mismatch')
282 282 return False
283 283 if len(password) < 6:
284 284 log.error('password is too short - use at least 6 characters')
285 285 return False
286 286
287 287 return password
288 288
289 289 def create_test_admin_and_users(self):
290 290 log.info('creating admin and regular test users')
291 291 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
292 292 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
293 293 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
294 294 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
295 295 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
296 296
297 297 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
298 298 TEST_USER_ADMIN_EMAIL, True, api_key=True)
299 299
300 300 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
301 301 TEST_USER_REGULAR_EMAIL, False, api_key=True)
302 302
303 303 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
304 304 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
305 305
306 306 def create_ui_settings(self, repo_store_path):
307 307 """
308 308 Creates ui settings, fills out hooks
309 309 and disables dotencode
310 310 """
311 311 settings_model = SettingsModel(sa=self.sa)
312 312 from rhodecode.lib.vcs.backends.hg import largefiles_store
313 313 from rhodecode.lib.vcs.backends.git import lfs_store
314 314
315 315 # Build HOOKS
316 316 hooks = [
317 317 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
318 318
319 319 # HG
320 320 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
321 321 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
322 322 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
323 323 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
324 324 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
325 325 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
326 326
327 327 ]
328 328
329 329 for key, value in hooks:
330 330 hook_obj = settings_model.get_ui_by_key(key)
331 331 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
332 332 hooks2.ui_section = 'hooks'
333 333 hooks2.ui_key = key
334 334 hooks2.ui_value = value
335 335 self.sa.add(hooks2)
336 336
337 337 # enable largefiles
338 338 largefiles = RhodeCodeUi()
339 339 largefiles.ui_section = 'extensions'
340 340 largefiles.ui_key = 'largefiles'
341 341 largefiles.ui_value = ''
342 342 self.sa.add(largefiles)
343 343
344 344 # set default largefiles cache dir, defaults to
345 345 # /repo_store_location/.cache/largefiles
346 346 largefiles = RhodeCodeUi()
347 347 largefiles.ui_section = 'largefiles'
348 348 largefiles.ui_key = 'usercache'
349 349 largefiles.ui_value = largefiles_store(repo_store_path)
350 350
351 351 self.sa.add(largefiles)
352 352
353 353 # set default lfs cache dir, defaults to
354 354 # /repo_store_location/.cache/lfs_store
355 355 lfsstore = RhodeCodeUi()
356 356 lfsstore.ui_section = 'vcs_git_lfs'
357 357 lfsstore.ui_key = 'store_location'
358 358 lfsstore.ui_value = lfs_store(repo_store_path)
359 359
360 360 self.sa.add(lfsstore)
361 361
362 362 # enable hgsubversion disabled by default
363 363 hgsubversion = RhodeCodeUi()
364 364 hgsubversion.ui_section = 'extensions'
365 365 hgsubversion.ui_key = 'hgsubversion'
366 366 hgsubversion.ui_value = ''
367 367 hgsubversion.ui_active = False
368 368 self.sa.add(hgsubversion)
369 369
370 370 # enable hgevolve disabled by default
371 371 hgevolve = RhodeCodeUi()
372 372 hgevolve.ui_section = 'extensions'
373 373 hgevolve.ui_key = 'evolve'
374 374 hgevolve.ui_value = ''
375 375 hgevolve.ui_active = False
376 376 self.sa.add(hgevolve)
377 377
378 hgevolve = RhodeCodeUi()
379 hgevolve.ui_section = 'experimental'
380 hgevolve.ui_key = 'evolution'
381 hgevolve.ui_value = ''
382 hgevolve.ui_active = False
383 self.sa.add(hgevolve)
384
385 hgevolve = RhodeCodeUi()
386 hgevolve.ui_section = 'experimental'
387 hgevolve.ui_key = 'evolution.exchange'
388 hgevolve.ui_value = ''
389 hgevolve.ui_active = False
390 self.sa.add(hgevolve)
391
392 hgevolve = RhodeCodeUi()
393 hgevolve.ui_section = 'extensions'
394 hgevolve.ui_key = 'topic'
395 hgevolve.ui_value = ''
396 hgevolve.ui_active = False
397 self.sa.add(hgevolve)
398
378 399 # enable hggit disabled by default
379 400 hggit = RhodeCodeUi()
380 401 hggit.ui_section = 'extensions'
381 402 hggit.ui_key = 'hggit'
382 403 hggit.ui_value = ''
383 404 hggit.ui_active = False
384 405 self.sa.add(hggit)
385 406
386 407 # set svn branch defaults
387 408 branches = ["/branches/*", "/trunk"]
388 409 tags = ["/tags/*"]
389 410
390 411 for branch in branches:
391 412 settings_model.create_ui_section_value(
392 413 RhodeCodeUi.SVN_BRANCH_ID, branch)
393 414
394 415 for tag in tags:
395 416 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
396 417
397 418 def create_auth_plugin_options(self, skip_existing=False):
398 419 """
399 420 Create default auth plugin settings, and make it active
400 421
401 422 :param skip_existing:
402 423 """
403 424
404 425 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
405 426 ('auth_rhodecode_enabled', 'True', 'bool')]:
406 427 if (skip_existing and
407 428 SettingsModel().get_setting_by_name(k) is not None):
408 429 log.debug('Skipping option %s', k)
409 430 continue
410 431 setting = RhodeCodeSetting(k, v, t)
411 432 self.sa.add(setting)
412 433
413 434 def create_default_options(self, skip_existing=False):
414 435 """Creates default settings"""
415 436
416 437 for k, v, t in [
417 438 ('default_repo_enable_locking', False, 'bool'),
418 439 ('default_repo_enable_downloads', False, 'bool'),
419 440 ('default_repo_enable_statistics', False, 'bool'),
420 441 ('default_repo_private', False, 'bool'),
421 442 ('default_repo_type', 'hg', 'unicode')]:
422 443
423 444 if (skip_existing and
424 445 SettingsModel().get_setting_by_name(k) is not None):
425 446 log.debug('Skipping option %s', k)
426 447 continue
427 448 setting = RhodeCodeSetting(k, v, t)
428 449 self.sa.add(setting)
429 450
430 451 def fixup_groups(self):
431 452 def_usr = User.get_default_user()
432 453 for g in RepoGroup.query().all():
433 454 g.group_name = g.get_new_name(g.name)
434 455 self.sa.add(g)
435 456 # get default perm
436 457 default = UserRepoGroupToPerm.query()\
437 458 .filter(UserRepoGroupToPerm.group == g)\
438 459 .filter(UserRepoGroupToPerm.user == def_usr)\
439 460 .scalar()
440 461
441 462 if default is None:
442 463 log.debug('missing default permission for group %s adding', g)
443 464 perm_obj = RepoGroupModel()._create_default_perms(g)
444 465 self.sa.add(perm_obj)
445 466
446 467 def reset_permissions(self, username):
447 468 """
448 469 Resets permissions to default state, useful when old systems had
449 470 bad permissions, we must clean them up
450 471
451 472 :param username:
452 473 """
453 474 default_user = User.get_by_username(username)
454 475 if not default_user:
455 476 return
456 477
457 478 u2p = UserToPerm.query()\
458 479 .filter(UserToPerm.user == default_user).all()
459 480 fixed = False
460 481 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
461 482 for p in u2p:
462 483 Session().delete(p)
463 484 fixed = True
464 485 self.populate_default_permissions()
465 486 return fixed
466 487
467 488 def update_repo_info(self):
468 489 RepoModel.update_repoinfo()
469 490
470 491 def config_prompt(self, test_repo_path='', retries=3):
471 492 defaults = self.cli_args
472 493 _path = defaults.get('repos_location')
473 494 if retries == 3:
474 495 log.info('Setting up repositories config')
475 496
476 497 if _path is not None:
477 498 path = _path
478 499 elif not self.tests and not test_repo_path:
479 500 path = raw_input(
480 501 'Enter a valid absolute path to store repositories. '
481 502 'All repositories in that path will be added automatically:'
482 503 )
483 504 else:
484 505 path = test_repo_path
485 506 path_ok = True
486 507
487 508 # check proper dir
488 509 if not os.path.isdir(path):
489 510 path_ok = False
490 511 log.error('Given path %s is not a valid directory', path)
491 512
492 513 elif not os.path.isabs(path):
493 514 path_ok = False
494 515 log.error('Given path %s is not an absolute path', path)
495 516
496 517 # check if path is at least readable.
497 518 if not os.access(path, os.R_OK):
498 519 path_ok = False
499 520 log.error('Given path %s is not readable', path)
500 521
501 522 # check write access, warn user about non writeable paths
502 523 elif not os.access(path, os.W_OK) and path_ok:
503 524 log.warning('No write permission to given path %s', path)
504 525
505 526 q = ('Given path %s is not writeable, do you want to '
506 527 'continue with read only mode ? [y/n]' % (path,))
507 528 if not self.ask_ok(q):
508 529 log.error('Canceled by user')
509 530 sys.exit(-1)
510 531
511 532 if retries == 0:
512 533 sys.exit('max retries reached')
513 534 if not path_ok:
514 535 retries -= 1
515 536 return self.config_prompt(test_repo_path, retries)
516 537
517 538 real_path = os.path.normpath(os.path.realpath(path))
518 539
519 540 if real_path != os.path.normpath(path):
520 541 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
521 542 'given path as %s ? [y/n]') % (real_path,)
522 543 if not self.ask_ok(q):
523 544 log.error('Canceled by user')
524 545 sys.exit(-1)
525 546
526 547 return real_path
527 548
528 549 def create_settings(self, path):
529 550
530 551 self.create_ui_settings(path)
531 552
532 553 ui_config = [
533 554 ('web', 'push_ssl', 'False'),
534 555 ('web', 'allow_archive', 'gz zip bz2'),
535 556 ('web', 'allow_push', '*'),
536 557 ('web', 'baseurl', '/'),
537 558 ('paths', '/', path),
538 559 ('phases', 'publish', 'True')
539 560 ]
540 561 for section, key, value in ui_config:
541 562 ui_conf = RhodeCodeUi()
542 563 setattr(ui_conf, 'ui_section', section)
543 564 setattr(ui_conf, 'ui_key', key)
544 565 setattr(ui_conf, 'ui_value', value)
545 566 self.sa.add(ui_conf)
546 567
547 568 # rhodecode app settings
548 569 settings = [
549 570 ('realm', 'RhodeCode', 'unicode'),
550 571 ('title', '', 'unicode'),
551 572 ('pre_code', '', 'unicode'),
552 573 ('post_code', '', 'unicode'),
553 574 ('show_public_icon', True, 'bool'),
554 575 ('show_private_icon', True, 'bool'),
555 576 ('stylify_metatags', False, 'bool'),
556 577 ('dashboard_items', 100, 'int'),
557 578 ('admin_grid_items', 25, 'int'),
558 579 ('show_version', True, 'bool'),
559 580 ('use_gravatar', False, 'bool'),
560 581 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
561 582 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
562 583 ('support_url', '', 'unicode'),
563 584 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
564 585 ('show_revision_number', True, 'bool'),
565 586 ('show_sha_length', 12, 'int'),
566 587 ]
567 588
568 589 for key, val, type_ in settings:
569 590 sett = RhodeCodeSetting(key, val, type_)
570 591 self.sa.add(sett)
571 592
572 593 self.create_auth_plugin_options()
573 594 self.create_default_options()
574 595
575 596 log.info('created ui config')
576 597
577 598 def create_user(self, username, password, email='', admin=False,
578 599 strict_creation_check=True, api_key=None):
579 600 log.info('creating user `%s`', username)
580 601 user = UserModel().create_or_update(
581 602 username, password, email, firstname=u'RhodeCode', lastname=u'Admin',
582 603 active=True, admin=admin, extern_type="rhodecode",
583 604 strict_creation_check=strict_creation_check)
584 605
585 606 if api_key:
586 607 log.info('setting a new default auth token for user `%s`', username)
587 608 UserModel().add_auth_token(
588 609 user=user, lifetime_minutes=-1,
589 610 role=UserModel.auth_token_role.ROLE_ALL,
590 611 description=u'BUILTIN TOKEN')
591 612
592 613 def create_default_user(self):
593 614 log.info('creating default user')
594 615 # create default user for handling default permissions.
595 616 user = UserModel().create_or_update(username=User.DEFAULT_USER,
596 617 password=str(uuid.uuid1())[:20],
597 618 email=User.DEFAULT_USER_EMAIL,
598 619 firstname=u'Anonymous',
599 620 lastname=u'User',
600 621 strict_creation_check=False)
601 622 # based on configuration options activate/de-activate this user which
602 623 # controlls anonymous access
603 624 if self.cli_args.get('public_access') is False:
604 625 log.info('Public access disabled')
605 626 user.active = False
606 627 Session().add(user)
607 628 Session().commit()
608 629
609 630 def create_permissions(self):
610 631 """
611 632 Creates all permissions defined in the system
612 633 """
613 634 # module.(access|create|change|delete)_[name]
614 635 # module.(none|read|write|admin)
615 636 log.info('creating permissions')
616 637 PermissionModel(self.sa).create_permissions()
617 638
618 639 def populate_default_permissions(self):
619 640 """
620 641 Populate default permissions. It will create only the default
621 642 permissions that are missing, and not alter already defined ones
622 643 """
623 644 log.info('creating default user permissions')
624 645 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,781 +1,782 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Utilities library for RhodeCode
23 23 """
24 24
25 25 import datetime
26 26 import decorator
27 27 import json
28 28 import logging
29 29 import os
30 30 import re
31 31 import sys
32 32 import shutil
33 33 import tempfile
34 34 import traceback
35 35 import tarfile
36 36 import warnings
37 37 import hashlib
38 38 from os.path import join as jn
39 39
40 40 import paste
41 41 import pkg_resources
42 42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 43 from mako import exceptions
44 44 from pyramid.threadlocal import get_current_registry
45 45 from rhodecode.lib.request import Request
46 46
47 47 from rhodecode.lib.vcs.backends.base import Config
48 48 from rhodecode.lib.vcs.exceptions import VCSError
49 49 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
50 50 from rhodecode.lib.utils2 import (
51 51 safe_str, safe_unicode, get_current_rhodecode_user, md5, sha1)
52 52 from rhodecode.model import meta
53 53 from rhodecode.model.db import (
54 54 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 55 from rhodecode.model.meta import Session
56 56
57 57
58 58 log = logging.getLogger(__name__)
59 59
60 60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 61
62 62 # String which contains characters that are not allowed in slug names for
63 63 # repositories or repository groups. It is properly escaped to use it in
64 64 # regular expressions.
65 65 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66 66
67 67 # Regex that matches forbidden characters in repo/group slugs.
68 68 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
69 69
70 70 # Regex that matches allowed characters in repo/group slugs.
71 71 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
72 72
73 73 # Regex that matches whole repo/group slugs.
74 74 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
75 75
76 76 _license_cache = None
77 77
78 78
79 79 def repo_name_slug(value):
80 80 """
81 81 Return slug of name of repository
82 82 This function is called on each creation/modification
83 83 of repository to prevent bad names in repo
84 84 """
85 85 replacement_char = '-'
86 86
87 87 slug = remove_formatting(value)
88 88 slug = SLUG_BAD_CHAR_RE.sub('', slug)
89 89 slug = re.sub('[\s]+', '-', slug)
90 90 slug = collapse(slug, replacement_char)
91 91 return slug
92 92
93 93
94 94 #==============================================================================
95 95 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
96 96 #==============================================================================
97 97 def get_repo_slug(request):
98 98 _repo = ''
99 99
100 100 if hasattr(request, 'db_repo'):
101 101 # if our requests has set db reference use it for name, this
102 102 # translates the example.com/_<id> into proper repo names
103 103 _repo = request.db_repo.repo_name
104 104 elif getattr(request, 'matchdict', None):
105 105 # pyramid
106 106 _repo = request.matchdict.get('repo_name')
107 107
108 108 if _repo:
109 109 _repo = _repo.rstrip('/')
110 110 return _repo
111 111
112 112
113 113 def get_repo_group_slug(request):
114 114 _group = ''
115 115 if hasattr(request, 'db_repo_group'):
116 116 # if our requests has set db reference use it for name, this
117 117 # translates the example.com/_<id> into proper repo group names
118 118 _group = request.db_repo_group.group_name
119 119 elif getattr(request, 'matchdict', None):
120 120 # pyramid
121 121 _group = request.matchdict.get('repo_group_name')
122 122
123 123 if _group:
124 124 _group = _group.rstrip('/')
125 125 return _group
126 126
127 127
128 128 def get_user_group_slug(request):
129 129 _user_group = ''
130 130
131 131 if hasattr(request, 'db_user_group'):
132 132 _user_group = request.db_user_group.users_group_name
133 133 elif getattr(request, 'matchdict', None):
134 134 # pyramid
135 135 _user_group = request.matchdict.get('user_group_id')
136 136 _user_group_name = request.matchdict.get('user_group_name')
137 137 try:
138 138 if _user_group:
139 139 _user_group = UserGroup.get(_user_group)
140 140 elif _user_group_name:
141 141 _user_group = UserGroup.get_by_group_name(_user_group_name)
142 142
143 143 if _user_group:
144 144 _user_group = _user_group.users_group_name
145 145 except Exception:
146 146 log.exception('Failed to get user group by id and name')
147 147 # catch all failures here
148 148 return None
149 149
150 150 return _user_group
151 151
152 152
153 153 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
154 154 """
155 155 Scans given path for repos and return (name,(type,path)) tuple
156 156
157 157 :param path: path to scan for repositories
158 158 :param recursive: recursive search and return names with subdirs in front
159 159 """
160 160
161 161 # remove ending slash for better results
162 162 path = path.rstrip(os.sep)
163 163 log.debug('now scanning in %s location recursive:%s...', path, recursive)
164 164
165 165 def _get_repos(p):
166 166 dirpaths = _get_dirpaths(p)
167 167 if not _is_dir_writable(p):
168 168 log.warning('repo path without write access: %s', p)
169 169
170 170 for dirpath in dirpaths:
171 171 if os.path.isfile(os.path.join(p, dirpath)):
172 172 continue
173 173 cur_path = os.path.join(p, dirpath)
174 174
175 175 # skip removed repos
176 176 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
177 177 continue
178 178
179 179 #skip .<somethin> dirs
180 180 if dirpath.startswith('.'):
181 181 continue
182 182
183 183 try:
184 184 scm_info = get_scm(cur_path)
185 185 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
186 186 except VCSError:
187 187 if not recursive:
188 188 continue
189 189 #check if this dir containts other repos for recursive scan
190 190 rec_path = os.path.join(p, dirpath)
191 191 if os.path.isdir(rec_path):
192 192 for inner_scm in _get_repos(rec_path):
193 193 yield inner_scm
194 194
195 195 return _get_repos(path)
196 196
197 197
198 198 def _get_dirpaths(p):
199 199 try:
200 200 # OS-independable way of checking if we have at least read-only
201 201 # access or not.
202 202 dirpaths = os.listdir(p)
203 203 except OSError:
204 204 log.warning('ignoring repo path without read access: %s', p)
205 205 return []
206 206
207 207 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
208 208 # decode paths and suddenly returns unicode objects itself. The items it
209 209 # cannot decode are returned as strings and cause issues.
210 210 #
211 211 # Those paths are ignored here until a solid solution for path handling has
212 212 # been built.
213 213 expected_type = type(p)
214 214
215 215 def _has_correct_type(item):
216 216 if type(item) is not expected_type:
217 217 log.error(
218 218 u"Ignoring path %s since it cannot be decoded into unicode.",
219 219 # Using "repr" to make sure that we see the byte value in case
220 220 # of support.
221 221 repr(item))
222 222 return False
223 223 return True
224 224
225 225 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
226 226
227 227 return dirpaths
228 228
229 229
230 230 def _is_dir_writable(path):
231 231 """
232 232 Probe if `path` is writable.
233 233
234 234 Due to trouble on Cygwin / Windows, this is actually probing if it is
235 235 possible to create a file inside of `path`, stat does not produce reliable
236 236 results in this case.
237 237 """
238 238 try:
239 239 with tempfile.TemporaryFile(dir=path):
240 240 pass
241 241 except OSError:
242 242 return False
243 243 return True
244 244
245 245
246 246 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None, config=None):
247 247 """
248 248 Returns True if given path is a valid repository False otherwise.
249 249 If expect_scm param is given also, compare if given scm is the same
250 250 as expected from scm parameter. If explicit_scm is given don't try to
251 251 detect the scm, just use the given one to check if repo is valid
252 252
253 253 :param repo_name:
254 254 :param base_path:
255 255 :param expect_scm:
256 256 :param explicit_scm:
257 257 :param config:
258 258
259 259 :return True: if given path is a valid repository
260 260 """
261 261 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
262 262 log.debug('Checking if `%s` is a valid path for repository. '
263 263 'Explicit type: %s', repo_name, explicit_scm)
264 264
265 265 try:
266 266 if explicit_scm:
267 267 detected_scms = [get_scm_backend(explicit_scm)(
268 268 full_path, config=config).alias]
269 269 else:
270 270 detected_scms = get_scm(full_path)
271 271
272 272 if expect_scm:
273 273 return detected_scms[0] == expect_scm
274 274 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
275 275 return True
276 276 except VCSError:
277 277 log.debug('path: %s is not a valid repo !', full_path)
278 278 return False
279 279
280 280
281 281 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
282 282 """
283 283 Returns True if given path is a repository group, False otherwise
284 284
285 285 :param repo_name:
286 286 :param base_path:
287 287 """
288 288 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
289 289 log.debug('Checking if `%s` is a valid path for repository group',
290 290 repo_group_name)
291 291
292 292 # check if it's not a repo
293 293 if is_valid_repo(repo_group_name, base_path):
294 294 log.debug('Repo called %s exist, it is not a valid repo group', repo_group_name)
295 295 return False
296 296
297 297 try:
298 298 # we need to check bare git repos at higher level
299 299 # since we might match branches/hooks/info/objects or possible
300 300 # other things inside bare git repo
301 301 maybe_repo = os.path.dirname(full_path)
302 302 if maybe_repo == base_path:
303 303 # skip root level repo check, we know root location CANNOT BE a repo group
304 304 return False
305 305
306 306 scm_ = get_scm(maybe_repo)
307 307 log.debug('path: %s is a vcs object:%s, not valid repo group', full_path, scm_)
308 308 return False
309 309 except VCSError:
310 310 pass
311 311
312 312 # check if it's a valid path
313 313 if skip_path_check or os.path.isdir(full_path):
314 314 log.debug('path: %s is a valid repo group !', full_path)
315 315 return True
316 316
317 317 log.debug('path: %s is not a valid repo group !', full_path)
318 318 return False
319 319
320 320
321 321 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
322 322 while True:
323 323 ok = raw_input(prompt)
324 324 if ok.lower() in ('y', 'ye', 'yes'):
325 325 return True
326 326 if ok.lower() in ('n', 'no', 'nop', 'nope'):
327 327 return False
328 328 retries = retries - 1
329 329 if retries < 0:
330 330 raise IOError
331 331 print(complaint)
332 332
333 333 # propagated from mercurial documentation
334 334 ui_sections = [
335 335 'alias', 'auth',
336 336 'decode/encode', 'defaults',
337 337 'diff', 'email',
338 338 'extensions', 'format',
339 339 'merge-patterns', 'merge-tools',
340 340 'hooks', 'http_proxy',
341 341 'smtp', 'patch',
342 342 'paths', 'profiling',
343 343 'server', 'trusted',
344 344 'ui', 'web', ]
345 345
346 346
347 347 def config_data_from_db(clear_session=True, repo=None):
348 348 """
349 349 Read the configuration data from the database and return configuration
350 350 tuples.
351 351 """
352 352 from rhodecode.model.settings import VcsSettingsModel
353 353
354 354 config = []
355 355
356 356 sa = meta.Session()
357 357 settings_model = VcsSettingsModel(repo=repo, sa=sa)
358 358
359 359 ui_settings = settings_model.get_ui_settings()
360 360
361 361 ui_data = []
362 362 for setting in ui_settings:
363 363 if setting.active:
364 364 ui_data.append((setting.section, setting.key, setting.value))
365 365 config.append((
366 366 safe_str(setting.section), safe_str(setting.key),
367 367 safe_str(setting.value)))
368 368 if setting.key == 'push_ssl':
369 369 # force set push_ssl requirement to False, rhodecode
370 370 # handles that
371 371 config.append((
372 372 safe_str(setting.section), safe_str(setting.key), False))
373 373 log.debug(
374 'settings ui from db: %s',
374 'settings ui from db@repo[%s]: %s',
375 repo,
375 376 ','.join(map(lambda s: '[{}] {}={}'.format(*s), ui_data)))
376 377 if clear_session:
377 378 meta.Session.remove()
378 379
379 380 # TODO: mikhail: probably it makes no sense to re-read hooks information.
380 381 # It's already there and activated/deactivated
381 382 skip_entries = []
382 383 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
383 384 if 'pull' not in enabled_hook_classes:
384 385 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
385 386 if 'push' not in enabled_hook_classes:
386 387 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
387 388 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
388 389 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
389 390
390 391 config = [entry for entry in config if entry[:2] not in skip_entries]
391 392
392 393 return config
393 394
394 395
395 396 def make_db_config(clear_session=True, repo=None):
396 397 """
397 398 Create a :class:`Config` instance based on the values in the database.
398 399 """
399 400 config = Config()
400 401 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
401 402 for section, option, value in config_data:
402 403 config.set(section, option, value)
403 404 return config
404 405
405 406
406 407 def get_enabled_hook_classes(ui_settings):
407 408 """
408 409 Return the enabled hook classes.
409 410
410 411 :param ui_settings: List of ui_settings as returned
411 412 by :meth:`VcsSettingsModel.get_ui_settings`
412 413
413 414 :return: a list with the enabled hook classes. The order is not guaranteed.
414 415 :rtype: list
415 416 """
416 417 enabled_hooks = []
417 418 active_hook_keys = [
418 419 key for section, key, value, active in ui_settings
419 420 if section == 'hooks' and active]
420 421
421 422 hook_names = {
422 423 RhodeCodeUi.HOOK_PUSH: 'push',
423 424 RhodeCodeUi.HOOK_PULL: 'pull',
424 425 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
425 426 }
426 427
427 428 for key in active_hook_keys:
428 429 hook = hook_names.get(key)
429 430 if hook:
430 431 enabled_hooks.append(hook)
431 432
432 433 return enabled_hooks
433 434
434 435
435 436 def set_rhodecode_config(config):
436 437 """
437 438 Updates pyramid config with new settings from database
438 439
439 440 :param config:
440 441 """
441 442 from rhodecode.model.settings import SettingsModel
442 443 app_settings = SettingsModel().get_all_settings()
443 444
444 445 for k, v in app_settings.items():
445 446 config[k] = v
446 447
447 448
448 449 def get_rhodecode_realm():
449 450 """
450 451 Return the rhodecode realm from database.
451 452 """
452 453 from rhodecode.model.settings import SettingsModel
453 454 realm = SettingsModel().get_setting_by_name('realm')
454 455 return safe_str(realm.app_settings_value)
455 456
456 457
457 458 def get_rhodecode_base_path():
458 459 """
459 460 Returns the base path. The base path is the filesystem path which points
460 461 to the repository store.
461 462 """
462 463 from rhodecode.model.settings import SettingsModel
463 464 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
464 465 return safe_str(paths_ui.ui_value)
465 466
466 467
467 468 def map_groups(path):
468 469 """
469 470 Given a full path to a repository, create all nested groups that this
470 471 repo is inside. This function creates parent-child relationships between
471 472 groups and creates default perms for all new groups.
472 473
473 474 :param paths: full path to repository
474 475 """
475 476 from rhodecode.model.repo_group import RepoGroupModel
476 477 sa = meta.Session()
477 478 groups = path.split(Repository.NAME_SEP)
478 479 parent = None
479 480 group = None
480 481
481 482 # last element is repo in nested groups structure
482 483 groups = groups[:-1]
483 484 rgm = RepoGroupModel(sa)
484 485 owner = User.get_first_super_admin()
485 486 for lvl, group_name in enumerate(groups):
486 487 group_name = '/'.join(groups[:lvl] + [group_name])
487 488 group = RepoGroup.get_by_group_name(group_name)
488 489 desc = '%s group' % group_name
489 490
490 491 # skip folders that are now removed repos
491 492 if REMOVED_REPO_PAT.match(group_name):
492 493 break
493 494
494 495 if group is None:
495 496 log.debug('creating group level: %s group_name: %s',
496 497 lvl, group_name)
497 498 group = RepoGroup(group_name, parent)
498 499 group.group_description = desc
499 500 group.user = owner
500 501 sa.add(group)
501 502 perm_obj = rgm._create_default_perms(group)
502 503 sa.add(perm_obj)
503 504 sa.flush()
504 505
505 506 parent = group
506 507 return group
507 508
508 509
509 510 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
510 511 """
511 512 maps all repos given in initial_repo_list, non existing repositories
512 513 are created, if remove_obsolete is True it also checks for db entries
513 514 that are not in initial_repo_list and removes them.
514 515
515 516 :param initial_repo_list: list of repositories found by scanning methods
516 517 :param remove_obsolete: check for obsolete entries in database
517 518 """
518 519 from rhodecode.model.repo import RepoModel
519 520 from rhodecode.model.repo_group import RepoGroupModel
520 521 from rhodecode.model.settings import SettingsModel
521 522
522 523 sa = meta.Session()
523 524 repo_model = RepoModel()
524 525 user = User.get_first_super_admin()
525 526 added = []
526 527
527 528 # creation defaults
528 529 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
529 530 enable_statistics = defs.get('repo_enable_statistics')
530 531 enable_locking = defs.get('repo_enable_locking')
531 532 enable_downloads = defs.get('repo_enable_downloads')
532 533 private = defs.get('repo_private')
533 534
534 535 for name, repo in initial_repo_list.items():
535 536 group = map_groups(name)
536 537 unicode_name = safe_unicode(name)
537 538 db_repo = repo_model.get_by_repo_name(unicode_name)
538 539 # found repo that is on filesystem not in RhodeCode database
539 540 if not db_repo:
540 541 log.info('repository %s not found, creating now', name)
541 542 added.append(name)
542 543 desc = (repo.description
543 544 if repo.description != 'unknown'
544 545 else '%s repository' % name)
545 546
546 547 db_repo = repo_model._create_repo(
547 548 repo_name=name,
548 549 repo_type=repo.alias,
549 550 description=desc,
550 551 repo_group=getattr(group, 'group_id', None),
551 552 owner=user,
552 553 enable_locking=enable_locking,
553 554 enable_downloads=enable_downloads,
554 555 enable_statistics=enable_statistics,
555 556 private=private,
556 557 state=Repository.STATE_CREATED
557 558 )
558 559 sa.commit()
559 560 # we added that repo just now, and make sure we updated server info
560 561 if db_repo.repo_type == 'git':
561 562 git_repo = db_repo.scm_instance()
562 563 # update repository server-info
563 564 log.debug('Running update server info')
564 565 git_repo._update_server_info()
565 566
566 567 db_repo.update_commit_cache()
567 568
568 569 config = db_repo._config
569 570 config.set('extensions', 'largefiles', '')
570 571 repo = db_repo.scm_instance(config=config)
571 572 repo.install_hooks()
572 573
573 574 removed = []
574 575 if remove_obsolete:
575 576 # remove from database those repositories that are not in the filesystem
576 577 for repo in sa.query(Repository).all():
577 578 if repo.repo_name not in initial_repo_list.keys():
578 579 log.debug("Removing non-existing repository found in db `%s`",
579 580 repo.repo_name)
580 581 try:
581 582 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
582 583 sa.commit()
583 584 removed.append(repo.repo_name)
584 585 except Exception:
585 586 # don't hold further removals on error
586 587 log.error(traceback.format_exc())
587 588 sa.rollback()
588 589
589 590 def splitter(full_repo_name):
590 591 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
591 592 gr_name = None
592 593 if len(_parts) == 2:
593 594 gr_name = _parts[0]
594 595 return gr_name
595 596
596 597 initial_repo_group_list = [splitter(x) for x in
597 598 initial_repo_list.keys() if splitter(x)]
598 599
599 600 # remove from database those repository groups that are not in the
600 601 # filesystem due to parent child relationships we need to delete them
601 602 # in a specific order of most nested first
602 603 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
603 604 nested_sort = lambda gr: len(gr.split('/'))
604 605 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
605 606 if group_name not in initial_repo_group_list:
606 607 repo_group = RepoGroup.get_by_group_name(group_name)
607 608 if (repo_group.children.all() or
608 609 not RepoGroupModel().check_exist_filesystem(
609 610 group_name=group_name, exc_on_failure=False)):
610 611 continue
611 612
612 613 log.info(
613 614 'Removing non-existing repository group found in db `%s`',
614 615 group_name)
615 616 try:
616 617 RepoGroupModel(sa).delete(group_name, fs_remove=False)
617 618 sa.commit()
618 619 removed.append(group_name)
619 620 except Exception:
620 621 # don't hold further removals on error
621 622 log.exception(
622 623 'Unable to remove repository group `%s`',
623 624 group_name)
624 625 sa.rollback()
625 626 raise
626 627
627 628 return added, removed
628 629
629 630
630 631 def load_rcextensions(root_path):
631 632 import rhodecode
632 633 from rhodecode.config import conf
633 634
634 635 path = os.path.join(root_path)
635 636 sys.path.append(path)
636 637 try:
637 638 rcextensions = __import__('rcextensions')
638 639 except ImportError:
639 640 log.warn('Unable to load rcextensions from %s', path)
640 641 rcextensions = None
641 642
642 643 if rcextensions:
643 644 log.debug('Found rcextensions module loaded %s...', rcextensions)
644 645 rhodecode.EXTENSIONS = rcextensions
645 646
646 647 # Additional mappings that are not present in the pygments lexers
647 648 conf.LANGUAGES_EXTENSIONS_MAP.update(
648 649 getattr(rhodecode.EXTENSIONS, 'EXTRA_MAPPINGS', {}))
649 650
650 651
651 652 def get_custom_lexer(extension):
652 653 """
653 654 returns a custom lexer if it is defined in rcextensions module, or None
654 655 if there's no custom lexer defined
655 656 """
656 657 import rhodecode
657 658 from pygments import lexers
658 659
659 660 # custom override made by RhodeCode
660 661 if extension in ['mako']:
661 662 return lexers.get_lexer_by_name('html+mako')
662 663
663 664 # check if we didn't define this extension as other lexer
664 665 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
665 666 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
666 667 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
667 668 return lexers.get_lexer_by_name(_lexer_name)
668 669
669 670
670 671 #==============================================================================
671 672 # TEST FUNCTIONS AND CREATORS
672 673 #==============================================================================
673 674 def create_test_index(repo_location, config):
674 675 """
675 676 Makes default test index.
676 677 """
677 678 import rc_testdata
678 679
679 680 rc_testdata.extract_search_index(
680 681 'vcs_search_index', os.path.dirname(config['search.location']))
681 682
682 683
683 684 def create_test_directory(test_path):
684 685 """
685 686 Create test directory if it doesn't exist.
686 687 """
687 688 if not os.path.isdir(test_path):
688 689 log.debug('Creating testdir %s', test_path)
689 690 os.makedirs(test_path)
690 691
691 692
692 693 def create_test_database(test_path, config):
693 694 """
694 695 Makes a fresh database.
695 696 """
696 697 from rhodecode.lib.db_manage import DbManage
697 698
698 699 # PART ONE create db
699 700 dbconf = config['sqlalchemy.db1.url']
700 701 log.debug('making test db %s', dbconf)
701 702
702 703 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
703 704 tests=True, cli_args={'force_ask': True})
704 705 dbmanage.create_tables(override=True)
705 706 dbmanage.set_db_version()
706 707 # for tests dynamically set new root paths based on generated content
707 708 dbmanage.create_settings(dbmanage.config_prompt(test_path))
708 709 dbmanage.create_default_user()
709 710 dbmanage.create_test_admin_and_users()
710 711 dbmanage.create_permissions()
711 712 dbmanage.populate_default_permissions()
712 713 Session().commit()
713 714
714 715
715 716 def create_test_repositories(test_path, config):
716 717 """
717 718 Creates test repositories in the temporary directory. Repositories are
718 719 extracted from archives within the rc_testdata package.
719 720 """
720 721 import rc_testdata
721 722 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
722 723
723 724 log.debug('making test vcs repositories')
724 725
725 726 idx_path = config['search.location']
726 727 data_path = config['cache_dir']
727 728
728 729 # clean index and data
729 730 if idx_path and os.path.exists(idx_path):
730 731 log.debug('remove %s', idx_path)
731 732 shutil.rmtree(idx_path)
732 733
733 734 if data_path and os.path.exists(data_path):
734 735 log.debug('remove %s', data_path)
735 736 shutil.rmtree(data_path)
736 737
737 738 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
738 739 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
739 740
740 741 # Note: Subversion is in the process of being integrated with the system,
741 742 # until we have a properly packed version of the test svn repository, this
742 743 # tries to copy over the repo from a package "rc_testdata"
743 744 svn_repo_path = rc_testdata.get_svn_repo_archive()
744 745 with tarfile.open(svn_repo_path) as tar:
745 746 tar.extractall(jn(test_path, SVN_REPO))
746 747
747 748
748 749 def password_changed(auth_user, session):
749 750 # Never report password change in case of default user or anonymous user.
750 751 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
751 752 return False
752 753
753 754 password_hash = md5(auth_user.password) if auth_user.password else None
754 755 rhodecode_user = session.get('rhodecode_user', {})
755 756 session_password_hash = rhodecode_user.get('password', '')
756 757 return password_hash != session_password_hash
757 758
758 759
759 760 def read_opensource_licenses():
760 761 global _license_cache
761 762
762 763 if not _license_cache:
763 764 licenses = pkg_resources.resource_string(
764 765 'rhodecode', 'config/licenses.json')
765 766 _license_cache = json.loads(licenses)
766 767
767 768 return _license_cache
768 769
769 770
770 771 def generate_platform_uuid():
771 772 """
772 773 Generates platform UUID based on it's name
773 774 """
774 775 import platform
775 776
776 777 try:
777 778 uuid_list = [platform.platform()]
778 779 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
779 780 except Exception as e:
780 781 log.error('Failed to generate host uuid: %s', e)
781 782 return 'UNDEFINED'
@@ -1,845 +1,886 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import hashlib
23 23 import logging
24 24 from collections import namedtuple
25 25 from functools import wraps
26 26 import bleach
27 27
28 28 from rhodecode.lib import rc_cache
29 29 from rhodecode.lib.utils2 import (
30 30 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
31 31 from rhodecode.lib.vcs.backends import base
32 32 from rhodecode.model import BaseModel
33 33 from rhodecode.model.db import (
34 34 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting, CacheKey)
35 35 from rhodecode.model.meta import Session
36 36
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 UiSetting = namedtuple(
42 42 'UiSetting', ['section', 'key', 'value', 'active'])
43 43
44 44 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
45 45
46 46
47 47 class SettingNotFound(Exception):
48 48 def __init__(self, setting_id):
49 49 msg = 'Setting `{}` is not found'.format(setting_id)
50 50 super(SettingNotFound, self).__init__(msg)
51 51
52 52
53 53 class SettingsModel(BaseModel):
54 54 BUILTIN_HOOKS = (
55 55 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
56 56 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
57 57 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
58 58 RhodeCodeUi.HOOK_PUSH_KEY,)
59 59 HOOKS_SECTION = 'hooks'
60 60
61 61 def __init__(self, sa=None, repo=None):
62 62 self.repo = repo
63 63 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
64 64 self.SettingsDbModel = (
65 65 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
66 66 super(SettingsModel, self).__init__(sa)
67 67
68 68 def get_ui_by_key(self, key):
69 69 q = self.UiDbModel.query()
70 70 q = q.filter(self.UiDbModel.ui_key == key)
71 71 q = self._filter_by_repo(RepoRhodeCodeUi, q)
72 72 return q.scalar()
73 73
74 74 def get_ui_by_section(self, section):
75 75 q = self.UiDbModel.query()
76 76 q = q.filter(self.UiDbModel.ui_section == section)
77 77 q = self._filter_by_repo(RepoRhodeCodeUi, q)
78 78 return q.all()
79 79
80 80 def get_ui_by_section_and_key(self, section, key):
81 81 q = self.UiDbModel.query()
82 82 q = q.filter(self.UiDbModel.ui_section == section)
83 83 q = q.filter(self.UiDbModel.ui_key == key)
84 84 q = self._filter_by_repo(RepoRhodeCodeUi, q)
85 85 return q.scalar()
86 86
87 87 def get_ui(self, section=None, key=None):
88 88 q = self.UiDbModel.query()
89 89 q = self._filter_by_repo(RepoRhodeCodeUi, q)
90 90
91 91 if section:
92 92 q = q.filter(self.UiDbModel.ui_section == section)
93 93 if key:
94 94 q = q.filter(self.UiDbModel.ui_key == key)
95 95
96 96 # TODO: mikhail: add caching
97 97 result = [
98 98 UiSetting(
99 99 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
100 100 value=safe_str(r.ui_value), active=r.ui_active
101 101 )
102 102 for r in q.all()
103 103 ]
104 104 return result
105 105
106 106 def get_builtin_hooks(self):
107 107 q = self.UiDbModel.query()
108 108 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
109 109 return self._get_hooks(q)
110 110
111 111 def get_custom_hooks(self):
112 112 q = self.UiDbModel.query()
113 113 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
114 114 return self._get_hooks(q)
115 115
116 116 def create_ui_section_value(self, section, val, key=None, active=True):
117 117 new_ui = self.UiDbModel()
118 118 new_ui.ui_section = section
119 119 new_ui.ui_value = val
120 120 new_ui.ui_active = active
121 121
122 repository_id = ''
122 123 if self.repo:
123 124 repo = self._get_repo(self.repo)
124 125 repository_id = repo.repo_id
125 126 new_ui.repository_id = repository_id
126 127
127 128 if not key:
128 129 # keys are unique so they need appended info
129 130 if self.repo:
130 131 key = hashlib.sha1(
131 132 '{}{}{}'.format(section, val, repository_id)).hexdigest()
132 133 else:
133 134 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
134 135
135 136 new_ui.ui_key = key
136 137
137 138 Session().add(new_ui)
138 139 return new_ui
139 140
140 141 def create_or_update_hook(self, key, value):
141 142 ui = (
142 143 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
143 144 self.UiDbModel())
144 145 ui.ui_section = self.HOOKS_SECTION
145 146 ui.ui_active = True
146 147 ui.ui_key = key
147 148 ui.ui_value = value
148 149
149 150 if self.repo:
150 151 repo = self._get_repo(self.repo)
151 152 repository_id = repo.repo_id
152 153 ui.repository_id = repository_id
153 154
154 155 Session().add(ui)
155 156 return ui
156 157
157 158 def delete_ui(self, id_):
158 159 ui = self.UiDbModel.get(id_)
159 160 if not ui:
160 161 raise SettingNotFound(id_)
161 162 Session().delete(ui)
162 163
163 164 def get_setting_by_name(self, name):
164 165 q = self._get_settings_query()
165 166 q = q.filter(self.SettingsDbModel.app_settings_name == name)
166 167 return q.scalar()
167 168
168 169 def create_or_update_setting(
169 170 self, name, val=Optional(''), type_=Optional('unicode')):
170 171 """
171 172 Creates or updates RhodeCode setting. If updates is triggered it will
172 173 only update parameters that are explicityl set Optional instance will
173 174 be skipped
174 175
175 176 :param name:
176 177 :param val:
177 178 :param type_:
178 179 :return:
179 180 """
180 181
181 182 res = self.get_setting_by_name(name)
182 183 repo = self._get_repo(self.repo) if self.repo else None
183 184
184 185 if not res:
185 186 val = Optional.extract(val)
186 187 type_ = Optional.extract(type_)
187 188
188 189 args = (
189 190 (repo.repo_id, name, val, type_)
190 191 if repo else (name, val, type_))
191 192 res = self.SettingsDbModel(*args)
192 193
193 194 else:
194 195 if self.repo:
195 196 res.repository_id = repo.repo_id
196 197
197 198 res.app_settings_name = name
198 199 if not isinstance(type_, Optional):
199 200 # update if set
200 201 res.app_settings_type = type_
201 202 if not isinstance(val, Optional):
202 203 # update if set
203 204 res.app_settings_value = val
204 205
205 206 Session().add(res)
206 207 return res
207 208
208 209 def invalidate_settings_cache(self):
209 210 invalidation_namespace = CacheKey.SETTINGS_INVALIDATION_NAMESPACE
210 211 CacheKey.set_invalidate(invalidation_namespace)
211 212
212 213 def get_all_settings(self, cache=False):
213 214 region = rc_cache.get_or_create_region('sql_cache_short')
214 215 invalidation_namespace = CacheKey.SETTINGS_INVALIDATION_NAMESPACE
215 216
216 217 @region.conditional_cache_on_arguments(condition=cache)
217 218 def _get_all_settings(name, key):
218 219 q = self._get_settings_query()
219 220 if not q:
220 221 raise Exception('Could not get application settings !')
221 222
222 223 settings = {
223 224 'rhodecode_' + result.app_settings_name: result.app_settings_value
224 225 for result in q
225 226 }
226 227 return settings
227 228
228 229 repo = self._get_repo(self.repo) if self.repo else None
229 230 key = "settings_repo.{}".format(repo.repo_id) if repo else "settings_app"
230 231
231 232 inv_context_manager = rc_cache.InvalidationContext(
232 233 uid='cache_settings', invalidation_namespace=invalidation_namespace)
233 234 with inv_context_manager as invalidation_context:
234 235 # check for stored invalidation signal, and maybe purge the cache
235 236 # before computing it again
236 237 if invalidation_context.should_invalidate():
237 238 # NOTE:(marcink) we flush the whole sql_cache_short region, because it
238 239 # reads different settings etc. It's little too much but those caches
239 240 # are anyway very short lived and it's a safest way.
240 241 region = rc_cache.get_or_create_region('sql_cache_short')
241 242 region.invalidate()
242 243
243 244 result = _get_all_settings('rhodecode_settings', key)
244 245 log.debug('Fetching app settings for key: %s took: %.3fs', key,
245 246 inv_context_manager.compute_time)
246 247
247 248 return result
248 249
249 250 def get_auth_settings(self):
250 251 q = self._get_settings_query()
251 252 q = q.filter(
252 253 self.SettingsDbModel.app_settings_name.startswith('auth_'))
253 254 rows = q.all()
254 255 auth_settings = {
255 256 row.app_settings_name: row.app_settings_value for row in rows}
256 257 return auth_settings
257 258
258 259 def get_auth_plugins(self):
259 260 auth_plugins = self.get_setting_by_name("auth_plugins")
260 261 return auth_plugins.app_settings_value
261 262
262 263 def get_default_repo_settings(self, strip_prefix=False):
263 264 q = self._get_settings_query()
264 265 q = q.filter(
265 266 self.SettingsDbModel.app_settings_name.startswith('default_'))
266 267 rows = q.all()
267 268
268 269 result = {}
269 270 for row in rows:
270 271 key = row.app_settings_name
271 272 if strip_prefix:
272 273 key = remove_prefix(key, prefix='default_')
273 274 result.update({key: row.app_settings_value})
274 275 return result
275 276
276 277 def get_repo(self):
277 278 repo = self._get_repo(self.repo)
278 279 if not repo:
279 280 raise Exception(
280 281 'Repository `{}` cannot be found inside the database'.format(
281 282 self.repo))
282 283 return repo
283 284
284 285 def _filter_by_repo(self, model, query):
285 286 if self.repo:
286 287 repo = self.get_repo()
287 288 query = query.filter(model.repository_id == repo.repo_id)
288 289 return query
289 290
290 291 def _get_hooks(self, query):
291 292 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
292 293 query = self._filter_by_repo(RepoRhodeCodeUi, query)
293 294 return query.all()
294 295
295 296 def _get_settings_query(self):
296 297 q = self.SettingsDbModel.query()
297 298 return self._filter_by_repo(RepoRhodeCodeSetting, q)
298 299
299 300 def list_enabled_social_plugins(self, settings):
300 301 enabled = []
301 302 for plug in SOCIAL_PLUGINS_LIST:
302 303 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
303 304 )):
304 305 enabled.append(plug)
305 306 return enabled
306 307
307 308
308 309 def assert_repo_settings(func):
309 310 @wraps(func)
310 311 def _wrapper(self, *args, **kwargs):
311 312 if not self.repo_settings:
312 313 raise Exception('Repository is not specified')
313 314 return func(self, *args, **kwargs)
314 315 return _wrapper
315 316
316 317
317 318 class IssueTrackerSettingsModel(object):
318 319 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
319 320 SETTINGS_PREFIX = 'issuetracker_'
320 321
321 322 def __init__(self, sa=None, repo=None):
322 323 self.global_settings = SettingsModel(sa=sa)
323 324 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
324 325
325 326 @property
326 327 def inherit_global_settings(self):
327 328 if not self.repo_settings:
328 329 return True
329 330 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
330 331 return setting.app_settings_value if setting else True
331 332
332 333 @inherit_global_settings.setter
333 334 def inherit_global_settings(self, value):
334 335 if self.repo_settings:
335 336 settings = self.repo_settings.create_or_update_setting(
336 337 self.INHERIT_SETTINGS, value, type_='bool')
337 338 Session().add(settings)
338 339
339 340 def _get_keyname(self, key, uid, prefix=''):
340 341 return '{0}{1}{2}_{3}'.format(
341 342 prefix, self.SETTINGS_PREFIX, key, uid)
342 343
343 344 def _make_dict_for_settings(self, qs):
344 345 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
345 346
346 347 issuetracker_entries = {}
347 348 # create keys
348 349 for k, v in qs.items():
349 350 if k.startswith(prefix_match):
350 351 uid = k[len(prefix_match):]
351 352 issuetracker_entries[uid] = None
352 353
353 354 def url_cleaner(input_str):
354 355 input_str = input_str.replace('"', '').replace("'", '')
355 356 input_str = bleach.clean(input_str, strip=True)
356 357 return input_str
357 358
358 359 # populate
359 360 for uid in issuetracker_entries:
360 361 url_data = qs.get(self._get_keyname('url', uid, 'rhodecode_'))
361 362
362 363 issuetracker_entries[uid] = AttributeDict({
363 364 'pat': qs.get(
364 365 self._get_keyname('pat', uid, 'rhodecode_')),
365 366 'url': url_cleaner(
366 367 qs.get(self._get_keyname('url', uid, 'rhodecode_')) or ''),
367 368 'pref': bleach.clean(
368 369 qs.get(self._get_keyname('pref', uid, 'rhodecode_')) or ''),
369 370 'desc': qs.get(
370 371 self._get_keyname('desc', uid, 'rhodecode_')),
371 372 })
372 373
373 374 return issuetracker_entries
374 375
375 376 def get_global_settings(self, cache=False):
376 377 """
377 378 Returns list of global issue tracker settings
378 379 """
379 380 defaults = self.global_settings.get_all_settings(cache=cache)
380 381 settings = self._make_dict_for_settings(defaults)
381 382 return settings
382 383
383 384 def get_repo_settings(self, cache=False):
384 385 """
385 386 Returns list of issue tracker settings per repository
386 387 """
387 388 if not self.repo_settings:
388 389 raise Exception('Repository is not specified')
389 390 all_settings = self.repo_settings.get_all_settings(cache=cache)
390 391 settings = self._make_dict_for_settings(all_settings)
391 392 return settings
392 393
393 394 def get_settings(self, cache=False):
394 395 if self.inherit_global_settings:
395 396 return self.get_global_settings(cache=cache)
396 397 else:
397 398 return self.get_repo_settings(cache=cache)
398 399
399 400 def delete_entries(self, uid):
400 401 if self.repo_settings:
401 402 all_patterns = self.get_repo_settings()
402 403 settings_model = self.repo_settings
403 404 else:
404 405 all_patterns = self.get_global_settings()
405 406 settings_model = self.global_settings
406 407 entries = all_patterns.get(uid, [])
407 408
408 409 for del_key in entries:
409 410 setting_name = self._get_keyname(del_key, uid)
410 411 entry = settings_model.get_setting_by_name(setting_name)
411 412 if entry:
412 413 Session().delete(entry)
413 414
414 415 Session().commit()
415 416
416 417 def create_or_update_setting(
417 418 self, name, val=Optional(''), type_=Optional('unicode')):
418 419 if self.repo_settings:
419 420 setting = self.repo_settings.create_or_update_setting(
420 421 name, val, type_)
421 422 else:
422 423 setting = self.global_settings.create_or_update_setting(
423 424 name, val, type_)
424 425 return setting
425 426
426 427
427 428 class VcsSettingsModel(object):
428 429
429 430 INHERIT_SETTINGS = 'inherit_vcs_settings'
430 431 GENERAL_SETTINGS = (
431 432 'use_outdated_comments',
432 433 'pr_merge_enabled',
433 434 'hg_use_rebase_for_merging',
434 435 'hg_close_branch_before_merging',
435 436 'git_use_rebase_for_merging',
436 437 'git_close_branch_before_merging',
437 438 'diff_cache',
438 439 )
439 440
440 441 HOOKS_SETTINGS = (
441 442 ('hooks', 'changegroup.repo_size'),
442 443 ('hooks', 'changegroup.push_logger'),
443 ('hooks', 'outgoing.pull_logger'),)
444 ('hooks', 'outgoing.pull_logger'),
445 )
444 446 HG_SETTINGS = (
445 447 ('extensions', 'largefiles'),
446 448 ('phases', 'publish'),
447 ('extensions', 'evolve'),)
449 ('extensions', 'evolve'),
450 ('extensions', 'topic'),
451 ('experimental', 'evolution'),
452 )
448 453 GIT_SETTINGS = (
449 ('vcs_git_lfs', 'enabled'),)
454 ('vcs_git_lfs', 'enabled'),
455 )
450 456 GLOBAL_HG_SETTINGS = (
451 457 ('extensions', 'largefiles'),
452 458 ('largefiles', 'usercache'),
453 459 ('phases', 'publish'),
454 460 ('extensions', 'hgsubversion'),
455 ('extensions', 'evolve'),)
461 ('extensions', 'evolve'),
462 ('extensions', 'topic'),
463 ('experimental', 'evolution'),
464 )
465
456 466 GLOBAL_GIT_SETTINGS = (
457 467 ('vcs_git_lfs', 'enabled'),
458 ('vcs_git_lfs', 'store_location'))
468 ('vcs_git_lfs', 'store_location')
469 )
459 470
460 471 GLOBAL_SVN_SETTINGS = (
461 472 ('vcs_svn_proxy', 'http_requests_enabled'),
462 ('vcs_svn_proxy', 'http_server_url'))
473 ('vcs_svn_proxy', 'http_server_url')
474 )
463 475
464 476 SVN_BRANCH_SECTION = 'vcs_svn_branch'
465 477 SVN_TAG_SECTION = 'vcs_svn_tag'
466 478 SSL_SETTING = ('web', 'push_ssl')
467 479 PATH_SETTING = ('paths', '/')
468 480
469 481 def __init__(self, sa=None, repo=None):
470 482 self.global_settings = SettingsModel(sa=sa)
471 483 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
472 484 self._ui_settings = (
473 485 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
474 486 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
475 487
476 488 @property
477 489 @assert_repo_settings
478 490 def inherit_global_settings(self):
479 491 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
480 492 return setting.app_settings_value if setting else True
481 493
482 494 @inherit_global_settings.setter
483 495 @assert_repo_settings
484 496 def inherit_global_settings(self, value):
485 497 self.repo_settings.create_or_update_setting(
486 498 self.INHERIT_SETTINGS, value, type_='bool')
487 499
488 500 def get_global_svn_branch_patterns(self):
489 501 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
490 502
491 503 @assert_repo_settings
492 504 def get_repo_svn_branch_patterns(self):
493 505 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
494 506
495 507 def get_global_svn_tag_patterns(self):
496 508 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
497 509
498 510 @assert_repo_settings
499 511 def get_repo_svn_tag_patterns(self):
500 512 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
501 513
502 514 def get_global_settings(self):
503 515 return self._collect_all_settings(global_=True)
504 516
505 517 @assert_repo_settings
506 518 def get_repo_settings(self):
507 519 return self._collect_all_settings(global_=False)
508 520
509 521 @assert_repo_settings
510 522 def create_or_update_repo_settings(
511 523 self, data, inherit_global_settings=False):
512 524 from rhodecode.model.scm import ScmModel
513 525
514 526 self.inherit_global_settings = inherit_global_settings
515 527
516 528 repo = self.repo_settings.get_repo()
517 529 if not inherit_global_settings:
518 530 if repo.repo_type == 'svn':
519 531 self.create_repo_svn_settings(data)
520 532 else:
521 533 self.create_or_update_repo_hook_settings(data)
522 534 self.create_or_update_repo_pr_settings(data)
523 535
524 536 if repo.repo_type == 'hg':
525 537 self.create_or_update_repo_hg_settings(data)
526 538
527 539 if repo.repo_type == 'git':
528 540 self.create_or_update_repo_git_settings(data)
529 541
530 542 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
531 543
532 544 @assert_repo_settings
533 545 def create_or_update_repo_hook_settings(self, data):
534 546 for section, key in self.HOOKS_SETTINGS:
535 547 data_key = self._get_form_ui_key(section, key)
536 548 if data_key not in data:
537 549 raise ValueError(
538 550 'The given data does not contain {} key'.format(data_key))
539 551
540 552 active = data.get(data_key)
541 553 repo_setting = self.repo_settings.get_ui_by_section_and_key(
542 554 section, key)
543 555 if not repo_setting:
544 556 global_setting = self.global_settings.\
545 557 get_ui_by_section_and_key(section, key)
546 558 self.repo_settings.create_ui_section_value(
547 559 section, global_setting.ui_value, key=key, active=active)
548 560 else:
549 561 repo_setting.ui_active = active
550 562 Session().add(repo_setting)
551 563
552 564 def update_global_hook_settings(self, data):
553 565 for section, key in self.HOOKS_SETTINGS:
554 566 data_key = self._get_form_ui_key(section, key)
555 567 if data_key not in data:
556 568 raise ValueError(
557 569 'The given data does not contain {} key'.format(data_key))
558 570 active = data.get(data_key)
559 571 repo_setting = self.global_settings.get_ui_by_section_and_key(
560 572 section, key)
561 573 repo_setting.ui_active = active
562 574 Session().add(repo_setting)
563 575
564 576 @assert_repo_settings
565 577 def create_or_update_repo_pr_settings(self, data):
566 578 return self._create_or_update_general_settings(
567 579 self.repo_settings, data)
568 580
569 581 def create_or_update_global_pr_settings(self, data):
570 582 return self._create_or_update_general_settings(
571 583 self.global_settings, data)
572 584
573 585 @assert_repo_settings
574 586 def create_repo_svn_settings(self, data):
575 587 return self._create_svn_settings(self.repo_settings, data)
576 588
589 def _set_evolution(self, settings, is_enabled):
590 if is_enabled:
591 # if evolve is active set evolution=all
592
593 self._create_or_update_ui(
594 settings, *('experimental', 'evolution'), value='all',
595 active=True)
596 self._create_or_update_ui(
597 settings, *('experimental', 'evolution.exchange'), value='yes',
598 active=True)
599 # if evolve is active set topics server support
600 self._create_or_update_ui(
601 settings, *('extensions', 'topic'), value='',
602 active=True)
603
604 else:
605 self._create_or_update_ui(
606 settings, *('experimental', 'evolution'), value='',
607 active=False)
608 self._create_or_update_ui(
609 settings, *('experimental', 'evolution.exchange'), value='no',
610 active=False)
611 self._create_or_update_ui(
612 settings, *('extensions', 'topic'), value='',
613 active=False)
614
577 615 @assert_repo_settings
578 616 def create_or_update_repo_hg_settings(self, data):
579 617 largefiles, phases, evolve = \
580 self.HG_SETTINGS
618 self.HG_SETTINGS[:3]
581 619 largefiles_key, phases_key, evolve_key = \
582 self._get_settings_keys(self.HG_SETTINGS, data)
620 self._get_settings_keys(self.HG_SETTINGS[:3], data)
583 621
584 622 self._create_or_update_ui(
585 623 self.repo_settings, *largefiles, value='',
586 624 active=data[largefiles_key])
587 625 self._create_or_update_ui(
588 626 self.repo_settings, *evolve, value='',
589 627 active=data[evolve_key])
628 self._set_evolution(self.repo_settings, is_enabled=data[evolve_key])
629
590 630 self._create_or_update_ui(
591 631 self.repo_settings, *phases, value=safe_str(data[phases_key]))
592 632
593 633 def create_or_update_global_hg_settings(self, data):
594 634 largefiles, largefiles_store, phases, hgsubversion, evolve \
595 = self.GLOBAL_HG_SETTINGS
635 = self.GLOBAL_HG_SETTINGS[:5]
596 636 largefiles_key, largefiles_store_key, phases_key, subversion_key, evolve_key \
597 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS, data)
637 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS[:5], data)
598 638
599 639 self._create_or_update_ui(
600 640 self.global_settings, *largefiles, value='',
601 641 active=data[largefiles_key])
602 642 self._create_or_update_ui(
603 self.global_settings, *largefiles_store,
604 value=data[largefiles_store_key])
643 self.global_settings, *largefiles_store, value=data[largefiles_store_key])
605 644 self._create_or_update_ui(
606 645 self.global_settings, *phases, value=safe_str(data[phases_key]))
607 646 self._create_or_update_ui(
608 647 self.global_settings, *hgsubversion, active=data[subversion_key])
609 648 self._create_or_update_ui(
610 649 self.global_settings, *evolve, value='',
611 650 active=data[evolve_key])
651 self._set_evolution(self.global_settings, is_enabled=data[evolve_key])
612 652
613 653 def create_or_update_repo_git_settings(self, data):
614 # NOTE(marcink): # comma make unpack work properly
654 # NOTE(marcink): # comma makes unpack work properly
615 655 lfs_enabled, \
616 656 = self.GIT_SETTINGS
617 657
618 658 lfs_enabled_key, \
619 659 = self._get_settings_keys(self.GIT_SETTINGS, data)
620 660
621 661 self._create_or_update_ui(
622 662 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
623 663 active=data[lfs_enabled_key])
624 664
625 665 def create_or_update_global_git_settings(self, data):
626 666 lfs_enabled, lfs_store_location \
627 667 = self.GLOBAL_GIT_SETTINGS
628 668 lfs_enabled_key, lfs_store_location_key \
629 669 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
630 670
631 671 self._create_or_update_ui(
632 672 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
633 673 active=data[lfs_enabled_key])
634 674 self._create_or_update_ui(
635 675 self.global_settings, *lfs_store_location,
636 676 value=data[lfs_store_location_key])
637 677
638 678 def create_or_update_global_svn_settings(self, data):
639 679 # branch/tags patterns
640 680 self._create_svn_settings(self.global_settings, data)
641 681
642 682 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
643 683 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
644 684 self.GLOBAL_SVN_SETTINGS, data)
645 685
646 686 self._create_or_update_ui(
647 687 self.global_settings, *http_requests_enabled,
648 688 value=safe_str(data[http_requests_enabled_key]))
649 689 self._create_or_update_ui(
650 690 self.global_settings, *http_server_url,
651 691 value=data[http_server_url_key])
652 692
653 693 def update_global_ssl_setting(self, value):
654 694 self._create_or_update_ui(
655 695 self.global_settings, *self.SSL_SETTING, value=value)
656 696
657 697 def update_global_path_setting(self, value):
658 698 self._create_or_update_ui(
659 699 self.global_settings, *self.PATH_SETTING, value=value)
660 700
661 701 @assert_repo_settings
662 702 def delete_repo_svn_pattern(self, id_):
663 703 ui = self.repo_settings.UiDbModel.get(id_)
664 704 if ui and ui.repository.repo_name == self.repo_settings.repo:
665 705 # only delete if it's the same repo as initialized settings
666 706 self.repo_settings.delete_ui(id_)
667 707 else:
668 708 # raise error as if we wouldn't find this option
669 709 self.repo_settings.delete_ui(-1)
670 710
671 711 def delete_global_svn_pattern(self, id_):
672 712 self.global_settings.delete_ui(id_)
673 713
674 714 @assert_repo_settings
675 715 def get_repo_ui_settings(self, section=None, key=None):
676 716 global_uis = self.global_settings.get_ui(section, key)
677 717 repo_uis = self.repo_settings.get_ui(section, key)
718
678 719 filtered_repo_uis = self._filter_ui_settings(repo_uis)
679 720 filtered_repo_uis_keys = [
680 721 (s.section, s.key) for s in filtered_repo_uis]
681 722
682 723 def _is_global_ui_filtered(ui):
683 724 return (
684 725 (ui.section, ui.key) in filtered_repo_uis_keys
685 726 or ui.section in self._svn_sections)
686 727
687 728 filtered_global_uis = [
688 729 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
689 730
690 731 return filtered_global_uis + filtered_repo_uis
691 732
692 733 def get_global_ui_settings(self, section=None, key=None):
693 734 return self.global_settings.get_ui(section, key)
694 735
695 736 def get_ui_settings_as_config_obj(self, section=None, key=None):
696 737 config = base.Config()
697 738
698 739 ui_settings = self.get_ui_settings(section=section, key=key)
699 740
700 741 for entry in ui_settings:
701 742 config.set(entry.section, entry.key, entry.value)
702 743
703 744 return config
704 745
705 746 def get_ui_settings(self, section=None, key=None):
706 747 if not self.repo_settings or self.inherit_global_settings:
707 748 return self.get_global_ui_settings(section, key)
708 749 else:
709 750 return self.get_repo_ui_settings(section, key)
710 751
711 752 def get_svn_patterns(self, section=None):
712 753 if not self.repo_settings:
713 754 return self.get_global_ui_settings(section)
714 755 else:
715 756 return self.get_repo_ui_settings(section)
716 757
717 758 @assert_repo_settings
718 759 def get_repo_general_settings(self):
719 760 global_settings = self.global_settings.get_all_settings()
720 761 repo_settings = self.repo_settings.get_all_settings()
721 762 filtered_repo_settings = self._filter_general_settings(repo_settings)
722 763 global_settings.update(filtered_repo_settings)
723 764 return global_settings
724 765
725 766 def get_global_general_settings(self):
726 767 return self.global_settings.get_all_settings()
727 768
728 769 def get_general_settings(self):
729 770 if not self.repo_settings or self.inherit_global_settings:
730 771 return self.get_global_general_settings()
731 772 else:
732 773 return self.get_repo_general_settings()
733 774
734 775 def get_repos_location(self):
735 776 return self.global_settings.get_ui_by_key('/').ui_value
736 777
737 778 def _filter_ui_settings(self, settings):
738 779 filtered_settings = [
739 780 s for s in settings if self._should_keep_setting(s)]
740 781 return filtered_settings
741 782
742 783 def _should_keep_setting(self, setting):
743 784 keep = (
744 785 (setting.section, setting.key) in self._ui_settings or
745 786 setting.section in self._svn_sections)
746 787 return keep
747 788
748 789 def _filter_general_settings(self, settings):
749 790 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
750 791 return {
751 792 k: settings[k]
752 793 for k in settings if k in keys}
753 794
754 795 def _collect_all_settings(self, global_=False):
755 796 settings = self.global_settings if global_ else self.repo_settings
756 797 result = {}
757 798
758 799 for section, key in self._ui_settings:
759 800 ui = settings.get_ui_by_section_and_key(section, key)
760 801 result_key = self._get_form_ui_key(section, key)
761 802
762 803 if ui:
763 804 if section in ('hooks', 'extensions'):
764 805 result[result_key] = ui.ui_active
765 806 elif result_key in ['vcs_git_lfs_enabled']:
766 807 result[result_key] = ui.ui_active
767 808 else:
768 809 result[result_key] = ui.ui_value
769 810
770 811 for name in self.GENERAL_SETTINGS:
771 812 setting = settings.get_setting_by_name(name)
772 813 if setting:
773 814 result_key = 'rhodecode_{}'.format(name)
774 815 result[result_key] = setting.app_settings_value
775 816
776 817 return result
777 818
778 819 def _get_form_ui_key(self, section, key):
779 820 return '{section}_{key}'.format(
780 821 section=section, key=key.replace('.', '_'))
781 822
782 823 def _create_or_update_ui(
783 824 self, settings, section, key, value=None, active=None):
784 825 ui = settings.get_ui_by_section_and_key(section, key)
785 826 if not ui:
786 827 active = True if active is None else active
787 828 settings.create_ui_section_value(
788 829 section, value, key=key, active=active)
789 830 else:
790 831 if active is not None:
791 832 ui.ui_active = active
792 833 if value is not None:
793 834 ui.ui_value = value
794 835 Session().add(ui)
795 836
796 837 def _create_svn_settings(self, settings, data):
797 838 svn_settings = {
798 839 'new_svn_branch': self.SVN_BRANCH_SECTION,
799 840 'new_svn_tag': self.SVN_TAG_SECTION
800 841 }
801 842 for key in svn_settings:
802 843 if data.get(key):
803 844 settings.create_ui_section_value(svn_settings[key], data[key])
804 845
805 846 def _create_or_update_general_settings(self, settings, data):
806 847 for name in self.GENERAL_SETTINGS:
807 848 data_key = 'rhodecode_{}'.format(name)
808 849 if data_key not in data:
809 850 raise ValueError(
810 851 'The given data does not contain {} key'.format(data_key))
811 852 setting = settings.create_or_update_setting(
812 853 name, data[data_key], 'bool')
813 854 Session().add(setting)
814 855
815 856 def _get_settings_keys(self, settings, data):
816 857 data_keys = [self._get_form_ui_key(*s) for s in settings]
817 858 for data_key in data_keys:
818 859 if data_key not in data:
819 860 raise ValueError(
820 861 'The given data does not contain {} key'.format(data_key))
821 862 return data_keys
822 863
823 864 def create_largeobjects_dirs_if_needed(self, repo_store_path):
824 865 """
825 866 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
826 867 does a repository scan if enabled in the settings.
827 868 """
828 869
829 870 from rhodecode.lib.vcs.backends.hg import largefiles_store
830 871 from rhodecode.lib.vcs.backends.git import lfs_store
831 872
832 873 paths = [
833 874 largefiles_store(repo_store_path),
834 875 lfs_store(repo_store_path)]
835 876
836 877 for path in paths:
837 878 if os.path.isdir(path):
838 879 continue
839 880 if os.path.isfile(path):
840 881 continue
841 882 # not a file nor dir, we try to create it
842 883 try:
843 884 os.makedirs(path)
844 885 except Exception:
845 886 log.warning('Failed to create largefiles dir:%s', path)
@@ -1,384 +1,384 b''
1 1 ## snippet for displaying vcs settings
2 2 ## usage:
3 3 ## <%namespace name="vcss" file="/base/vcssettings.mako"/>
4 4 ## ${vcss.vcs_settings_fields()}
5 5
6 6 <%def name="vcs_settings_fields(suffix='', svn_branch_patterns=None, svn_tag_patterns=None, repo_type=None, display_globals=False, allow_repo_location_change=False, **kwargs)">
7 7 % if display_globals:
8 8 <div class="panel panel-default">
9 9 <div class="panel-heading" id="general">
10 10 <h3 class="panel-title">${_('General')}<a class="permalink" href="#general"> ΒΆ</a></h3>
11 11 </div>
12 12 <div class="panel-body">
13 13 <div class="field">
14 14 <div class="checkbox">
15 15 ${h.checkbox('web_push_ssl' + suffix, 'True')}
16 16 <label for="web_push_ssl${suffix}">${_('Require SSL for vcs operations')}</label>
17 17 </div>
18 18 <div class="label">
19 19 <span class="help-block">${_('Activate to set RhodeCode to require SSL for pushing or pulling. If SSL certificate is missing it will return a HTTP Error 406: Not Acceptable.')}</span>
20 20 </div>
21 21 </div>
22 22 </div>
23 23 </div>
24 24 % endif
25 25
26 26 % if display_globals:
27 27 <div class="panel panel-default">
28 28 <div class="panel-heading" id="vcs-storage-options">
29 29 <h3 class="panel-title">${_('Main Storage Location')}<a class="permalink" href="#vcs-storage-options"> ΒΆ</a></h3>
30 30 </div>
31 31 <div class="panel-body">
32 32 <div class="field">
33 33 <div class="inputx locked_input">
34 34 %if allow_repo_location_change:
35 35 ${h.text('paths_root_path',size=59,readonly="readonly", class_="disabled")}
36 36 <span id="path_unlock" class="tooltip"
37 37 title="${h.tooltip(_('Click to unlock. You must restart RhodeCode in order to make this setting take effect.'))}">
38 38 <div class="btn btn-default lock_input_button"><i id="path_unlock_icon" class="icon-lock"></i></div>
39 39 </span>
40 40 %else:
41 41 ${_('Repository location change is disabled. You can enable this by changing the `allow_repo_location_change` inside .ini file.')}
42 42 ## form still requires this but we cannot internally change it anyway
43 43 ${h.hidden('paths_root_path',size=30,readonly="readonly", class_="disabled")}
44 44 %endif
45 45 </div>
46 46 </div>
47 47 <div class="label">
48 48 <span class="help-block">${_('Filesystem location where repositories should be stored. After changing this value a restart and rescan of the repository folder are required.')}</span>
49 49 </div>
50 50 </div>
51 51 </div>
52 52 % endif
53 53
54 54 % if display_globals or repo_type in ['git', 'hg']:
55 55 <div class="panel panel-default">
56 56 <div class="panel-heading" id="vcs-hooks-options">
57 57 <h3 class="panel-title">${_('Internal Hooks')}<a class="permalink" href="#vcs-hooks-options"> ΒΆ</a></h3>
58 58 </div>
59 59 <div class="panel-body">
60 60 <div class="field">
61 61 <div class="checkbox">
62 62 ${h.checkbox('hooks_changegroup_repo_size' + suffix, 'True', **kwargs)}
63 63 <label for="hooks_changegroup_repo_size${suffix}">${_('Show repository size after push')}</label>
64 64 </div>
65 65
66 66 <div class="label">
67 67 <span class="help-block">${_('Trigger a hook that calculates repository size after each push.')}</span>
68 68 </div>
69 69 <div class="checkbox">
70 70 ${h.checkbox('hooks_changegroup_push_logger' + suffix, 'True', **kwargs)}
71 71 <label for="hooks_changegroup_push_logger${suffix}">${_('Execute pre/post push hooks')}</label>
72 72 </div>
73 73 <div class="label">
74 74 <span class="help-block">${_('Execute Built in pre/post push hooks. This also executes rcextensions hooks.')}</span>
75 75 </div>
76 76 <div class="checkbox">
77 77 ${h.checkbox('hooks_outgoing_pull_logger' + suffix, 'True', **kwargs)}
78 78 <label for="hooks_outgoing_pull_logger${suffix}">${_('Execute pre/post pull hooks')}</label>
79 79 </div>
80 80 <div class="label">
81 81 <span class="help-block">${_('Execute Built in pre/post pull hooks. This also executes rcextensions hooks.')}</span>
82 82 </div>
83 83 </div>
84 84 </div>
85 85 </div>
86 86 % endif
87 87
88 88 % if display_globals or repo_type in ['hg']:
89 89 <div class="panel panel-default">
90 90 <div class="panel-heading" id="vcs-hg-options">
91 91 <h3 class="panel-title">${_('Mercurial Settings')}<a class="permalink" href="#vcs-hg-options"> ΒΆ</a></h3>
92 92 </div>
93 93 <div class="panel-body">
94 94 <div class="checkbox">
95 95 ${h.checkbox('extensions_largefiles' + suffix, 'True', **kwargs)}
96 96 <label for="extensions_largefiles${suffix}">${_('Enable largefiles extension')}</label>
97 97 </div>
98 98 <div class="label">
99 99 % if display_globals:
100 100 <span class="help-block">${_('Enable Largefiles extensions for all repositories.')}</span>
101 101 % else:
102 102 <span class="help-block">${_('Enable Largefiles extensions for this repository.')}</span>
103 103 % endif
104 104 </div>
105 105
106 106 % if display_globals:
107 107 <div class="field">
108 108 <div class="input">
109 109 ${h.text('largefiles_usercache' + suffix, size=59)}
110 110 </div>
111 111 </div>
112 112 <div class="label">
113 113 <span class="help-block">${_('Filesystem location where Mercurial largefile objects should be stored.')}</span>
114 114 </div>
115 115 % endif
116 116
117 117 <div class="checkbox">
118 118 ${h.checkbox('phases_publish' + suffix, 'True', **kwargs)}
119 119 <label for="phases_publish${suffix}">${_('Set repositories as publishing') if display_globals else _('Set repository as publishing')}</label>
120 120 </div>
121 121 <div class="label">
122 122 <span class="help-block">${_('When this is enabled all commits in the repository are seen as public commits by clients.')}</span>
123 123 </div>
124 124 % if display_globals:
125 125 <div class="checkbox">
126 126 ${h.checkbox('extensions_hgsubversion' + suffix,'True')}
127 127 <label for="extensions_hgsubversion${suffix}">${_('Enable hgsubversion extension')}</label>
128 128 </div>
129 129 <div class="label">
130 130 <span class="help-block">${_('Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type.')}</span>
131 131 </div>
132 132 % endif
133 133
134 134 <div class="checkbox">
135 135 ${h.checkbox('extensions_evolve' + suffix, 'True', **kwargs)}
136 <label for="extensions_evolve${suffix}">${_('Enable evolve extension')}</label>
136 <label for="extensions_evolve${suffix}">${_('Enable Evolve and Topic extension')}</label>
137 137 </div>
138 138 <div class="label">
139 139 % if display_globals:
140 <span class="help-block">${_('Enable evolve extension for all repositories.')}</span>
140 <span class="help-block">${_('Enable Evolve and Topic extensions for all repositories.')}</span>
141 141 % else:
142 <span class="help-block">${_('Enable evolve extension for this repository.')}</span>
142 <span class="help-block">${_('Enable Evolve and Topic extensions for this repository.')}</span>
143 143 % endif
144 144 </div>
145 145
146 146 </div>
147 147 </div>
148 148 % endif
149 149
150 150 % if display_globals or repo_type in ['git']:
151 151 <div class="panel panel-default">
152 152 <div class="panel-heading" id="vcs-git-options">
153 153 <h3 class="panel-title">${_('Git Settings')}<a class="permalink" href="#vcs-git-options"> ΒΆ</a></h3>
154 154 </div>
155 155 <div class="panel-body">
156 156 <div class="checkbox">
157 157 ${h.checkbox('vcs_git_lfs_enabled' + suffix, 'True', **kwargs)}
158 158 <label for="vcs_git_lfs_enabled${suffix}">${_('Enable lfs extension')}</label>
159 159 </div>
160 160 <div class="label">
161 161 % if display_globals:
162 162 <span class="help-block">${_('Enable lfs extensions for all repositories.')}</span>
163 163 % else:
164 164 <span class="help-block">${_('Enable lfs extensions for this repository.')}</span>
165 165 % endif
166 166 </div>
167 167
168 168 % if display_globals:
169 169 <div class="field">
170 170 <div class="input">
171 171 ${h.text('vcs_git_lfs_store_location' + suffix, size=59)}
172 172 </div>
173 173 </div>
174 174 <div class="label">
175 175 <span class="help-block">${_('Filesystem location where Git lfs objects should be stored.')}</span>
176 176 </div>
177 177 % endif
178 178 </div>
179 179 </div>
180 180 % endif
181 181
182 182
183 183 % if display_globals:
184 184 <div class="panel panel-default">
185 185 <div class="panel-heading" id="vcs-global-svn-options">
186 186 <h3 class="panel-title">${_('Global Subversion Settings')}<a class="permalink" href="#vcs-global-svn-options"> ΒΆ</a></h3>
187 187 </div>
188 188 <div class="panel-body">
189 189 <div class="field">
190 190 <div class="checkbox">
191 191 ${h.checkbox('vcs_svn_proxy_http_requests_enabled' + suffix, 'True', **kwargs)}
192 192 <label for="vcs_svn_proxy_http_requests_enabled${suffix}">${_('Proxy subversion HTTP requests')}</label>
193 193 </div>
194 194 <div class="label">
195 195 <span class="help-block">
196 196 ${_('Subversion HTTP Support. Enables communication with SVN over HTTP protocol.')}
197 197 <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('SVN Protocol setup Documentation')}</a>.
198 198 </span>
199 199 </div>
200 200 </div>
201 201 <div class="field">
202 202 <div class="label">
203 203 <label for="vcs_svn_proxy_http_server_url">${_('Subversion HTTP Server URL')}</label><br/>
204 204 </div>
205 205 <div class="input">
206 206 ${h.text('vcs_svn_proxy_http_server_url',size=59)}
207 207 % if c.svn_proxy_generate_config:
208 208 <span class="buttons">
209 209 <button class="btn btn-primary" id="vcs_svn_generate_cfg">${_('Generate Apache Config')}</button>
210 210 </span>
211 211 % endif
212 212 </div>
213 213 </div>
214 214 </div>
215 215 </div>
216 216 % endif
217 217
218 218 % if display_globals or repo_type in ['svn']:
219 219 <div class="panel panel-default">
220 220 <div class="panel-heading" id="vcs-svn-options">
221 221 <h3 class="panel-title">${_('Subversion Settings')}<a class="permalink" href="#vcs-svn-options"> ΒΆ</a></h3>
222 222 </div>
223 223 <div class="panel-body">
224 224 <div class="field">
225 225 <div class="content" >
226 226 <label>${_('Repository patterns')}</label><br/>
227 227 </div>
228 228 </div>
229 229 <div class="label">
230 230 <span class="help-block">${_('Patterns for identifying SVN branches and tags. For recursive search, use "*". Eg.: "/branches/*"')}</span>
231 231 </div>
232 232
233 233 <div class="field branch_patterns">
234 234 <div class="input" >
235 235 <label>${_('Branches')}:</label><br/>
236 236 </div>
237 237 % if svn_branch_patterns:
238 238 % for branch in svn_branch_patterns:
239 239 <div class="input adjacent" id="${'id%s' % branch.ui_id}">
240 240 ${h.hidden('branch_ui_key' + suffix, branch.ui_key)}
241 241 ${h.text('branch_value_%d' % branch.ui_id + suffix, branch.ui_value, size=59, readonly="readonly", class_='disabled')}
242 242 % if kwargs.get('disabled') != 'disabled':
243 243 <span class="btn btn-x" onclick="ajaxDeletePattern(${branch.ui_id},'${'id%s' % branch.ui_id}')">
244 244 ${_('Delete')}
245 245 </span>
246 246 % endif
247 247 </div>
248 248 % endfor
249 249 %endif
250 250 </div>
251 251 % if kwargs.get('disabled') != 'disabled':
252 252 <div class="field branch_patterns">
253 253 <div class="input" >
254 254 ${h.text('new_svn_branch',size=59,placeholder='New branch pattern')}
255 255 </div>
256 256 </div>
257 257 % endif
258 258 <div class="field tag_patterns">
259 259 <div class="input" >
260 260 <label>${_('Tags')}:</label><br/>
261 261 </div>
262 262 % if svn_tag_patterns:
263 263 % for tag in svn_tag_patterns:
264 264 <div class="input" id="${'id%s' % tag.ui_id + suffix}">
265 265 ${h.hidden('tag_ui_key' + suffix, tag.ui_key)}
266 266 ${h.text('tag_ui_value_new_%d' % tag.ui_id + suffix, tag.ui_value, size=59, readonly="readonly", class_='disabled tag_input')}
267 267 % if kwargs.get('disabled') != 'disabled':
268 268 <span class="btn btn-x" onclick="ajaxDeletePattern(${tag.ui_id},'${'id%s' % tag.ui_id}')">
269 269 ${_('Delete')}
270 270 </span>
271 271 %endif
272 272 </div>
273 273 % endfor
274 274 % endif
275 275 </div>
276 276 % if kwargs.get('disabled') != 'disabled':
277 277 <div class="field tag_patterns">
278 278 <div class="input" >
279 279 ${h.text('new_svn_tag' + suffix, size=59, placeholder='New tag pattern')}
280 280 </div>
281 281 </div>
282 282 %endif
283 283 </div>
284 284 </div>
285 285 % else:
286 286 ${h.hidden('new_svn_branch' + suffix, '')}
287 287 ${h.hidden('new_svn_tag' + suffix, '')}
288 288 % endif
289 289
290 290
291 291 % if display_globals or repo_type in ['hg', 'git']:
292 292 <div class="panel panel-default">
293 293 <div class="panel-heading" id="vcs-pull-requests-options">
294 294 <h3 class="panel-title">${_('Pull Request Settings')}<a class="permalink" href="#vcs-pull-requests-options"> ΒΆ</a></h3>
295 295 </div>
296 296 <div class="panel-body">
297 297 <div class="checkbox">
298 298 ${h.checkbox('rhodecode_pr_merge_enabled' + suffix, 'True', **kwargs)}
299 299 <label for="rhodecode_pr_merge_enabled${suffix}">${_('Enable server-side merge for pull requests')}</label>
300 300 </div>
301 301 <div class="label">
302 302 <span class="help-block">${_('Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface.')}</span>
303 303 </div>
304 304 <div class="checkbox">
305 305 ${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
306 306 <label for="rhodecode_use_outdated_comments${suffix}">${_('Invalidate and relocate inline comments during update')}</label>
307 307 </div>
308 308 <div class="label">
309 309 <span class="help-block">${_('During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden.')}</span>
310 310 </div>
311 311 </div>
312 312 </div>
313 313 % endif
314 314
315 315 % if display_globals or repo_type in ['hg', 'git', 'svn']:
316 316 <div class="panel panel-default">
317 317 <div class="panel-heading" id="vcs-pull-requests-options">
318 318 <h3 class="panel-title">${_('Diff cache')}<a class="permalink" href="#vcs-pull-requests-options"> ΒΆ</a></h3>
319 319 </div>
320 320 <div class="panel-body">
321 321 <div class="checkbox">
322 322 ${h.checkbox('rhodecode_diff_cache' + suffix, 'True', **kwargs)}
323 323 <label for="rhodecode_diff_cache${suffix}">${_('Enable caching diffs for pull requests cache and commits')}</label>
324 324 </div>
325 325 </div>
326 326 </div>
327 327 % endif
328 328
329 329 % if display_globals or repo_type in ['hg',]:
330 330 <div class="panel panel-default">
331 331 <div class="panel-heading" id="vcs-pull-requests-options">
332 332 <h3 class="panel-title">${_('Mercurial Pull Request Settings')}<a class="permalink" href="#vcs-hg-pull-requests-options"> ΒΆ</a></h3>
333 333 </div>
334 334 <div class="panel-body">
335 335 ## Specific HG settings
336 336 <div class="checkbox">
337 337 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
338 338 <label for="rhodecode_hg_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
339 339 </div>
340 340 <div class="label">
341 341 <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
342 342 </div>
343 343
344 344 <div class="checkbox">
345 345 ${h.checkbox('rhodecode_hg_close_branch_before_merging' + suffix, 'True', **kwargs)}
346 346 <label for="rhodecode_hg_close_branch_before_merging{suffix}">${_('Close branch before merging it')}</label>
347 347 </div>
348 348 <div class="label">
349 349 <span class="help-block">${_('Close branch before merging it into destination branch. No effect when rebase strategy is use.')}</span>
350 350 </div>
351 351
352 352
353 353 </div>
354 354 </div>
355 355 % endif
356 356
357 357 ## DISABLED FOR GIT FOR NOW as the rebase/close is not supported yet
358 358 ## % if display_globals or repo_type in ['git']:
359 359 ## <div class="panel panel-default">
360 360 ## <div class="panel-heading" id="vcs-pull-requests-options">
361 361 ## <h3 class="panel-title">${_('Git Pull Request Settings')}<a class="permalink" href="#vcs-git-pull-requests-options"> ΒΆ</a></h3>
362 362 ## </div>
363 363 ## <div class="panel-body">
364 364 ## <div class="checkbox">
365 365 ## ${h.checkbox('rhodecode_git_use_rebase_for_merging' + suffix, 'True', **kwargs)}
366 366 ## <label for="rhodecode_git_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
367 367 ## </div>
368 368 ## <div class="label">
369 369 ## <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
370 370 ## </div>
371 371 ##
372 372 ## <div class="checkbox">
373 373 ## ${h.checkbox('rhodecode_git_close_branch_before_merging' + suffix, 'True', **kwargs)}
374 374 ## <label for="rhodecode_git_close_branch_before_merging{suffix}">${_('Delete branch after merging it')}</label>
375 375 ## </div>
376 376 ## <div class="label">
377 377 ## <span class="help-block">${_('Delete branch after merging it into destination branch. No effect when rebase strategy is use.')}</span>
378 378 ## </div>
379 379 ## </div>
380 380 ## </div>
381 381 ## % endif
382 382
383 383
384 384 </%def>
@@ -1,1082 +1,1080 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.lib.utils2 import str2bool
25 25 from rhodecode.model.meta import Session
26 26 from rhodecode.model.settings import VcsSettingsModel, UiSetting
27 27
28 28
29 29 HOOKS_FORM_DATA = {
30 30 'hooks_changegroup_repo_size': True,
31 31 'hooks_changegroup_push_logger': True,
32 32 'hooks_outgoing_pull_logger': True
33 33 }
34 34
35 35 SVN_FORM_DATA = {
36 36 'new_svn_branch': 'test-branch',
37 37 'new_svn_tag': 'test-tag'
38 38 }
39 39
40 40 GENERAL_FORM_DATA = {
41 41 'rhodecode_pr_merge_enabled': True,
42 42 'rhodecode_use_outdated_comments': True,
43 43 'rhodecode_hg_use_rebase_for_merging': True,
44 44 'rhodecode_hg_close_branch_before_merging': True,
45 45 'rhodecode_git_use_rebase_for_merging': True,
46 46 'rhodecode_git_close_branch_before_merging': True,
47 47 'rhodecode_diff_cache': True,
48 48 }
49 49
50 50
51 51 class TestInheritGlobalSettingsProperty(object):
52 52 def test_get_raises_exception_when_repository_not_specified(self):
53 53 model = VcsSettingsModel()
54 54 with pytest.raises(Exception) as exc_info:
55 55 model.inherit_global_settings
56 56 assert str(exc_info.value) == 'Repository is not specified'
57 57
58 58 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
59 59 model = VcsSettingsModel(repo=repo_stub.repo_name)
60 60 assert model.inherit_global_settings is True
61 61
62 62 def test_value_is_returned(self, repo_stub, settings_util):
63 63 model = VcsSettingsModel(repo=repo_stub.repo_name)
64 64 settings_util.create_repo_rhodecode_setting(
65 65 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
66 66 assert model.inherit_global_settings is False
67 67
68 68 def test_value_is_set(self, repo_stub):
69 69 model = VcsSettingsModel(repo=repo_stub.repo_name)
70 70 model.inherit_global_settings = False
71 71 setting = model.repo_settings.get_setting_by_name(
72 72 VcsSettingsModel.INHERIT_SETTINGS)
73 73 try:
74 74 assert setting.app_settings_type == 'bool'
75 75 assert setting.app_settings_value is False
76 76 finally:
77 77 Session().delete(setting)
78 78 Session().commit()
79 79
80 80 def test_set_raises_exception_when_repository_not_specified(self):
81 81 model = VcsSettingsModel()
82 82 with pytest.raises(Exception) as exc_info:
83 83 model.inherit_global_settings = False
84 84 assert str(exc_info.value) == 'Repository is not specified'
85 85
86 86
87 87 class TestVcsSettingsModel(object):
88 88 def test_global_svn_branch_patterns(self):
89 89 model = VcsSettingsModel()
90 90 expected_result = {'test': 'test'}
91 91 with mock.patch.object(model, 'global_settings') as settings_mock:
92 92 get_settings = settings_mock.get_ui_by_section
93 93 get_settings.return_value = expected_result
94 94 settings_mock.return_value = expected_result
95 95 result = model.get_global_svn_branch_patterns()
96 96
97 97 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
98 98 assert expected_result == result
99 99
100 100 def test_repo_svn_branch_patterns(self):
101 101 model = VcsSettingsModel()
102 102 expected_result = {'test': 'test'}
103 103 with mock.patch.object(model, 'repo_settings') as settings_mock:
104 104 get_settings = settings_mock.get_ui_by_section
105 105 get_settings.return_value = expected_result
106 106 settings_mock.return_value = expected_result
107 107 result = model.get_repo_svn_branch_patterns()
108 108
109 109 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
110 110 assert expected_result == result
111 111
112 112 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
113 113 self):
114 114 model = VcsSettingsModel()
115 115 with pytest.raises(Exception) as exc_info:
116 116 model.get_repo_svn_branch_patterns()
117 117 assert str(exc_info.value) == 'Repository is not specified'
118 118
119 119 def test_global_svn_tag_patterns(self):
120 120 model = VcsSettingsModel()
121 121 expected_result = {'test': 'test'}
122 122 with mock.patch.object(model, 'global_settings') as settings_mock:
123 123 get_settings = settings_mock.get_ui_by_section
124 124 get_settings.return_value = expected_result
125 125 settings_mock.return_value = expected_result
126 126 result = model.get_global_svn_tag_patterns()
127 127
128 128 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
129 129 assert expected_result == result
130 130
131 131 def test_repo_svn_tag_patterns(self):
132 132 model = VcsSettingsModel()
133 133 expected_result = {'test': 'test'}
134 134 with mock.patch.object(model, 'repo_settings') as settings_mock:
135 135 get_settings = settings_mock.get_ui_by_section
136 136 get_settings.return_value = expected_result
137 137 settings_mock.return_value = expected_result
138 138 result = model.get_repo_svn_tag_patterns()
139 139
140 140 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
141 141 assert expected_result == result
142 142
143 143 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
144 144 model = VcsSettingsModel()
145 145 with pytest.raises(Exception) as exc_info:
146 146 model.get_repo_svn_tag_patterns()
147 147 assert str(exc_info.value) == 'Repository is not specified'
148 148
149 149 def test_get_global_settings(self):
150 150 expected_result = {'test': 'test'}
151 151 model = VcsSettingsModel()
152 152 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
153 153 collect_mock.return_value = expected_result
154 154 result = model.get_global_settings()
155 155
156 156 collect_mock.assert_called_once_with(global_=True)
157 157 assert result == expected_result
158 158
159 159 def test_get_repo_settings(self, repo_stub):
160 160 model = VcsSettingsModel(repo=repo_stub.repo_name)
161 161 expected_result = {'test': 'test'}
162 162 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
163 163 collect_mock.return_value = expected_result
164 164 result = model.get_repo_settings()
165 165
166 166 collect_mock.assert_called_once_with(global_=False)
167 167 assert result == expected_result
168 168
169 169 @pytest.mark.parametrize('settings, global_', [
170 170 ('global_settings', True),
171 171 ('repo_settings', False)
172 172 ])
173 173 def test_collect_all_settings(self, settings, global_):
174 174 model = VcsSettingsModel()
175 175 result_mock = self._mock_result()
176 176
177 177 settings_patch = mock.patch.object(model, settings)
178 178 with settings_patch as settings_mock:
179 179 settings_mock.get_ui_by_section_and_key.return_value = result_mock
180 180 settings_mock.get_setting_by_name.return_value = result_mock
181 181 result = model._collect_all_settings(global_=global_)
182 182
183 183 ui_settings = model.HG_SETTINGS + model.GIT_SETTINGS + model.HOOKS_SETTINGS
184 184 self._assert_get_settings_calls(
185 185 settings_mock, ui_settings, model.GENERAL_SETTINGS)
186 186 self._assert_collect_all_settings_result(
187 187 ui_settings, model.GENERAL_SETTINGS, result)
188 188
189 189 @pytest.mark.parametrize('settings, global_', [
190 190 ('global_settings', True),
191 191 ('repo_settings', False)
192 192 ])
193 193 def test_collect_all_settings_without_empty_value(self, settings, global_):
194 194 model = VcsSettingsModel()
195 195
196 196 settings_patch = mock.patch.object(model, settings)
197 197 with settings_patch as settings_mock:
198 198 settings_mock.get_ui_by_section_and_key.return_value = None
199 199 settings_mock.get_setting_by_name.return_value = None
200 200 result = model._collect_all_settings(global_=global_)
201 201
202 202 assert result == {}
203 203
204 204 def _mock_result(self):
205 205 result_mock = mock.Mock()
206 206 result_mock.ui_value = 'ui_value'
207 207 result_mock.ui_active = True
208 208 result_mock.app_settings_value = 'setting_value'
209 209 return result_mock
210 210
211 211 def _assert_get_settings_calls(
212 212 self, settings_mock, ui_settings, general_settings):
213 213 assert (
214 214 settings_mock.get_ui_by_section_and_key.call_count ==
215 215 len(ui_settings))
216 216 assert (
217 217 settings_mock.get_setting_by_name.call_count ==
218 218 len(general_settings))
219 219
220 220 for section, key in ui_settings:
221 221 expected_call = mock.call(section, key)
222 222 assert (
223 223 expected_call in
224 224 settings_mock.get_ui_by_section_and_key.call_args_list)
225 225
226 226 for name in general_settings:
227 227 expected_call = mock.call(name)
228 228 assert (
229 229 expected_call in
230 230 settings_mock.get_setting_by_name.call_args_list)
231 231
232 232 def _assert_collect_all_settings_result(
233 233 self, ui_settings, general_settings, result):
234 234 expected_result = {}
235 235 for section, key in ui_settings:
236 236 key = '{}_{}'.format(section, key.replace('.', '_'))
237 237
238 238 if section in ('extensions', 'hooks'):
239 239 value = True
240 240 elif key in ['vcs_git_lfs_enabled']:
241 241 value = True
242 242 else:
243 243 value = 'ui_value'
244 244 expected_result[key] = value
245 245
246 246 for name in general_settings:
247 247 key = 'rhodecode_' + name
248 248 expected_result[key] = 'setting_value'
249 249
250 250 assert expected_result == result
251 251
252 252
253 253 class TestCreateOrUpdateRepoHookSettings(object):
254 254 def test_create_when_no_repo_object_found(self, repo_stub):
255 255 model = VcsSettingsModel(repo=repo_stub.repo_name)
256 256
257 257 self._create_settings(model, HOOKS_FORM_DATA)
258 258
259 259 cleanup = []
260 260 try:
261 261 for section, key in model.HOOKS_SETTINGS:
262 262 ui = model.repo_settings.get_ui_by_section_and_key(
263 263 section, key)
264 264 assert ui.ui_active is True
265 265 cleanup.append(ui)
266 266 finally:
267 267 for ui in cleanup:
268 268 Session().delete(ui)
269 269 Session().commit()
270 270
271 271 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
272 272 model = VcsSettingsModel(repo=repo_stub.repo_name)
273 273
274 274 deleted_key = 'hooks_changegroup_repo_size'
275 275 data = HOOKS_FORM_DATA.copy()
276 276 data.pop(deleted_key)
277 277
278 278 with pytest.raises(ValueError) as exc_info:
279 279 model.create_or_update_repo_hook_settings(data)
280 280 msg = 'The given data does not contain {} key'.format(deleted_key)
281 281 assert str(exc_info.value) == msg
282 282
283 283 def test_update_when_repo_object_found(self, repo_stub, settings_util):
284 284 model = VcsSettingsModel(repo=repo_stub.repo_name)
285 285 for section, key in model.HOOKS_SETTINGS:
286 286 settings_util.create_repo_rhodecode_ui(
287 287 repo_stub, section, None, key=key, active=False)
288 288 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
289 289 for section, key in model.HOOKS_SETTINGS:
290 290 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
291 291 assert ui.ui_active is True
292 292
293 293 def _create_settings(self, model, data):
294 294 global_patch = mock.patch.object(model, 'global_settings')
295 295 global_setting = mock.Mock()
296 296 global_setting.ui_value = 'Test value'
297 297 with global_patch as global_mock:
298 298 global_mock.get_ui_by_section_and_key.return_value = global_setting
299 299 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
300 300
301 301
302 302 class TestUpdateGlobalHookSettings(object):
303 303 def test_update_raises_exception_when_data_incomplete(self):
304 304 model = VcsSettingsModel()
305 305
306 306 deleted_key = 'hooks_changegroup_repo_size'
307 307 data = HOOKS_FORM_DATA.copy()
308 308 data.pop(deleted_key)
309 309
310 310 with pytest.raises(ValueError) as exc_info:
311 311 model.update_global_hook_settings(data)
312 312 msg = 'The given data does not contain {} key'.format(deleted_key)
313 313 assert str(exc_info.value) == msg
314 314
315 315 def test_update_global_hook_settings(self, settings_util):
316 316 model = VcsSettingsModel()
317 317 setting_mock = mock.MagicMock()
318 318 setting_mock.ui_active = False
319 319 get_settings_patcher = mock.patch.object(
320 320 model.global_settings, 'get_ui_by_section_and_key',
321 321 return_value=setting_mock)
322 322 session_patcher = mock.patch('rhodecode.model.settings.Session')
323 323 with get_settings_patcher as get_settings_mock, session_patcher:
324 324 model.update_global_hook_settings(HOOKS_FORM_DATA)
325 325 assert setting_mock.ui_active is True
326 326 assert get_settings_mock.call_count == 3
327 327
328 328
329 329 class TestCreateOrUpdateRepoGeneralSettings(object):
330 330 def test_calls_create_or_update_general_settings(self, repo_stub):
331 331 model = VcsSettingsModel(repo=repo_stub.repo_name)
332 332 create_patch = mock.patch.object(
333 333 model, '_create_or_update_general_settings')
334 334 with create_patch as create_mock:
335 335 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
336 336 create_mock.assert_called_once_with(
337 337 model.repo_settings, GENERAL_FORM_DATA)
338 338
339 339 def test_raises_exception_when_repository_is_not_specified(self):
340 340 model = VcsSettingsModel()
341 341 with pytest.raises(Exception) as exc_info:
342 342 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
343 343 assert str(exc_info.value) == 'Repository is not specified'
344 344
345 345
346 346 class TestCreateOrUpdatGlobalGeneralSettings(object):
347 347 def test_calls_create_or_update_general_settings(self):
348 348 model = VcsSettingsModel()
349 349 create_patch = mock.patch.object(
350 350 model, '_create_or_update_general_settings')
351 351 with create_patch as create_mock:
352 352 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
353 353 create_mock.assert_called_once_with(
354 354 model.global_settings, GENERAL_FORM_DATA)
355 355
356 356
357 357 class TestCreateOrUpdateGeneralSettings(object):
358 358 def test_create_when_no_repo_settings_found(self, repo_stub):
359 359 model = VcsSettingsModel(repo=repo_stub.repo_name)
360 360 model._create_or_update_general_settings(
361 361 model.repo_settings, GENERAL_FORM_DATA)
362 362
363 363 cleanup = []
364 364 try:
365 365 for name in model.GENERAL_SETTINGS:
366 366 setting = model.repo_settings.get_setting_by_name(name)
367 367 assert setting.app_settings_value is True
368 368 cleanup.append(setting)
369 369 finally:
370 370 for setting in cleanup:
371 371 Session().delete(setting)
372 372 Session().commit()
373 373
374 374 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
375 375 model = VcsSettingsModel(repo=repo_stub.repo_name)
376 376
377 377 deleted_key = 'rhodecode_pr_merge_enabled'
378 378 data = GENERAL_FORM_DATA.copy()
379 379 data.pop(deleted_key)
380 380
381 381 with pytest.raises(ValueError) as exc_info:
382 382 model._create_or_update_general_settings(model.repo_settings, data)
383 383
384 384 msg = 'The given data does not contain {} key'.format(deleted_key)
385 385 assert str(exc_info.value) == msg
386 386
387 387 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
388 388 model = VcsSettingsModel(repo=repo_stub.repo_name)
389 389 for name in model.GENERAL_SETTINGS:
390 390 settings_util.create_repo_rhodecode_setting(
391 391 repo_stub, name, False, 'bool')
392 392
393 393 model._create_or_update_general_settings(
394 394 model.repo_settings, GENERAL_FORM_DATA)
395 395
396 396 for name in model.GENERAL_SETTINGS:
397 397 setting = model.repo_settings.get_setting_by_name(name)
398 398 assert setting.app_settings_value is True
399 399
400 400
401 401 class TestCreateRepoSvnSettings(object):
402 402 def test_calls_create_svn_settings(self, repo_stub):
403 403 model = VcsSettingsModel(repo=repo_stub.repo_name)
404 404 with mock.patch.object(model, '_create_svn_settings') as create_mock:
405 405 model.create_repo_svn_settings(SVN_FORM_DATA)
406 406 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
407 407
408 408 def test_raises_exception_when_repository_is_not_specified(self):
409 409 model = VcsSettingsModel()
410 410 with pytest.raises(Exception) as exc_info:
411 411 model.create_repo_svn_settings(SVN_FORM_DATA)
412 412 assert str(exc_info.value) == 'Repository is not specified'
413 413
414 414
415 415 class TestCreateSvnSettings(object):
416 416 def test_create(self, repo_stub):
417 417 model = VcsSettingsModel(repo=repo_stub.repo_name)
418 418 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
419 419 Session().commit()
420 420
421 421 branch_ui = model.repo_settings.get_ui_by_section(
422 422 model.SVN_BRANCH_SECTION)
423 423 tag_ui = model.repo_settings.get_ui_by_section(
424 424 model.SVN_TAG_SECTION)
425 425
426 426 try:
427 427 assert len(branch_ui) == 1
428 428 assert len(tag_ui) == 1
429 429 finally:
430 430 Session().delete(branch_ui[0])
431 431 Session().delete(tag_ui[0])
432 432 Session().commit()
433 433
434 434 def test_create_tag(self, repo_stub):
435 435 model = VcsSettingsModel(repo=repo_stub.repo_name)
436 436 data = SVN_FORM_DATA.copy()
437 437 data.pop('new_svn_branch')
438 438 model._create_svn_settings(model.repo_settings, data)
439 439 Session().commit()
440 440
441 441 branch_ui = model.repo_settings.get_ui_by_section(
442 442 model.SVN_BRANCH_SECTION)
443 443 tag_ui = model.repo_settings.get_ui_by_section(
444 444 model.SVN_TAG_SECTION)
445 445
446 446 try:
447 447 assert len(branch_ui) == 0
448 448 assert len(tag_ui) == 1
449 449 finally:
450 450 Session().delete(tag_ui[0])
451 451 Session().commit()
452 452
453 453 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
454 454 model = VcsSettingsModel(repo=repo_stub.repo_name)
455 455 model._create_svn_settings(model.repo_settings, {})
456 456 Session().commit()
457 457
458 458 branch_ui = model.repo_settings.get_ui_by_section(
459 459 model.SVN_BRANCH_SECTION)
460 460 tag_ui = model.repo_settings.get_ui_by_section(
461 461 model.SVN_TAG_SECTION)
462 462
463 463 assert len(branch_ui) == 0
464 464 assert len(tag_ui) == 0
465 465
466 466 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
467 467 model = VcsSettingsModel(repo=repo_stub.repo_name)
468 468 data = {
469 469 'new_svn_branch': '',
470 470 'new_svn_tag': ''
471 471 }
472 472 model._create_svn_settings(model.repo_settings, data)
473 473 Session().commit()
474 474
475 475 branch_ui = model.repo_settings.get_ui_by_section(
476 476 model.SVN_BRANCH_SECTION)
477 477 tag_ui = model.repo_settings.get_ui_by_section(
478 478 model.SVN_TAG_SECTION)
479 479
480 480 assert len(branch_ui) == 0
481 481 assert len(tag_ui) == 0
482 482
483 483
484 484 class TestCreateOrUpdateUi(object):
485 485 def test_create(self, repo_stub):
486 486 model = VcsSettingsModel(repo=repo_stub.repo_name)
487 487 model._create_or_update_ui(
488 488 model.repo_settings, 'test-section', 'test-key', active=False,
489 489 value='False')
490 490 Session().commit()
491 491
492 492 created_ui = model.repo_settings.get_ui_by_section_and_key(
493 493 'test-section', 'test-key')
494 494
495 495 try:
496 496 assert created_ui.ui_active is False
497 497 assert str2bool(created_ui.ui_value) is False
498 498 finally:
499 499 Session().delete(created_ui)
500 500 Session().commit()
501 501
502 502 def test_update(self, repo_stub, settings_util):
503 503 model = VcsSettingsModel(repo=repo_stub.repo_name)
504
505 largefiles, phases, evolve = model.HG_SETTINGS
504 # care about only 3 first settings
505 largefiles, phases, evolve = model.HG_SETTINGS[:3]
506 506
507 507 section = 'test-section'
508 508 key = 'test-key'
509 509 settings_util.create_repo_rhodecode_ui(
510 510 repo_stub, section, 'True', key=key, active=True)
511 511
512 512 model._create_or_update_ui(
513 513 model.repo_settings, section, key, active=False, value='False')
514 514 Session().commit()
515 515
516 516 created_ui = model.repo_settings.get_ui_by_section_and_key(
517 517 section, key)
518 518 assert created_ui.ui_active is False
519 519 assert str2bool(created_ui.ui_value) is False
520 520
521 521
522 522 class TestCreateOrUpdateRepoHgSettings(object):
523 523 FORM_DATA = {
524 524 'extensions_largefiles': False,
525 525 'extensions_evolve': False,
526 526 'phases_publish': False
527 527 }
528 528
529 529 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
530 530 model = VcsSettingsModel(repo=repo_stub.repo_name)
531 531 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
532 532 model.create_or_update_repo_hg_settings(self.FORM_DATA)
533 533 expected_calls = [
534 mock.call(model.repo_settings, 'extensions', 'largefiles',
535 active=False, value=''),
536 mock.call(model.repo_settings, 'extensions', 'evolve',
537 active=False, value=''),
534 mock.call(model.repo_settings, 'extensions', 'largefiles', active=False, value=''),
535 mock.call(model.repo_settings, 'extensions', 'evolve', active=False, value=''),
536 mock.call(model.repo_settings, 'experimental', 'evolution', active=False, value=''),
537 mock.call(model.repo_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
538 mock.call(model.repo_settings, 'extensions', 'topic', active=False, value=''),
538 539 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
539 540 ]
540 541 assert expected_calls == create_mock.call_args_list
541 542
542 543 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
543 544 def test_key_is_not_found(self, repo_stub, field_to_remove):
544 545 model = VcsSettingsModel(repo=repo_stub.repo_name)
545 546 data = self.FORM_DATA.copy()
546 547 data.pop(field_to_remove)
547 548 with pytest.raises(ValueError) as exc_info:
548 549 model.create_or_update_repo_hg_settings(data)
549 550 expected_message = 'The given data does not contain {} key'.format(
550 551 field_to_remove)
551 552 assert str(exc_info.value) == expected_message
552 553
553 554 def test_create_raises_exception_when_repository_not_specified(self):
554 555 model = VcsSettingsModel()
555 556 with pytest.raises(Exception) as exc_info:
556 557 model.create_or_update_repo_hg_settings(self.FORM_DATA)
557 558 assert str(exc_info.value) == 'Repository is not specified'
558 559
559 560
560 561 class TestUpdateGlobalSslSetting(object):
561 562 def test_updates_global_hg_settings(self):
562 563 model = VcsSettingsModel()
563 564 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
564 565 model.update_global_ssl_setting('False')
565 566 create_mock.assert_called_once_with(
566 567 model.global_settings, 'web', 'push_ssl', value='False')
567 568
568 569
569 570 class TestUpdateGlobalPathSetting(object):
570 571 def test_updates_global_path_settings(self):
571 572 model = VcsSettingsModel()
572 573 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
573 574 model.update_global_path_setting('False')
574 575 create_mock.assert_called_once_with(
575 576 model.global_settings, 'paths', '/', value='False')
576 577
577 578
578 579 class TestCreateOrUpdateGlobalHgSettings(object):
579 580 FORM_DATA = {
580 581 'extensions_largefiles': False,
581 582 'largefiles_usercache': '/example/largefiles-store',
582 583 'phases_publish': False,
583 584 'extensions_hgsubversion': False,
584 585 'extensions_evolve': False
585 586 }
586 587
587 588 def test_creates_repo_hg_settings_when_data_is_correct(self):
588 589 model = VcsSettingsModel()
589 590 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
590 591 model.create_or_update_global_hg_settings(self.FORM_DATA)
591 592 expected_calls = [
592 mock.call(model.global_settings, 'extensions', 'largefiles',
593 active=False, value=''),
594 mock.call(model.global_settings, 'largefiles', 'usercache',
595 value='/example/largefiles-store'),
596 mock.call(model.global_settings, 'phases', 'publish',
597 value='False'),
598 mock.call(model.global_settings, 'extensions', 'hgsubversion',
599 active=False),
600 mock.call(model.global_settings, 'extensions', 'evolve',
601 active=False, value='')
593 mock.call(model.global_settings, 'extensions', 'largefiles', active=False, value=''),
594 mock.call(model.global_settings, 'largefiles', 'usercache', value='/example/largefiles-store'),
595 mock.call(model.global_settings, 'phases', 'publish', value='False'),
596 mock.call(model.global_settings, 'extensions', 'hgsubversion', active=False),
597 mock.call(model.global_settings, 'extensions', 'evolve', active=False, value=''),
598 mock.call(model.global_settings, 'experimental', 'evolution', active=False, value=''),
599 mock.call(model.global_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
600 mock.call(model.global_settings, 'extensions', 'topic', active=False, value=''),
602 601 ]
602
603 603 assert expected_calls == create_mock.call_args_list
604 604
605 605 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
606 606 def test_key_is_not_found(self, repo_stub, field_to_remove):
607 607 model = VcsSettingsModel(repo=repo_stub.repo_name)
608 608 data = self.FORM_DATA.copy()
609 609 data.pop(field_to_remove)
610 610 with pytest.raises(Exception) as exc_info:
611 611 model.create_or_update_global_hg_settings(data)
612 612 expected_message = 'The given data does not contain {} key'.format(
613 613 field_to_remove)
614 614 assert str(exc_info.value) == expected_message
615 615
616 616
617 617 class TestCreateOrUpdateGlobalGitSettings(object):
618 618 FORM_DATA = {
619 619 'vcs_git_lfs_enabled': False,
620 620 'vcs_git_lfs_store_location': '/example/lfs-store',
621 621 }
622 622
623 623 def test_creates_repo_hg_settings_when_data_is_correct(self):
624 624 model = VcsSettingsModel()
625 625 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
626 626 model.create_or_update_global_git_settings(self.FORM_DATA)
627 627 expected_calls = [
628 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled',
629 active=False, value=False),
630 mock.call(model.global_settings, 'vcs_git_lfs', 'store_location',
631 value='/example/lfs-store'),
628 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled', active=False, value=False),
629 mock.call(model.global_settings, 'vcs_git_lfs', 'store_location', value='/example/lfs-store'),
632 630 ]
633 631 assert expected_calls == create_mock.call_args_list
634 632
635 633
636 634 class TestDeleteRepoSvnPattern(object):
637 635 def test_success_when_repo_is_set(self, backend_svn, settings_util):
638 636 repo = backend_svn.create_repo()
639 637 repo_name = repo.repo_name
640 638
641 639 model = VcsSettingsModel(repo=repo_name)
642 640 entry = settings_util.create_repo_rhodecode_ui(
643 641 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
644 642 Session().commit()
645 643
646 644 model.delete_repo_svn_pattern(entry.ui_id)
647 645
648 646 def test_fail_when_delete_id_from_other_repo(self, backend_svn):
649 647 repo_name = backend_svn.repo_name
650 648 model = VcsSettingsModel(repo=repo_name)
651 649 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
652 650 with delete_ui_patch as delete_ui_mock:
653 651 model.delete_repo_svn_pattern(123)
654 652 delete_ui_mock.assert_called_once_with(-1)
655 653
656 654 def test_raises_exception_when_repository_is_not_specified(self):
657 655 model = VcsSettingsModel()
658 656 with pytest.raises(Exception) as exc_info:
659 657 model.delete_repo_svn_pattern(123)
660 658 assert str(exc_info.value) == 'Repository is not specified'
661 659
662 660
663 661 class TestDeleteGlobalSvnPattern(object):
664 662 def test_delete_global_svn_pattern_calls_delete_ui(self):
665 663 model = VcsSettingsModel()
666 664 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
667 665 with delete_ui_patch as delete_ui_mock:
668 666 model.delete_global_svn_pattern(123)
669 667 delete_ui_mock.assert_called_once_with(123)
670 668
671 669
672 670 class TestFilterUiSettings(object):
673 671 def test_settings_are_filtered(self):
674 672 model = VcsSettingsModel()
675 673 repo_settings = [
676 674 UiSetting('extensions', 'largefiles', '', True),
677 675 UiSetting('phases', 'publish', 'True', True),
678 676 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
679 677 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
680 678 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
681 679 UiSetting(
682 680 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
683 681 'test_branch', True),
684 682 UiSetting(
685 683 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
686 684 'test_tag', True),
687 685 ]
688 686 non_repo_settings = [
689 687 UiSetting('largefiles', 'usercache', '/example/largefiles-store', True),
690 688 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
691 689 UiSetting('hooks', 'test2', 'hook', True),
692 690 UiSetting(
693 691 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
694 692 'test_tag', True),
695 693 ]
696 694 settings = repo_settings + non_repo_settings
697 695 filtered_settings = model._filter_ui_settings(settings)
698 696 assert sorted(filtered_settings) == sorted(repo_settings)
699 697
700 698
701 699 class TestFilterGeneralSettings(object):
702 700 def test_settings_are_filtered(self):
703 701 model = VcsSettingsModel()
704 702 settings = {
705 703 'rhodecode_abcde': 'value1',
706 704 'rhodecode_vwxyz': 'value2',
707 705 }
708 706 general_settings = {
709 707 'rhodecode_{}'.format(key): 'value'
710 708 for key in VcsSettingsModel.GENERAL_SETTINGS
711 709 }
712 710 settings.update(general_settings)
713 711
714 712 filtered_settings = model._filter_general_settings(general_settings)
715 713 assert sorted(filtered_settings) == sorted(general_settings)
716 714
717 715
718 716 class TestGetRepoUiSettings(object):
719 717 def test_global_uis_are_returned_when_no_repo_uis_found(
720 718 self, repo_stub):
721 719 model = VcsSettingsModel(repo=repo_stub.repo_name)
722 720 result = model.get_repo_ui_settings()
723 721 svn_sections = (
724 722 VcsSettingsModel.SVN_TAG_SECTION,
725 723 VcsSettingsModel.SVN_BRANCH_SECTION)
726 724 expected_result = [
727 725 s for s in model.global_settings.get_ui()
728 726 if s.section not in svn_sections]
729 727 assert sorted(result) == sorted(expected_result)
730 728
731 729 def test_repo_uis_are_overriding_global_uis(
732 730 self, repo_stub, settings_util):
733 731 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
734 732 settings_util.create_repo_rhodecode_ui(
735 733 repo_stub, section, 'repo', key=key, active=False)
736 734 model = VcsSettingsModel(repo=repo_stub.repo_name)
737 735 result = model.get_repo_ui_settings()
738 736 for setting in result:
739 737 locator = (setting.section, setting.key)
740 738 if locator in VcsSettingsModel.HOOKS_SETTINGS:
741 739 assert setting.value == 'repo'
742 740
743 741 assert setting.active is False
744 742
745 743 def test_global_svn_patterns_are_not_in_list(
746 744 self, repo_stub, settings_util):
747 745 svn_sections = (
748 746 VcsSettingsModel.SVN_TAG_SECTION,
749 747 VcsSettingsModel.SVN_BRANCH_SECTION)
750 748 for section in svn_sections:
751 749 settings_util.create_rhodecode_ui(
752 750 section, 'repo', key='deadbeef' + section, active=False)
753 751 model = VcsSettingsModel(repo=repo_stub.repo_name)
754 752 result = model.get_repo_ui_settings()
755 753 for setting in result:
756 754 assert setting.section not in svn_sections
757 755
758 756 def test_repo_uis_filtered_by_section_are_returned(
759 757 self, repo_stub, settings_util):
760 758 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
761 759 settings_util.create_repo_rhodecode_ui(
762 760 repo_stub, section, 'repo', key=key, active=False)
763 761 model = VcsSettingsModel(repo=repo_stub.repo_name)
764 762 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
765 763 result = model.get_repo_ui_settings(section=section)
766 764 for setting in result:
767 765 assert setting.section == section
768 766
769 767 def test_repo_uis_filtered_by_key_are_returned(
770 768 self, repo_stub, settings_util):
771 769 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
772 770 settings_util.create_repo_rhodecode_ui(
773 771 repo_stub, section, 'repo', key=key, active=False)
774 772 model = VcsSettingsModel(repo=repo_stub.repo_name)
775 773 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
776 774 result = model.get_repo_ui_settings(key=key)
777 775 for setting in result:
778 776 assert setting.key == key
779 777
780 778 def test_raises_exception_when_repository_is_not_specified(self):
781 779 model = VcsSettingsModel()
782 780 with pytest.raises(Exception) as exc_info:
783 781 model.get_repo_ui_settings()
784 782 assert str(exc_info.value) == 'Repository is not specified'
785 783
786 784
787 785 class TestGetRepoGeneralSettings(object):
788 786 def test_global_settings_are_returned_when_no_repo_settings_found(
789 787 self, repo_stub):
790 788 model = VcsSettingsModel(repo=repo_stub.repo_name)
791 789 result = model.get_repo_general_settings()
792 790 expected_result = model.global_settings.get_all_settings()
793 791 assert sorted(result) == sorted(expected_result)
794 792
795 793 def test_repo_uis_are_overriding_global_uis(
796 794 self, repo_stub, settings_util):
797 795 for key in VcsSettingsModel.GENERAL_SETTINGS:
798 796 settings_util.create_repo_rhodecode_setting(
799 797 repo_stub, key, 'abcde', type_='unicode')
800 798 model = VcsSettingsModel(repo=repo_stub.repo_name)
801 799 result = model.get_repo_ui_settings()
802 800 for key in result:
803 801 if key in VcsSettingsModel.GENERAL_SETTINGS:
804 802 assert result[key] == 'abcde'
805 803
806 804 def test_raises_exception_when_repository_is_not_specified(self):
807 805 model = VcsSettingsModel()
808 806 with pytest.raises(Exception) as exc_info:
809 807 model.get_repo_general_settings()
810 808 assert str(exc_info.value) == 'Repository is not specified'
811 809
812 810
813 811 class TestGetGlobalGeneralSettings(object):
814 812 def test_global_settings_are_returned(self, repo_stub):
815 813 model = VcsSettingsModel()
816 814 result = model.get_global_general_settings()
817 815 expected_result = model.global_settings.get_all_settings()
818 816 assert sorted(result) == sorted(expected_result)
819 817
820 818 def test_repo_uis_are_not_overriding_global_uis(
821 819 self, repo_stub, settings_util):
822 820 for key in VcsSettingsModel.GENERAL_SETTINGS:
823 821 settings_util.create_repo_rhodecode_setting(
824 822 repo_stub, key, 'abcde', type_='unicode')
825 823 model = VcsSettingsModel(repo=repo_stub.repo_name)
826 824 result = model.get_global_general_settings()
827 825 expected_result = model.global_settings.get_all_settings()
828 826 assert sorted(result) == sorted(expected_result)
829 827
830 828
831 829 class TestGetGlobalUiSettings(object):
832 830 def test_global_uis_are_returned(self, repo_stub):
833 831 model = VcsSettingsModel()
834 832 result = model.get_global_ui_settings()
835 833 expected_result = model.global_settings.get_ui()
836 834 assert sorted(result) == sorted(expected_result)
837 835
838 836 def test_repo_uis_are_not_overriding_global_uis(
839 837 self, repo_stub, settings_util):
840 838 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
841 839 settings_util.create_repo_rhodecode_ui(
842 840 repo_stub, section, 'repo', key=key, active=False)
843 841 model = VcsSettingsModel(repo=repo_stub.repo_name)
844 842 result = model.get_global_ui_settings()
845 843 expected_result = model.global_settings.get_ui()
846 844 assert sorted(result) == sorted(expected_result)
847 845
848 846 def test_ui_settings_filtered_by_section(
849 847 self, repo_stub, settings_util):
850 848 model = VcsSettingsModel(repo=repo_stub.repo_name)
851 849 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
852 850 result = model.get_global_ui_settings(section=section)
853 851 expected_result = model.global_settings.get_ui(section=section)
854 852 assert sorted(result) == sorted(expected_result)
855 853
856 854 def test_ui_settings_filtered_by_key(
857 855 self, repo_stub, settings_util):
858 856 model = VcsSettingsModel(repo=repo_stub.repo_name)
859 857 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
860 858 result = model.get_global_ui_settings(key=key)
861 859 expected_result = model.global_settings.get_ui(key=key)
862 860 assert sorted(result) == sorted(expected_result)
863 861
864 862
865 863 class TestGetGeneralSettings(object):
866 864 def test_global_settings_are_returned_when_inherited_is_true(
867 865 self, repo_stub, settings_util):
868 866 model = VcsSettingsModel(repo=repo_stub.repo_name)
869 867 model.inherit_global_settings = True
870 868 for key in VcsSettingsModel.GENERAL_SETTINGS:
871 869 settings_util.create_repo_rhodecode_setting(
872 870 repo_stub, key, 'abcde', type_='unicode')
873 871 result = model.get_general_settings()
874 872 expected_result = model.get_global_general_settings()
875 873 assert sorted(result) == sorted(expected_result)
876 874
877 875 def test_repo_settings_are_returned_when_inherited_is_false(
878 876 self, repo_stub, settings_util):
879 877 model = VcsSettingsModel(repo=repo_stub.repo_name)
880 878 model.inherit_global_settings = False
881 879 for key in VcsSettingsModel.GENERAL_SETTINGS:
882 880 settings_util.create_repo_rhodecode_setting(
883 881 repo_stub, key, 'abcde', type_='unicode')
884 882 result = model.get_general_settings()
885 883 expected_result = model.get_repo_general_settings()
886 884 assert sorted(result) == sorted(expected_result)
887 885
888 886 def test_global_settings_are_returned_when_no_repository_specified(self):
889 887 model = VcsSettingsModel()
890 888 result = model.get_general_settings()
891 889 expected_result = model.get_global_general_settings()
892 890 assert sorted(result) == sorted(expected_result)
893 891
894 892
895 893 class TestGetUiSettings(object):
896 894 def test_global_settings_are_returned_when_inherited_is_true(
897 895 self, repo_stub, settings_util):
898 896 model = VcsSettingsModel(repo=repo_stub.repo_name)
899 897 model.inherit_global_settings = True
900 898 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
901 899 settings_util.create_repo_rhodecode_ui(
902 900 repo_stub, section, 'repo', key=key, active=True)
903 901 result = model.get_ui_settings()
904 902 expected_result = model.get_global_ui_settings()
905 903 assert sorted(result) == sorted(expected_result)
906 904
907 905 def test_repo_settings_are_returned_when_inherited_is_false(
908 906 self, repo_stub, settings_util):
909 907 model = VcsSettingsModel(repo=repo_stub.repo_name)
910 908 model.inherit_global_settings = False
911 909 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
912 910 settings_util.create_repo_rhodecode_ui(
913 911 repo_stub, section, 'repo', key=key, active=True)
914 912 result = model.get_ui_settings()
915 913 expected_result = model.get_repo_ui_settings()
916 914 assert sorted(result) == sorted(expected_result)
917 915
918 916 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
919 917 model = VcsSettingsModel(repo=repo_stub.repo_name)
920 918 model.inherit_global_settings = False
921 919 args = ('section', 'key')
922 920 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
923 921 model.get_ui_settings(*args)
924 922 settings_mock.assert_called_once_with(*args)
925 923
926 924 def test_global_settings_filtered_by_section_and_key(self):
927 925 model = VcsSettingsModel()
928 926 args = ('section', 'key')
929 927 with mock.patch.object(model, 'get_global_ui_settings') as (
930 928 settings_mock):
931 929 model.get_ui_settings(*args)
932 930 settings_mock.assert_called_once_with(*args)
933 931
934 932 def test_global_settings_are_returned_when_no_repository_specified(self):
935 933 model = VcsSettingsModel()
936 934 result = model.get_ui_settings()
937 935 expected_result = model.get_global_ui_settings()
938 936 assert sorted(result) == sorted(expected_result)
939 937
940 938
941 939 class TestGetSvnPatterns(object):
942 940 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
943 941 model = VcsSettingsModel(repo=repo_stub.repo_name)
944 942 args = ('section', )
945 943 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
946 944 model.get_svn_patterns(*args)
947 945 settings_mock.assert_called_once_with(*args)
948 946
949 947 def test_global_settings_filtered_by_section_and_key(self):
950 948 model = VcsSettingsModel()
951 949 args = ('section', )
952 950 with mock.patch.object(model, 'get_global_ui_settings') as (
953 951 settings_mock):
954 952 model.get_svn_patterns(*args)
955 953 settings_mock.assert_called_once_with(*args)
956 954
957 955
958 956 class TestGetReposLocation(object):
959 957 def test_returns_repos_location(self, repo_stub):
960 958 model = VcsSettingsModel()
961 959
962 960 result_mock = mock.Mock()
963 961 result_mock.ui_value = '/tmp'
964 962
965 963 with mock.patch.object(model, 'global_settings') as settings_mock:
966 964 settings_mock.get_ui_by_key.return_value = result_mock
967 965 result = model.get_repos_location()
968 966
969 967 settings_mock.get_ui_by_key.assert_called_once_with('/')
970 968 assert result == '/tmp'
971 969
972 970
973 971 class TestCreateOrUpdateRepoSettings(object):
974 972 FORM_DATA = {
975 973 'inherit_global_settings': False,
976 974 'hooks_changegroup_repo_size': False,
977 975 'hooks_changegroup_push_logger': False,
978 976 'hooks_outgoing_pull_logger': False,
979 977 'extensions_largefiles': False,
980 978 'extensions_evolve': False,
981 979 'largefiles_usercache': '/example/largefiles-store',
982 980 'vcs_git_lfs_enabled': False,
983 981 'vcs_git_lfs_store_location': '/',
984 982 'phases_publish': 'False',
985 983 'rhodecode_pr_merge_enabled': False,
986 984 'rhodecode_use_outdated_comments': False,
987 985 'new_svn_branch': '',
988 986 'new_svn_tag': ''
989 987 }
990 988
991 989 def test_get_raises_exception_when_repository_not_specified(self):
992 990 model = VcsSettingsModel()
993 991 with pytest.raises(Exception) as exc_info:
994 992 model.create_or_update_repo_settings(data=self.FORM_DATA)
995 993 assert str(exc_info.value) == 'Repository is not specified'
996 994
997 995 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
998 996 repo = backend_svn.create_repo()
999 997 model = VcsSettingsModel(repo=repo)
1000 998 with self._patch_model(model) as mocks:
1001 999 model.create_or_update_repo_settings(
1002 1000 data=self.FORM_DATA, inherit_global_settings=False)
1003 1001 mocks['create_repo_svn_settings'].assert_called_once_with(
1004 1002 self.FORM_DATA)
1005 1003 non_called_methods = (
1006 1004 'create_or_update_repo_hook_settings',
1007 1005 'create_or_update_repo_pr_settings',
1008 1006 'create_or_update_repo_hg_settings')
1009 1007 for method in non_called_methods:
1010 1008 assert mocks[method].call_count == 0
1011 1009
1012 1010 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
1013 1011 repo = backend_hg.create_repo()
1014 1012 model = VcsSettingsModel(repo=repo)
1015 1013 with self._patch_model(model) as mocks:
1016 1014 model.create_or_update_repo_settings(
1017 1015 data=self.FORM_DATA, inherit_global_settings=False)
1018 1016
1019 1017 assert mocks['create_repo_svn_settings'].call_count == 0
1020 1018 called_methods = (
1021 1019 'create_or_update_repo_hook_settings',
1022 1020 'create_or_update_repo_pr_settings',
1023 1021 'create_or_update_repo_hg_settings')
1024 1022 for method in called_methods:
1025 1023 mocks[method].assert_called_once_with(self.FORM_DATA)
1026 1024
1027 1025 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
1028 1026 self, backend_git):
1029 1027 repo = backend_git.create_repo()
1030 1028 model = VcsSettingsModel(repo=repo)
1031 1029 with self._patch_model(model) as mocks:
1032 1030 model.create_or_update_repo_settings(
1033 1031 data=self.FORM_DATA, inherit_global_settings=False)
1034 1032
1035 1033 assert mocks['create_repo_svn_settings'].call_count == 0
1036 1034 called_methods = (
1037 1035 'create_or_update_repo_hook_settings',
1038 1036 'create_or_update_repo_pr_settings')
1039 1037 non_called_methods = (
1040 1038 'create_repo_svn_settings',
1041 1039 'create_or_update_repo_hg_settings'
1042 1040 )
1043 1041 for method in called_methods:
1044 1042 mocks[method].assert_called_once_with(self.FORM_DATA)
1045 1043 for method in non_called_methods:
1046 1044 assert mocks[method].call_count == 0
1047 1045
1048 1046 def test_no_methods_are_called_when_settings_are_inherited(
1049 1047 self, backend):
1050 1048 repo = backend.create_repo()
1051 1049 model = VcsSettingsModel(repo=repo)
1052 1050 with self._patch_model(model) as mocks:
1053 1051 model.create_or_update_repo_settings(
1054 1052 data=self.FORM_DATA, inherit_global_settings=True)
1055 1053 for method_name in mocks:
1056 1054 assert mocks[method_name].call_count == 0
1057 1055
1058 1056 def test_cache_is_marked_for_invalidation(self, repo_stub):
1059 1057 model = VcsSettingsModel(repo=repo_stub)
1060 1058 invalidation_patcher = mock.patch(
1061 1059 'rhodecode.model.scm.ScmModel.mark_for_invalidation')
1062 1060 with invalidation_patcher as invalidation_mock:
1063 1061 model.create_or_update_repo_settings(
1064 1062 data=self.FORM_DATA, inherit_global_settings=True)
1065 1063 invalidation_mock.assert_called_once_with(
1066 1064 repo_stub.repo_name, delete=True)
1067 1065
1068 1066 def test_inherit_flag_is_saved(self, repo_stub):
1069 1067 model = VcsSettingsModel(repo=repo_stub)
1070 1068 model.inherit_global_settings = True
1071 1069 with self._patch_model(model):
1072 1070 model.create_or_update_repo_settings(
1073 1071 data=self.FORM_DATA, inherit_global_settings=False)
1074 1072 assert model.inherit_global_settings is False
1075 1073
1076 1074 def _patch_model(self, model):
1077 1075 return mock.patch.multiple(
1078 1076 model,
1079 1077 create_repo_svn_settings=mock.DEFAULT,
1080 1078 create_or_update_repo_hook_settings=mock.DEFAULT,
1081 1079 create_or_update_repo_pr_settings=mock.DEFAULT,
1082 1080 create_or_update_repo_hg_settings=mock.DEFAULT)
General Comments 0
You need to be logged in to leave comments. Login now