##// END OF EJS Templates
feat(git lfs path and path to hg large files in *.ini files): moved git lfs path and path of hg large files to *.ini files.
ilin.s -
r5535:7350be7a default
parent child Browse files
Show More
@@ -1,899 +1,905 b''
1 1
2 2 ; #########################################
3 3 ; RHODECODE COMMUNITY EDITION CONFIGURATION
4 4 ; #########################################
5 5
6 6 [DEFAULT]
7 7 ; Debug flag sets all loggers to debug, and enables request tracking
8 8 debug = true
9 9
10 10 ; ########################################################################
11 11 ; EMAIL CONFIGURATION
12 12 ; These settings will be used by the RhodeCode mailing system
13 13 ; ########################################################################
14 14
15 15 ; prefix all emails subjects with given prefix, helps filtering out emails
16 16 #email_prefix = [RhodeCode]
17 17
18 18 ; email FROM address all mails will be sent
19 19 #app_email_from = rhodecode-noreply@localhost
20 20
21 21 #smtp_server = mail.server.com
22 22 #smtp_username =
23 23 #smtp_password =
24 24 #smtp_port =
25 25 #smtp_use_tls = false
26 26 #smtp_use_ssl = true
27 27
28 28 [server:main]
29 29 ; COMMON HOST/IP CONFIG, This applies mostly to develop setup,
30 30 ; Host port for gunicorn are controlled by gunicorn_conf.py
31 31 host = 127.0.0.1
32 32 port = 10020
33 33
34 34
35 35 ; ###########################
36 36 ; GUNICORN APPLICATION SERVER
37 37 ; ###########################
38 38
39 39 ; run with gunicorn --config gunicorn_conf.py --paste rhodecode.ini
40 40
41 41 ; Module to use, this setting shouldn't be changed
42 42 use = egg:gunicorn#main
43 43
44 44 ; Prefix middleware for RhodeCode.
45 45 ; recommended when using proxy setup.
46 46 ; allows to set RhodeCode under a prefix in server.
47 47 ; eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
48 48 ; And set your prefix like: `prefix = /custom_prefix`
49 49 ; be sure to also set beaker.session.cookie_path = /custom_prefix if you need
50 50 ; to make your cookies only work on prefix url
51 51 [filter:proxy-prefix]
52 52 use = egg:PasteDeploy#prefix
53 53 prefix = /
54 54
55 55 [app:main]
56 56 ; The %(here)s variable will be replaced with the absolute path of parent directory
57 57 ; of this file
58 58 ; Each option in the app:main can be override by an environmental variable
59 59 ;
60 60 ;To override an option:
61 61 ;
62 62 ;RC_<KeyName>
63 63 ;Everything should be uppercase, . and - should be replaced by _.
64 64 ;For example, if you have these configuration settings:
65 65 ;rc_cache.repo_object.backend = foo
66 66 ;can be overridden by
67 67 ;export RC_CACHE_REPO_OBJECT_BACKEND=foo
68 68
69 69 use = egg:rhodecode-enterprise-ce
70 70
71 71 ; enable proxy prefix middleware, defined above
72 72 #filter-with = proxy-prefix
73 73
74 74 ; #############
75 75 ; DEBUG OPTIONS
76 76 ; #############
77 77
78 78 pyramid.reload_templates = true
79 79
80 80 # During development the we want to have the debug toolbar enabled
81 81 pyramid.includes =
82 82 pyramid_debugtoolbar
83 83
84 84 debugtoolbar.hosts = 0.0.0.0/0
85 85 debugtoolbar.exclude_prefixes =
86 86 /css
87 87 /fonts
88 88 /images
89 89 /js
90 90
91 91 ## RHODECODE PLUGINS ##
92 92 rhodecode.includes =
93 93 rhodecode.api
94 94
95 95
96 96 # api prefix url
97 97 rhodecode.api.url = /_admin/api
98 98
99 99 ; enable debug style page
100 100 debug_style = true
101 101
102 102 ; #################
103 103 ; END DEBUG OPTIONS
104 104 ; #################
105 105
106 106 ; encryption key used to encrypt social plugin tokens,
107 107 ; remote_urls with credentials etc, if not set it defaults to
108 108 ; `beaker.session.secret`
109 109 #rhodecode.encrypted_values.secret =
110 110
111 111 ; decryption strict mode (enabled by default). It controls if decryption raises
112 112 ; `SignatureVerificationError` in case of wrong key, or damaged encryption data.
113 113 #rhodecode.encrypted_values.strict = false
114 114
115 115 ; Pick algorithm for encryption. Either fernet (more secure) or aes (default)
116 116 ; fernet is safer, and we strongly recommend switching to it.
117 117 ; Due to backward compatibility aes is used as default.
118 118 #rhodecode.encrypted_values.algorithm = fernet
119 119
120 120 ; Return gzipped responses from RhodeCode (static files/application)
121 121 gzip_responses = false
122 122
123 123 ; Auto-generate javascript routes file on startup
124 124 generate_js_files = false
125 125
126 126 ; System global default language.
127 127 ; All available languages: en (default), be, de, es, fr, it, ja, pl, pt, ru, zh
128 128 lang = en
129 129
130 130 ; Perform a full repository scan and import on each server start.
131 131 ; Settings this to true could lead to very long startup time.
132 132 startup.import_repos = false
133 133
134 134 ; URL at which the application is running. This is used for Bootstrapping
135 135 ; requests in context when no web request is available. Used in ishell, or
136 136 ; SSH calls. Set this for events to receive proper url for SSH calls.
137 137 app.base_url = http://rhodecode.local
138 138
139 139 ; Host at which the Service API is running.
140 140 app.service_api.host = http://rhodecode.local:10020
141 141
142 142 ; Secret for Service API authentication.
143 143 app.service_api.token =
144 144
145 145 ; Unique application ID. Should be a random unique string for security.
146 146 app_instance_uuid = rc-production
147 147
148 148 ; Cut off limit for large diffs (size in bytes). If overall diff size on
149 149 ; commit, or pull request exceeds this limit this diff will be displayed
150 150 ; partially. E.g 512000 == 512Kb
151 151 cut_off_limit_diff = 512000
152 152
153 153 ; Cut off limit for large files inside diffs (size in bytes). Each individual
154 154 ; file inside diff which exceeds this limit will be displayed partially.
155 155 ; E.g 128000 == 128Kb
156 156 cut_off_limit_file = 128000
157 157
158 158 ; Use cached version of vcs repositories everywhere. Recommended to be `true`
159 159 vcs_full_cache = true
160 160
161 161 ; Force https in RhodeCode, fixes https redirects, assumes it's always https.
162 162 ; Normally this is controlled by proper flags sent from http server such as Nginx or Apache
163 163 force_https = false
164 164
165 165 ; use Strict-Transport-Security headers
166 166 use_htsts = false
167 167
168 168 ; Set to true if your repos are exposed using the dumb protocol
169 169 git_update_server_info = false
170 170
171 171 ; RSS/ATOM feed options
172 172 rss_cut_off_limit = 256000
173 173 rss_items_per_page = 10
174 174 rss_include_diff = false
175 175
176 176 ; gist URL alias, used to create nicer urls for gist. This should be an
177 177 ; url that does rewrites to _admin/gists/{gistid}.
178 178 ; example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
179 179 ; RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
180 180 gist_alias_url =
181 181
182 182 ; List of views (using glob pattern syntax) that AUTH TOKENS could be
183 183 ; used for access.
184 184 ; Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
185 185 ; came from the the logged in user who own this authentication token.
186 186 ; Additionally @TOKEN syntax can be used to bound the view to specific
187 187 ; authentication token. Such view would be only accessible when used together
188 188 ; with this authentication token
189 189 ; list of all views can be found under `/_admin/permissions/auth_token_access`
190 190 ; The list should be "," separated and on a single line.
191 191 ; Most common views to enable:
192 192
193 193 # RepoCommitsView:repo_commit_download
194 194 # RepoCommitsView:repo_commit_patch
195 195 # RepoCommitsView:repo_commit_raw
196 196 # RepoCommitsView:repo_commit_raw@TOKEN
197 197 # RepoFilesView:repo_files_diff
198 198 # RepoFilesView:repo_archivefile
199 199 # RepoFilesView:repo_file_raw
200 200 # GistView:*
201 201 api_access_controllers_whitelist =
202 202
203 203 ; Default encoding used to convert from and to unicode
204 204 ; can be also a comma separated list of encoding in case of mixed encodings
205 205 default_encoding = UTF-8
206 206
207 207 ; instance-id prefix
208 208 ; a prefix key for this instance used for cache invalidation when running
209 209 ; multiple instances of RhodeCode, make sure it's globally unique for
210 210 ; all running RhodeCode instances. Leave empty if you don't use it
211 211 instance_id =
212 212
213 213 ; Fallback authentication plugin. Set this to a plugin ID to force the usage
214 214 ; of an authentication plugin also if it is disabled by it's settings.
215 215 ; This could be useful if you are unable to log in to the system due to broken
216 216 ; authentication settings. Then you can enable e.g. the internal RhodeCode auth
217 217 ; module to log in again and fix the settings.
218 218 ; Available builtin plugin IDs (hash is part of the ID):
219 219 ; egg:rhodecode-enterprise-ce#rhodecode
220 220 ; egg:rhodecode-enterprise-ce#pam
221 221 ; egg:rhodecode-enterprise-ce#ldap
222 222 ; egg:rhodecode-enterprise-ce#jasig_cas
223 223 ; egg:rhodecode-enterprise-ce#headers
224 224 ; egg:rhodecode-enterprise-ce#crowd
225 225
226 226 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
227 227
228 228 ; Flag to control loading of legacy plugins in py:/path format
229 229 auth_plugin.import_legacy_plugins = true
230 230
231 231 ; alternative return HTTP header for failed authentication. Default HTTP
232 232 ; response is 401 HTTPUnauthorized. Currently HG clients have troubles with
233 233 ; handling that causing a series of failed authentication calls.
234 234 ; Set this variable to 403 to return HTTPForbidden, or any other HTTP code
235 235 ; This will be served instead of default 401 on bad authentication
236 236 auth_ret_code =
237 237
238 238 ; use special detection method when serving auth_ret_code, instead of serving
239 239 ; ret_code directly, use 401 initially (Which triggers credentials prompt)
240 240 ; and then serve auth_ret_code to clients
241 241 auth_ret_code_detection = false
242 242
243 243 ; locking return code. When repository is locked return this HTTP code. 2XX
244 244 ; codes don't break the transactions while 4XX codes do
245 245 lock_ret_code = 423
246 246
247 247 ; Filesystem location were repositories should be stored
248 248 repo_store.path = /var/opt/rhodecode_repo_store
249 249
250 250 ; allows to setup custom hooks in settings page
251 251 allow_custom_hooks_settings = true
252 252
253 253 ; Generated license token required for EE edition license.
254 254 ; New generated token value can be found in Admin > settings > license page.
255 255 license_token =
256 256
257 257 ; This flag hides sensitive information on the license page such as token, and license data
258 258 license.hide_license_info = false
259 259
260 260 ; supervisor connection uri, for managing supervisor and logs.
261 261 supervisor.uri =
262 262
263 263 ; supervisord group name/id we only want this RC instance to handle
264 264 supervisor.group_id = dev
265 265
266 266 ; Display extended labs settings
267 267 labs_settings_active = true
268 268
269 269 ; Custom exception store path, defaults to TMPDIR
270 270 ; This is used to store exception from RhodeCode in shared directory
271 271 #exception_tracker.store_path =
272 272
273 273 ; Send email with exception details when it happens
274 274 #exception_tracker.send_email = false
275 275
276 276 ; Comma separated list of recipients for exception emails,
277 277 ; e.g admin@rhodecode.com,devops@rhodecode.com
278 278 ; Can be left empty, then emails will be sent to ALL super-admins
279 279 #exception_tracker.send_email_recipients =
280 280
281 281 ; optional prefix to Add to email Subject
282 282 #exception_tracker.email_prefix = [RHODECODE ERROR]
283 283
284 284 ; NOTE: this setting IS DEPRECATED:
285 285 ; file_store backend is always enabled
286 286 #file_store.enabled = true
287 287
288 288 ; NOTE: this setting IS DEPRECATED:
289 289 ; file_store.backend = X -> use `file_store.backend.type = filesystem_v2` instead
290 290 ; Storage backend, available options are: local
291 291 #file_store.backend = local
292 292
293 293 ; NOTE: this setting IS DEPRECATED:
294 294 ; file_store.storage_path = X -> use `file_store.filesystem_v2.storage_path = X` instead
295 295 ; path to store the uploaded binaries and artifacts
296 296 #file_store.storage_path = /var/opt/rhodecode_data/file_store
297 297
298 298 ; Artifacts file-store, is used to store comment attachments and artifacts uploads.
299 299 ; file_store backend type: filesystem_v1, filesystem_v2 or objectstore (s3-based) are available as options
300 300 ; filesystem_v1 is backwards compat with pre 5.1 storage changes
301 301 ; new installations should choose filesystem_v2 or objectstore (s3-based), pick filesystem when migrating from
302 302 ; previous installations to keep the artifacts without a need of migration
303 303 #file_store.backend.type = filesystem_v2
304 304
305 305 ; filesystem options...
306 306 #file_store.filesystem_v1.storage_path = /var/opt/rhodecode_data/artifacts_file_store
307 307
308 308 ; filesystem_v2 options...
309 309 #file_store.filesystem_v2.storage_path = /var/opt/rhodecode_data/artifacts_file_store
310 310 #file_store.filesystem_v2.shards = 8
311 311
312 312 ; objectstore options...
313 313 ; url for s3 compatible storage that allows to upload artifacts
314 314 ; e.g http://minio:9000
315 315 #file_store.backend.type = objectstore
316 316 #file_store.objectstore.url = http://s3-minio:9000
317 317
318 318 ; a top-level bucket to put all other shards in
319 319 ; objects will be stored in rhodecode-file-store/shard-N based on the bucket_shards number
320 320 #file_store.objectstore.bucket = rhodecode-file-store
321 321
322 322 ; number of sharded buckets to create to distribute archives across
323 323 ; default is 8 shards
324 324 #file_store.objectstore.bucket_shards = 8
325 325
326 326 ; key for s3 auth
327 327 #file_store.objectstore.key = s3admin
328 328
329 329 ; secret for s3 auth
330 330 #file_store.objectstore.secret = s3secret4
331 331
332 332 ;region for s3 storage
333 333 #file_store.objectstore.region = eu-central-1
334 334
335 335 ; Redis url to acquire/check generation of archives locks
336 336 archive_cache.locking.url = redis://redis:6379/1
337 337
338 338 ; Storage backend, only 'filesystem' and 'objectstore' are available now
339 339 archive_cache.backend.type = filesystem
340 340
341 341 ; url for s3 compatible storage that allows to upload artifacts
342 342 ; e.g http://minio:9000
343 343 archive_cache.objectstore.url = http://s3-minio:9000
344 344
345 345 ; key for s3 auth
346 346 archive_cache.objectstore.key = key
347 347
348 348 ; secret for s3 auth
349 349 archive_cache.objectstore.secret = secret
350 350
351 351 ;region for s3 storage
352 352 archive_cache.objectstore.region = eu-central-1
353 353
354 354 ; number of sharded buckets to create to distribute archives across
355 355 ; default is 8 shards
356 356 archive_cache.objectstore.bucket_shards = 8
357 357
358 358 ; a top-level bucket to put all other shards in
359 359 ; objects will be stored in rhodecode-archive-cache/shard-N based on the bucket_shards number
360 360 archive_cache.objectstore.bucket = rhodecode-archive-cache
361 361
362 362 ; if true, this cache will try to retry with retry_attempts=N times waiting retry_backoff time
363 363 archive_cache.objectstore.retry = false
364 364
365 365 ; number of seconds to wait for next try using retry
366 366 archive_cache.objectstore.retry_backoff = 1
367 367
368 368 ; how many tries do do a retry fetch from this backend
369 369 archive_cache.objectstore.retry_attempts = 10
370 370
371 371 ; Default is $cache_dir/archive_cache if not set
372 372 ; Generated repo archives will be cached at this location
373 373 ; and served from the cache during subsequent requests for the same archive of
374 374 ; the repository. This path is important to be shared across filesystems and with
375 375 ; RhodeCode and vcsserver
376 376 archive_cache.filesystem.store_dir = /var/opt/rhodecode_data/archive_cache
377 377
378 378 ; The limit in GB sets how much data we cache before recycling last used, defaults to 10 gb
379 379 archive_cache.filesystem.cache_size_gb = 1
380 380
381 381 ; Eviction policy used to clear out after cache_size_gb limit is reached
382 382 archive_cache.filesystem.eviction_policy = least-recently-stored
383 383
384 384 ; By default cache uses sharding technique, this specifies how many shards are there
385 385 ; default is 8 shards
386 386 archive_cache.filesystem.cache_shards = 8
387 387
388 388 ; if true, this cache will try to retry with retry_attempts=N times waiting retry_backoff time
389 389 archive_cache.filesystem.retry = false
390 390
391 391 ; number of seconds to wait for next try using retry
392 392 archive_cache.filesystem.retry_backoff = 1
393 393
394 394 ; how many tries do do a retry fetch from this backend
395 395 archive_cache.filesystem.retry_attempts = 10
396 396
397 397
398 398 ; #############
399 399 ; CELERY CONFIG
400 400 ; #############
401 401
402 402 ; manually run celery: /path/to/celery worker --task-events --beat --app rhodecode.lib.celerylib.loader --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler --loglevel DEBUG --ini /path/to/rhodecode.ini
403 403
404 404 use_celery = true
405 405
406 406 ; path to store schedule database
407 407 #celerybeat-schedule.path =
408 408
409 409 ; connection url to the message broker (default redis)
410 410 celery.broker_url = redis://redis:6379/8
411 411
412 412 ; results backend to get results for (default redis)
413 413 celery.result_backend = redis://redis:6379/8
414 414
415 415 ; rabbitmq example
416 416 #celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
417 417
418 418 ; maximum tasks to execute before worker restart
419 419 celery.max_tasks_per_child = 20
420 420
421 421 ; tasks will never be sent to the queue, but executed locally instead.
422 422 celery.task_always_eager = false
423 423
424 424 ; #############
425 425 ; DOGPILE CACHE
426 426 ; #############
427 427
428 428 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
429 429 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
430 430 cache_dir = /var/opt/rhodecode_data
431 431
432 432 ; *********************************************
433 433 ; `sql_cache_short` cache for heavy SQL queries
434 434 ; Only supported backend is `memory_lru`
435 435 ; *********************************************
436 436 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
437 437 rc_cache.sql_cache_short.expiration_time = 30
438 438
439 439
440 440 ; *****************************************************
441 441 ; `cache_repo_longterm` cache for repo object instances
442 442 ; Only supported backend is `memory_lru`
443 443 ; *****************************************************
444 444 rc_cache.cache_repo_longterm.backend = dogpile.cache.rc.memory_lru
445 445 ; by default we use 30 Days, cache is still invalidated on push
446 446 rc_cache.cache_repo_longterm.expiration_time = 2592000
447 447 ; max items in LRU cache, set to smaller number to save memory, and expire last used caches
448 448 rc_cache.cache_repo_longterm.max_size = 10000
449 449
450 450
451 451 ; *********************************************
452 452 ; `cache_general` cache for general purpose use
453 453 ; for simplicity use rc.file_namespace backend,
454 454 ; for performance and scale use rc.redis
455 455 ; *********************************************
456 456 rc_cache.cache_general.backend = dogpile.cache.rc.file_namespace
457 457 rc_cache.cache_general.expiration_time = 43200
458 458 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
459 459 #rc_cache.cache_general.arguments.filename = /tmp/cache_general_db
460 460
461 461 ; alternative `cache_general` redis backend with distributed lock
462 462 #rc_cache.cache_general.backend = dogpile.cache.rc.redis
463 463 #rc_cache.cache_general.expiration_time = 300
464 464
465 465 ; redis_expiration_time needs to be greater then expiration_time
466 466 #rc_cache.cache_general.arguments.redis_expiration_time = 7200
467 467
468 468 #rc_cache.cache_general.arguments.host = localhost
469 469 #rc_cache.cache_general.arguments.port = 6379
470 470 #rc_cache.cache_general.arguments.db = 0
471 471 #rc_cache.cache_general.arguments.socket_timeout = 30
472 472 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
473 473 #rc_cache.cache_general.arguments.distributed_lock = true
474 474
475 475 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
476 476 #rc_cache.cache_general.arguments.lock_auto_renewal = true
477 477
478 478 ; *************************************************
479 479 ; `cache_perms` cache for permission tree, auth TTL
480 480 ; for simplicity use rc.file_namespace backend,
481 481 ; for performance and scale use rc.redis
482 482 ; *************************************************
483 483 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
484 484 rc_cache.cache_perms.expiration_time = 3600
485 485 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
486 486 #rc_cache.cache_perms.arguments.filename = /tmp/cache_perms_db
487 487
488 488 ; alternative `cache_perms` redis backend with distributed lock
489 489 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
490 490 #rc_cache.cache_perms.expiration_time = 300
491 491
492 492 ; redis_expiration_time needs to be greater then expiration_time
493 493 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
494 494
495 495 #rc_cache.cache_perms.arguments.host = localhost
496 496 #rc_cache.cache_perms.arguments.port = 6379
497 497 #rc_cache.cache_perms.arguments.db = 0
498 498 #rc_cache.cache_perms.arguments.socket_timeout = 30
499 499 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
500 500 #rc_cache.cache_perms.arguments.distributed_lock = true
501 501
502 502 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
503 503 #rc_cache.cache_perms.arguments.lock_auto_renewal = true
504 504
505 505 ; ***************************************************
506 506 ; `cache_repo` cache for file tree, Readme, RSS FEEDS
507 507 ; for simplicity use rc.file_namespace backend,
508 508 ; for performance and scale use rc.redis
509 509 ; ***************************************************
510 510 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
511 511 rc_cache.cache_repo.expiration_time = 2592000
512 512 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
513 513 #rc_cache.cache_repo.arguments.filename = /tmp/cache_repo_db
514 514
515 515 ; alternative `cache_repo` redis backend with distributed lock
516 516 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
517 517 #rc_cache.cache_repo.expiration_time = 2592000
518 518
519 519 ; redis_expiration_time needs to be greater then expiration_time
520 520 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
521 521
522 522 #rc_cache.cache_repo.arguments.host = localhost
523 523 #rc_cache.cache_repo.arguments.port = 6379
524 524 #rc_cache.cache_repo.arguments.db = 1
525 525 #rc_cache.cache_repo.arguments.socket_timeout = 30
526 526 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
527 527 #rc_cache.cache_repo.arguments.distributed_lock = true
528 528
529 529 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
530 530 #rc_cache.cache_repo.arguments.lock_auto_renewal = true
531 531
532 532 ; ##############
533 533 ; BEAKER SESSION
534 534 ; ##############
535 535
536 536 ; beaker.session.type is type of storage options for the logged users sessions. Current allowed
537 537 ; types are file, ext:redis, ext:database, ext:memcached
538 538 ; Fastest ones are ext:redis and ext:database, DO NOT use memory type for session
539 539 #beaker.session.type = file
540 540 #beaker.session.data_dir = %(here)s/data/sessions
541 541
542 542 ; Redis based sessions
543 543 beaker.session.type = ext:redis
544 544 beaker.session.url = redis://redis:6379/2
545 545
546 546 ; DB based session, fast, and allows easy management over logged in users
547 547 #beaker.session.type = ext:database
548 548 #beaker.session.table_name = db_session
549 549 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
550 550 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
551 551 #beaker.session.sa.pool_recycle = 3600
552 552 #beaker.session.sa.echo = false
553 553
554 554 beaker.session.key = rhodecode
555 555 beaker.session.secret = develop-rc-uytcxaz
556 556 beaker.session.lock_dir = /data_ramdisk/lock
557 557
558 558 ; Secure encrypted cookie. Requires AES and AES python libraries
559 559 ; you must disable beaker.session.secret to use this
560 560 #beaker.session.encrypt_key = key_for_encryption
561 561 #beaker.session.validate_key = validation_key
562 562
563 563 ; Sets session as invalid (also logging out user) if it haven not been
564 564 ; accessed for given amount of time in seconds
565 565 beaker.session.timeout = 2592000
566 566 beaker.session.httponly = true
567 567
568 568 ; Path to use for the cookie. Set to prefix if you use prefix middleware
569 569 #beaker.session.cookie_path = /custom_prefix
570 570
571 571 ; Set https secure cookie
572 572 beaker.session.secure = false
573 573
574 574 ; default cookie expiration time in seconds, set to `true` to set expire
575 575 ; at browser close
576 576 #beaker.session.cookie_expires = 3600
577 577
578 578 ; #############################
579 579 ; SEARCH INDEXING CONFIGURATION
580 580 ; #############################
581 581
582 582 ; Full text search indexer is available in rhodecode-tools under
583 583 ; `rhodecode-tools index` command
584 584
585 585 ; WHOOSH Backend, doesn't require additional services to run
586 586 ; it works good with few dozen repos
587 587 search.module = rhodecode.lib.index.whoosh
588 588 search.location = %(here)s/data/index
589 589
590 590 ; ####################
591 591 ; CHANNELSTREAM CONFIG
592 592 ; ####################
593 593
594 594 ; channelstream enables persistent connections and live notification
595 595 ; in the system. It's also used by the chat system
596 596
597 597 channelstream.enabled = true
598 598
599 599 ; server address for channelstream server on the backend
600 600 channelstream.server = channelstream:9800
601 601
602 602 ; location of the channelstream server from outside world
603 603 ; use ws:// for http or wss:// for https. This address needs to be handled
604 604 ; by external HTTP server such as Nginx or Apache
605 605 ; see Nginx/Apache configuration examples in our docs
606 606 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
607 607 channelstream.secret = ENV_GENERATED
608 608 channelstream.history.location = /var/opt/rhodecode_data/channelstream_history
609 609
610 610 ; Internal application path that Javascript uses to connect into.
611 611 ; If you use proxy-prefix the prefix should be added before /_channelstream
612 612 channelstream.proxy_path = /_channelstream
613 613
614 614
615 615 ; ##############################
616 616 ; MAIN RHODECODE DATABASE CONFIG
617 617 ; ##############################
618 618
619 619 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
620 620 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
621 621 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode?charset=utf8
622 622 ; pymysql is an alternative driver for MySQL, use in case of problems with default one
623 623 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
624 624
625 625 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
626 626
627 627 ; see sqlalchemy docs for other advanced settings
628 628 ; print the sql statements to output
629 629 sqlalchemy.db1.echo = false
630 630
631 631 ; recycle the connections after this amount of seconds
632 632 sqlalchemy.db1.pool_recycle = 3600
633 633
634 634 ; the number of connections to keep open inside the connection pool.
635 635 ; 0 indicates no limit
636 636 ; the general calculus with gevent is:
637 637 ; if your system allows 500 concurrent greenlets (max_connections) that all do database access,
638 638 ; then increase pool size + max overflow so that they add up to 500.
639 639 #sqlalchemy.db1.pool_size = 5
640 640
641 641 ; The number of connections to allow in connection pool "overflow", that is
642 642 ; connections that can be opened above and beyond the pool_size setting,
643 643 ; which defaults to five.
644 644 #sqlalchemy.db1.max_overflow = 10
645 645
646 646 ; Connection check ping, used to detect broken database connections
647 647 ; could be enabled to better handle cases if MySQL has gone away errors
648 648 #sqlalchemy.db1.ping_connection = true
649 649
650 650 ; ##########
651 651 ; VCS CONFIG
652 652 ; ##########
653 653 vcs.server.enable = true
654 654 vcs.server = vcsserver:10010
655 655
656 656 ; Web server connectivity protocol, responsible for web based VCS operations
657 657 ; Available protocols are:
658 658 ; `http` - use http-rpc backend (default)
659 659 vcs.server.protocol = http
660 660
661 661 ; Push/Pull operations protocol, available options are:
662 662 ; `http` - use http-rpc backend (default)
663 663 vcs.scm_app_implementation = http
664 664
665 665 ; Push/Pull operations hooks protocol, available options are:
666 666 ; `http` - use http-rpc backend (default)
667 667 ; `celery` - use celery based hooks
668 668 #DEPRECATED:vcs.hooks.protocol = http
669 669 vcs.hooks.protocol.v2 = celery
670 670
671 671 ; Host on which this instance is listening for hooks. vcsserver will call this host to pull/push hooks so it should be
672 672 ; accessible via network.
673 673 ; Use vcs.hooks.host = "*" to bind to current hostname (for Docker)
674 674 vcs.hooks.host = *
675 675
676 676 ; Start VCSServer with this instance as a subprocess, useful for development
677 677 vcs.start_server = false
678 678
679 679 ; List of enabled VCS backends, available options are:
680 680 ; `hg` - mercurial
681 681 ; `git` - git
682 682 ; `svn` - subversion
683 683 vcs.backends = hg, git, svn
684 684
685 685 ; Wait this number of seconds before killing connection to the vcsserver
686 686 vcs.connection_timeout = 3600
687 687
688 688 ; Cache flag to cache vcsserver remote calls locally
689 689 ; It uses cache_region `cache_repo`
690 690 vcs.methods.cache = true
691 691
692 ; Filesystem location where Git lfs objects should be stored
693 vcs.git.lfs.storage_location = /var/opt/rhodecode_repo_store/.cache/git_lfs_store
694
695 ; Filesystem location where Mercurial largefile objects should be stored
696 vcs.hg.largefiles.storage_location = /var/opt/rhodecode_repo_store/.cache/hg_largefiles_store
697
692 698 ; ####################################################
693 699 ; Subversion proxy support (mod_dav_svn)
694 700 ; Maps RhodeCode repo groups into SVN paths for Apache
695 701 ; ####################################################
696 702
697 703 ; Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
698 704 ; Set a numeric version for your current SVN e.g 1.8, or 1.12
699 705 ; Legacy available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
700 706 #vcs.svn.compatible_version = 1.8
701 707
702 708 ; Redis connection settings for svn integrations logic
703 709 ; This connection string needs to be the same on ce and vcsserver
704 710 vcs.svn.redis_conn = redis://redis:6379/0
705 711
706 712 ; Enable SVN proxy of requests over HTTP
707 713 vcs.svn.proxy.enabled = true
708 714
709 715 ; host to connect to running SVN subsystem
710 716 vcs.svn.proxy.host = http://svn:8090
711 717
712 718 ; Enable or disable the config file generation.
713 719 svn.proxy.generate_config = true
714 720
715 721 ; Generate config file with `SVNListParentPath` set to `On`.
716 722 svn.proxy.list_parent_path = true
717 723
718 724 ; Set location and file name of generated config file.
719 725 svn.proxy.config_file_path = /etc/rhodecode/conf/svn/mod_dav_svn.conf
720 726
721 727 ; alternative mod_dav config template. This needs to be a valid mako template
722 728 ; Example template can be found in the source code:
723 729 ; rhodecode/apps/svn_support/templates/mod-dav-svn.conf.mako
724 730 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
725 731
726 732 ; Used as a prefix to the `Location` block in the generated config file.
727 733 ; In most cases it should be set to `/`.
728 734 svn.proxy.location_root = /
729 735
730 736 ; Command to reload the mod dav svn configuration on change.
731 737 ; Example: `/etc/init.d/apache2 reload` or /home/USER/apache_reload.sh
732 738 ; Make sure user who runs RhodeCode process is allowed to reload Apache
733 739 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
734 740
735 741 ; If the timeout expires before the reload command finishes, the command will
736 742 ; be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
737 743 #svn.proxy.reload_timeout = 10
738 744
739 745 ; ####################
740 746 ; SSH Support Settings
741 747 ; ####################
742 748
743 749 ; Defines if a custom authorized_keys file should be created and written on
744 750 ; any change user ssh keys. Setting this to false also disables possibility
745 751 ; of adding SSH keys by users from web interface. Super admins can still
746 752 ; manage SSH Keys.
747 753 ssh.generate_authorized_keyfile = true
748 754
749 755 ; Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
750 756 # ssh.authorized_keys_ssh_opts =
751 757
752 758 ; Path to the authorized_keys file where the generate entries are placed.
753 759 ; It is possible to have multiple key files specified in `sshd_config` e.g.
754 760 ; AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
755 761 ssh.authorized_keys_file_path = /etc/rhodecode/conf/ssh/authorized_keys_rhodecode
756 762
757 763 ; Command to execute the SSH wrapper. The binary is available in the
758 764 ; RhodeCode installation directory.
759 765 ; legacy: /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper
760 766 ; new rewrite: /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper-v2
761 767 #DEPRECATED: ssh.wrapper_cmd = /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper
762 768 ssh.wrapper_cmd.v2 = /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper-v2
763 769
764 770 ; Allow shell when executing the ssh-wrapper command
765 771 ssh.wrapper_cmd_allow_shell = false
766 772
767 773 ; Enables logging, and detailed output send back to the client during SSH
768 774 ; operations. Useful for debugging, shouldn't be used in production.
769 775 ssh.enable_debug_logging = true
770 776
771 777 ; Paths to binary executable, by default they are the names, but we can
772 778 ; override them if we want to use a custom one
773 779 ssh.executable.hg = /usr/local/bin/rhodecode_bin/vcs_bin/hg
774 780 ssh.executable.git = /usr/local/bin/rhodecode_bin/vcs_bin/git
775 781 ssh.executable.svn = /usr/local/bin/rhodecode_bin/vcs_bin/svnserve
776 782
777 783 ; Enables SSH key generator web interface. Disabling this still allows users
778 784 ; to add their own keys.
779 785 ssh.enable_ui_key_generator = true
780 786
781 787 ; Statsd client config, this is used to send metrics to statsd
782 788 ; We recommend setting statsd_exported and scrape them using Prometheus
783 789 #statsd.enabled = false
784 790 #statsd.statsd_host = 0.0.0.0
785 791 #statsd.statsd_port = 8125
786 792 #statsd.statsd_prefix =
787 793 #statsd.statsd_ipv6 = false
788 794
789 795 ; configure logging automatically at server startup set to false
790 796 ; to use the below custom logging config.
791 797 ; RC_LOGGING_FORMATTER
792 798 ; RC_LOGGING_LEVEL
793 799 ; env variables can control the settings for logging in case of autoconfigure
794 800
795 801 #logging.autoconfigure = true
796 802
797 803 ; specify your own custom logging config file to configure logging
798 804 #logging.logging_conf_file = /path/to/custom_logging.ini
799 805
800 806 ; Dummy marker to add new entries after.
801 807 ; Add any custom entries below. Please don't remove this marker.
802 808 custom.conf = 1
803 809
804 810
805 811 ; #####################
806 812 ; LOGGING CONFIGURATION
807 813 ; #####################
808 814
809 815 [loggers]
810 816 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
811 817
812 818 [handlers]
813 819 keys = console, console_sql
814 820
815 821 [formatters]
816 822 keys = generic, json, color_formatter, color_formatter_sql
817 823
818 824 ; #######
819 825 ; LOGGERS
820 826 ; #######
821 827 [logger_root]
822 828 level = NOTSET
823 829 handlers = console
824 830
825 831 [logger_sqlalchemy]
826 832 level = INFO
827 833 handlers = console_sql
828 834 qualname = sqlalchemy.engine
829 835 propagate = 0
830 836
831 837 [logger_beaker]
832 838 level = DEBUG
833 839 handlers =
834 840 qualname = beaker.container
835 841 propagate = 1
836 842
837 843 [logger_rhodecode]
838 844 level = DEBUG
839 845 handlers =
840 846 qualname = rhodecode
841 847 propagate = 1
842 848
843 849 [logger_ssh_wrapper]
844 850 level = DEBUG
845 851 handlers =
846 852 qualname = ssh_wrapper
847 853 propagate = 1
848 854
849 855 [logger_celery]
850 856 level = DEBUG
851 857 handlers =
852 858 qualname = celery
853 859
854 860
855 861 ; ########
856 862 ; HANDLERS
857 863 ; ########
858 864
859 865 [handler_console]
860 866 class = StreamHandler
861 867 args = (sys.stderr, )
862 868 level = DEBUG
863 869 ; To enable JSON formatted logs replace 'generic/color_formatter' with 'json'
864 870 ; This allows sending properly formatted logs to grafana loki or elasticsearch
865 871 formatter = color_formatter
866 872
867 873 [handler_console_sql]
868 874 ; "level = DEBUG" logs SQL queries and results.
869 875 ; "level = INFO" logs SQL queries.
870 876 ; "level = WARN" logs neither. (Recommended for production systems.)
871 877 class = StreamHandler
872 878 args = (sys.stderr, )
873 879 level = WARN
874 880 ; To enable JSON formatted logs replace 'generic/color_formatter_sql' with 'json'
875 881 ; This allows sending properly formatted logs to grafana loki or elasticsearch
876 882 formatter = color_formatter_sql
877 883
878 884 ; ##########
879 885 ; FORMATTERS
880 886 ; ##########
881 887
882 888 [formatter_generic]
883 889 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
884 890 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
885 891 datefmt = %Y-%m-%d %H:%M:%S
886 892
887 893 [formatter_color_formatter]
888 894 class = rhodecode.lib.logging_formatter.ColorFormatter
889 895 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
890 896 datefmt = %Y-%m-%d %H:%M:%S
891 897
892 898 [formatter_color_formatter_sql]
893 899 class = rhodecode.lib.logging_formatter.ColorFormatterSql
894 900 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
895 901 datefmt = %Y-%m-%d %H:%M:%S
896 902
897 903 [formatter_json]
898 904 format = %(timestamp)s %(levelname)s %(name)s %(message)s %(req_id)s
899 905 class = rhodecode.lib._vendor.jsonlogger.JsonFormatter
@@ -1,867 +1,873 b''
1 1
2 2 ; #########################################
3 3 ; RHODECODE COMMUNITY EDITION CONFIGURATION
4 4 ; #########################################
5 5
6 6 [DEFAULT]
7 7 ; Debug flag sets all loggers to debug, and enables request tracking
8 8 debug = false
9 9
10 10 ; ########################################################################
11 11 ; EMAIL CONFIGURATION
12 12 ; These settings will be used by the RhodeCode mailing system
13 13 ; ########################################################################
14 14
15 15 ; prefix all emails subjects with given prefix, helps filtering out emails
16 16 #email_prefix = [RhodeCode]
17 17
18 18 ; email FROM address all mails will be sent
19 19 #app_email_from = rhodecode-noreply@localhost
20 20
21 21 #smtp_server = mail.server.com
22 22 #smtp_username =
23 23 #smtp_password =
24 24 #smtp_port =
25 25 #smtp_use_tls = false
26 26 #smtp_use_ssl = true
27 27
28 28 [server:main]
29 29 ; COMMON HOST/IP CONFIG, This applies mostly to develop setup,
30 30 ; Host port for gunicorn are controlled by gunicorn_conf.py
31 31 host = 127.0.0.1
32 32 port = 10020
33 33
34 34
35 35 ; ###########################
36 36 ; GUNICORN APPLICATION SERVER
37 37 ; ###########################
38 38
39 39 ; run with gunicorn --config gunicorn_conf.py --paste rhodecode.ini
40 40
41 41 ; Module to use, this setting shouldn't be changed
42 42 use = egg:gunicorn#main
43 43
44 44 ; Prefix middleware for RhodeCode.
45 45 ; recommended when using proxy setup.
46 46 ; allows to set RhodeCode under a prefix in server.
47 47 ; eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
48 48 ; And set your prefix like: `prefix = /custom_prefix`
49 49 ; be sure to also set beaker.session.cookie_path = /custom_prefix if you need
50 50 ; to make your cookies only work on prefix url
51 51 [filter:proxy-prefix]
52 52 use = egg:PasteDeploy#prefix
53 53 prefix = /
54 54
55 55 [app:main]
56 56 ; The %(here)s variable will be replaced with the absolute path of parent directory
57 57 ; of this file
58 58 ; Each option in the app:main can be override by an environmental variable
59 59 ;
60 60 ;To override an option:
61 61 ;
62 62 ;RC_<KeyName>
63 63 ;Everything should be uppercase, . and - should be replaced by _.
64 64 ;For example, if you have these configuration settings:
65 65 ;rc_cache.repo_object.backend = foo
66 66 ;can be overridden by
67 67 ;export RC_CACHE_REPO_OBJECT_BACKEND=foo
68 68
69 69 use = egg:rhodecode-enterprise-ce
70 70
71 71 ; enable proxy prefix middleware, defined above
72 72 #filter-with = proxy-prefix
73 73
74 74 ; encryption key used to encrypt social plugin tokens,
75 75 ; remote_urls with credentials etc, if not set it defaults to
76 76 ; `beaker.session.secret`
77 77 #rhodecode.encrypted_values.secret =
78 78
79 79 ; decryption strict mode (enabled by default). It controls if decryption raises
80 80 ; `SignatureVerificationError` in case of wrong key, or damaged encryption data.
81 81 #rhodecode.encrypted_values.strict = false
82 82
83 83 ; Pick algorithm for encryption. Either fernet (more secure) or aes (default)
84 84 ; fernet is safer, and we strongly recommend switching to it.
85 85 ; Due to backward compatibility aes is used as default.
86 86 #rhodecode.encrypted_values.algorithm = fernet
87 87
88 88 ; Return gzipped responses from RhodeCode (static files/application)
89 89 gzip_responses = false
90 90
91 91 ; Auto-generate javascript routes file on startup
92 92 generate_js_files = false
93 93
94 94 ; System global default language.
95 95 ; All available languages: en (default), be, de, es, fr, it, ja, pl, pt, ru, zh
96 96 lang = en
97 97
98 98 ; Perform a full repository scan and import on each server start.
99 99 ; Settings this to true could lead to very long startup time.
100 100 startup.import_repos = false
101 101
102 102 ; URL at which the application is running. This is used for Bootstrapping
103 103 ; requests in context when no web request is available. Used in ishell, or
104 104 ; SSH calls. Set this for events to receive proper url for SSH calls.
105 105 app.base_url = http://rhodecode.local
106 106
107 107 ; Host at which the Service API is running.
108 108 app.service_api.host = http://rhodecode.local:10020
109 109
110 110 ; Secret for Service API authentication.
111 111 app.service_api.token =
112 112
113 113 ; Unique application ID. Should be a random unique string for security.
114 114 app_instance_uuid = rc-production
115 115
116 116 ; Cut off limit for large diffs (size in bytes). If overall diff size on
117 117 ; commit, or pull request exceeds this limit this diff will be displayed
118 118 ; partially. E.g 512000 == 512Kb
119 119 cut_off_limit_diff = 512000
120 120
121 121 ; Cut off limit for large files inside diffs (size in bytes). Each individual
122 122 ; file inside diff which exceeds this limit will be displayed partially.
123 123 ; E.g 128000 == 128Kb
124 124 cut_off_limit_file = 128000
125 125
126 126 ; Use cached version of vcs repositories everywhere. Recommended to be `true`
127 127 vcs_full_cache = true
128 128
129 129 ; Force https in RhodeCode, fixes https redirects, assumes it's always https.
130 130 ; Normally this is controlled by proper flags sent from http server such as Nginx or Apache
131 131 force_https = false
132 132
133 133 ; use Strict-Transport-Security headers
134 134 use_htsts = false
135 135
136 136 ; Set to true if your repos are exposed using the dumb protocol
137 137 git_update_server_info = false
138 138
139 139 ; RSS/ATOM feed options
140 140 rss_cut_off_limit = 256000
141 141 rss_items_per_page = 10
142 142 rss_include_diff = false
143 143
144 144 ; gist URL alias, used to create nicer urls for gist. This should be an
145 145 ; url that does rewrites to _admin/gists/{gistid}.
146 146 ; example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
147 147 ; RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
148 148 gist_alias_url =
149 149
150 150 ; List of views (using glob pattern syntax) that AUTH TOKENS could be
151 151 ; used for access.
152 152 ; Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
153 153 ; came from the the logged in user who own this authentication token.
154 154 ; Additionally @TOKEN syntax can be used to bound the view to specific
155 155 ; authentication token. Such view would be only accessible when used together
156 156 ; with this authentication token
157 157 ; list of all views can be found under `/_admin/permissions/auth_token_access`
158 158 ; The list should be "," separated and on a single line.
159 159 ; Most common views to enable:
160 160
161 161 # RepoCommitsView:repo_commit_download
162 162 # RepoCommitsView:repo_commit_patch
163 163 # RepoCommitsView:repo_commit_raw
164 164 # RepoCommitsView:repo_commit_raw@TOKEN
165 165 # RepoFilesView:repo_files_diff
166 166 # RepoFilesView:repo_archivefile
167 167 # RepoFilesView:repo_file_raw
168 168 # GistView:*
169 169 api_access_controllers_whitelist =
170 170
171 171 ; Default encoding used to convert from and to unicode
172 172 ; can be also a comma separated list of encoding in case of mixed encodings
173 173 default_encoding = UTF-8
174 174
175 175 ; instance-id prefix
176 176 ; a prefix key for this instance used for cache invalidation when running
177 177 ; multiple instances of RhodeCode, make sure it's globally unique for
178 178 ; all running RhodeCode instances. Leave empty if you don't use it
179 179 instance_id =
180 180
181 181 ; Fallback authentication plugin. Set this to a plugin ID to force the usage
182 182 ; of an authentication plugin also if it is disabled by it's settings.
183 183 ; This could be useful if you are unable to log in to the system due to broken
184 184 ; authentication settings. Then you can enable e.g. the internal RhodeCode auth
185 185 ; module to log in again and fix the settings.
186 186 ; Available builtin plugin IDs (hash is part of the ID):
187 187 ; egg:rhodecode-enterprise-ce#rhodecode
188 188 ; egg:rhodecode-enterprise-ce#pam
189 189 ; egg:rhodecode-enterprise-ce#ldap
190 190 ; egg:rhodecode-enterprise-ce#jasig_cas
191 191 ; egg:rhodecode-enterprise-ce#headers
192 192 ; egg:rhodecode-enterprise-ce#crowd
193 193
194 194 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
195 195
196 196 ; Flag to control loading of legacy plugins in py:/path format
197 197 auth_plugin.import_legacy_plugins = true
198 198
199 199 ; alternative return HTTP header for failed authentication. Default HTTP
200 200 ; response is 401 HTTPUnauthorized. Currently HG clients have troubles with
201 201 ; handling that causing a series of failed authentication calls.
202 202 ; Set this variable to 403 to return HTTPForbidden, or any other HTTP code
203 203 ; This will be served instead of default 401 on bad authentication
204 204 auth_ret_code =
205 205
206 206 ; use special detection method when serving auth_ret_code, instead of serving
207 207 ; ret_code directly, use 401 initially (Which triggers credentials prompt)
208 208 ; and then serve auth_ret_code to clients
209 209 auth_ret_code_detection = false
210 210
211 211 ; locking return code. When repository is locked return this HTTP code. 2XX
212 212 ; codes don't break the transactions while 4XX codes do
213 213 lock_ret_code = 423
214 214
215 215 ; Filesystem location were repositories should be stored
216 216 repo_store.path = /var/opt/rhodecode_repo_store
217 217
218 218 ; allows to setup custom hooks in settings page
219 219 allow_custom_hooks_settings = true
220 220
221 221 ; Generated license token required for EE edition license.
222 222 ; New generated token value can be found in Admin > settings > license page.
223 223 license_token =
224 224
225 225 ; This flag hides sensitive information on the license page such as token, and license data
226 226 license.hide_license_info = false
227 227
228 228 ; supervisor connection uri, for managing supervisor and logs.
229 229 supervisor.uri =
230 230
231 231 ; supervisord group name/id we only want this RC instance to handle
232 232 supervisor.group_id = prod
233 233
234 234 ; Display extended labs settings
235 235 labs_settings_active = true
236 236
237 237 ; Custom exception store path, defaults to TMPDIR
238 238 ; This is used to store exception from RhodeCode in shared directory
239 239 #exception_tracker.store_path =
240 240
241 241 ; Send email with exception details when it happens
242 242 #exception_tracker.send_email = false
243 243
244 244 ; Comma separated list of recipients for exception emails,
245 245 ; e.g admin@rhodecode.com,devops@rhodecode.com
246 246 ; Can be left empty, then emails will be sent to ALL super-admins
247 247 #exception_tracker.send_email_recipients =
248 248
249 249 ; optional prefix to Add to email Subject
250 250 #exception_tracker.email_prefix = [RHODECODE ERROR]
251 251
252 252 ; NOTE: this setting IS DEPRECATED:
253 253 ; file_store backend is always enabled
254 254 #file_store.enabled = true
255 255
256 256 ; NOTE: this setting IS DEPRECATED:
257 257 ; file_store.backend = X -> use `file_store.backend.type = filesystem_v2` instead
258 258 ; Storage backend, available options are: local
259 259 #file_store.backend = local
260 260
261 261 ; NOTE: this setting IS DEPRECATED:
262 262 ; file_store.storage_path = X -> use `file_store.filesystem_v2.storage_path = X` instead
263 263 ; path to store the uploaded binaries and artifacts
264 264 #file_store.storage_path = /var/opt/rhodecode_data/file_store
265 265
266 266 ; Artifacts file-store, is used to store comment attachments and artifacts uploads.
267 267 ; file_store backend type: filesystem_v1, filesystem_v2 or objectstore (s3-based) are available as options
268 268 ; filesystem_v1 is backwards compat with pre 5.1 storage changes
269 269 ; new installations should choose filesystem_v2 or objectstore (s3-based), pick filesystem when migrating from
270 270 ; previous installations to keep the artifacts without a need of migration
271 271 #file_store.backend.type = filesystem_v2
272 272
273 273 ; filesystem options...
274 274 #file_store.filesystem_v1.storage_path = /var/opt/rhodecode_data/artifacts_file_store
275 275
276 276 ; filesystem_v2 options...
277 277 #file_store.filesystem_v2.storage_path = /var/opt/rhodecode_data/artifacts_file_store
278 278 #file_store.filesystem_v2.shards = 8
279 279
280 280 ; objectstore options...
281 281 ; url for s3 compatible storage that allows to upload artifacts
282 282 ; e.g http://minio:9000
283 283 #file_store.backend.type = objectstore
284 284 #file_store.objectstore.url = http://s3-minio:9000
285 285
286 286 ; a top-level bucket to put all other shards in
287 287 ; objects will be stored in rhodecode-file-store/shard-N based on the bucket_shards number
288 288 #file_store.objectstore.bucket = rhodecode-file-store
289 289
290 290 ; number of sharded buckets to create to distribute archives across
291 291 ; default is 8 shards
292 292 #file_store.objectstore.bucket_shards = 8
293 293
294 294 ; key for s3 auth
295 295 #file_store.objectstore.key = s3admin
296 296
297 297 ; secret for s3 auth
298 298 #file_store.objectstore.secret = s3secret4
299 299
300 300 ;region for s3 storage
301 301 #file_store.objectstore.region = eu-central-1
302 302
303 303 ; Redis url to acquire/check generation of archives locks
304 304 archive_cache.locking.url = redis://redis:6379/1
305 305
306 306 ; Storage backend, only 'filesystem' and 'objectstore' are available now
307 307 archive_cache.backend.type = filesystem
308 308
309 309 ; url for s3 compatible storage that allows to upload artifacts
310 310 ; e.g http://minio:9000
311 311 archive_cache.objectstore.url = http://s3-minio:9000
312 312
313 313 ; key for s3 auth
314 314 archive_cache.objectstore.key = key
315 315
316 316 ; secret for s3 auth
317 317 archive_cache.objectstore.secret = secret
318 318
319 319 ;region for s3 storage
320 320 archive_cache.objectstore.region = eu-central-1
321 321
322 322 ; number of sharded buckets to create to distribute archives across
323 323 ; default is 8 shards
324 324 archive_cache.objectstore.bucket_shards = 8
325 325
326 326 ; a top-level bucket to put all other shards in
327 327 ; objects will be stored in rhodecode-archive-cache/shard-N based on the bucket_shards number
328 328 archive_cache.objectstore.bucket = rhodecode-archive-cache
329 329
330 330 ; if true, this cache will try to retry with retry_attempts=N times waiting retry_backoff time
331 331 archive_cache.objectstore.retry = false
332 332
333 333 ; number of seconds to wait for next try using retry
334 334 archive_cache.objectstore.retry_backoff = 1
335 335
336 336 ; how many tries do do a retry fetch from this backend
337 337 archive_cache.objectstore.retry_attempts = 10
338 338
339 339 ; Default is $cache_dir/archive_cache if not set
340 340 ; Generated repo archives will be cached at this location
341 341 ; and served from the cache during subsequent requests for the same archive of
342 342 ; the repository. This path is important to be shared across filesystems and with
343 343 ; RhodeCode and vcsserver
344 344 archive_cache.filesystem.store_dir = /var/opt/rhodecode_data/archive_cache
345 345
346 346 ; The limit in GB sets how much data we cache before recycling last used, defaults to 10 gb
347 347 archive_cache.filesystem.cache_size_gb = 40
348 348
349 349 ; Eviction policy used to clear out after cache_size_gb limit is reached
350 350 archive_cache.filesystem.eviction_policy = least-recently-stored
351 351
352 352 ; By default cache uses sharding technique, this specifies how many shards are there
353 353 ; default is 8 shards
354 354 archive_cache.filesystem.cache_shards = 8
355 355
356 356 ; if true, this cache will try to retry with retry_attempts=N times waiting retry_backoff time
357 357 archive_cache.filesystem.retry = false
358 358
359 359 ; number of seconds to wait for next try using retry
360 360 archive_cache.filesystem.retry_backoff = 1
361 361
362 362 ; how many tries do do a retry fetch from this backend
363 363 archive_cache.filesystem.retry_attempts = 10
364 364
365 365
366 366 ; #############
367 367 ; CELERY CONFIG
368 368 ; #############
369 369
370 370 ; manually run celery: /path/to/celery worker --task-events --beat --app rhodecode.lib.celerylib.loader --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler --loglevel DEBUG --ini /path/to/rhodecode.ini
371 371
372 372 use_celery = true
373 373
374 374 ; path to store schedule database
375 375 #celerybeat-schedule.path =
376 376
377 377 ; connection url to the message broker (default redis)
378 378 celery.broker_url = redis://redis:6379/8
379 379
380 380 ; results backend to get results for (default redis)
381 381 celery.result_backend = redis://redis:6379/8
382 382
383 383 ; rabbitmq example
384 384 #celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
385 385
386 386 ; maximum tasks to execute before worker restart
387 387 celery.max_tasks_per_child = 20
388 388
389 389 ; tasks will never be sent to the queue, but executed locally instead.
390 390 celery.task_always_eager = false
391 391
392 392 ; #############
393 393 ; DOGPILE CACHE
394 394 ; #############
395 395
396 396 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
397 397 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
398 398 cache_dir = /var/opt/rhodecode_data
399 399
400 400 ; *********************************************
401 401 ; `sql_cache_short` cache for heavy SQL queries
402 402 ; Only supported backend is `memory_lru`
403 403 ; *********************************************
404 404 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
405 405 rc_cache.sql_cache_short.expiration_time = 30
406 406
407 407
408 408 ; *****************************************************
409 409 ; `cache_repo_longterm` cache for repo object instances
410 410 ; Only supported backend is `memory_lru`
411 411 ; *****************************************************
412 412 rc_cache.cache_repo_longterm.backend = dogpile.cache.rc.memory_lru
413 413 ; by default we use 30 Days, cache is still invalidated on push
414 414 rc_cache.cache_repo_longterm.expiration_time = 2592000
415 415 ; max items in LRU cache, set to smaller number to save memory, and expire last used caches
416 416 rc_cache.cache_repo_longterm.max_size = 10000
417 417
418 418
419 419 ; *********************************************
420 420 ; `cache_general` cache for general purpose use
421 421 ; for simplicity use rc.file_namespace backend,
422 422 ; for performance and scale use rc.redis
423 423 ; *********************************************
424 424 rc_cache.cache_general.backend = dogpile.cache.rc.file_namespace
425 425 rc_cache.cache_general.expiration_time = 43200
426 426 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
427 427 #rc_cache.cache_general.arguments.filename = /tmp/cache_general_db
428 428
429 429 ; alternative `cache_general` redis backend with distributed lock
430 430 #rc_cache.cache_general.backend = dogpile.cache.rc.redis
431 431 #rc_cache.cache_general.expiration_time = 300
432 432
433 433 ; redis_expiration_time needs to be greater then expiration_time
434 434 #rc_cache.cache_general.arguments.redis_expiration_time = 7200
435 435
436 436 #rc_cache.cache_general.arguments.host = localhost
437 437 #rc_cache.cache_general.arguments.port = 6379
438 438 #rc_cache.cache_general.arguments.db = 0
439 439 #rc_cache.cache_general.arguments.socket_timeout = 30
440 440 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
441 441 #rc_cache.cache_general.arguments.distributed_lock = true
442 442
443 443 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
444 444 #rc_cache.cache_general.arguments.lock_auto_renewal = true
445 445
446 446 ; *************************************************
447 447 ; `cache_perms` cache for permission tree, auth TTL
448 448 ; for simplicity use rc.file_namespace backend,
449 449 ; for performance and scale use rc.redis
450 450 ; *************************************************
451 451 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
452 452 rc_cache.cache_perms.expiration_time = 3600
453 453 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
454 454 #rc_cache.cache_perms.arguments.filename = /tmp/cache_perms_db
455 455
456 456 ; alternative `cache_perms` redis backend with distributed lock
457 457 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
458 458 #rc_cache.cache_perms.expiration_time = 300
459 459
460 460 ; redis_expiration_time needs to be greater then expiration_time
461 461 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
462 462
463 463 #rc_cache.cache_perms.arguments.host = localhost
464 464 #rc_cache.cache_perms.arguments.port = 6379
465 465 #rc_cache.cache_perms.arguments.db = 0
466 466 #rc_cache.cache_perms.arguments.socket_timeout = 30
467 467 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
468 468 #rc_cache.cache_perms.arguments.distributed_lock = true
469 469
470 470 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
471 471 #rc_cache.cache_perms.arguments.lock_auto_renewal = true
472 472
473 473 ; ***************************************************
474 474 ; `cache_repo` cache for file tree, Readme, RSS FEEDS
475 475 ; for simplicity use rc.file_namespace backend,
476 476 ; for performance and scale use rc.redis
477 477 ; ***************************************************
478 478 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
479 479 rc_cache.cache_repo.expiration_time = 2592000
480 480 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
481 481 #rc_cache.cache_repo.arguments.filename = /tmp/cache_repo_db
482 482
483 483 ; alternative `cache_repo` redis backend with distributed lock
484 484 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
485 485 #rc_cache.cache_repo.expiration_time = 2592000
486 486
487 487 ; redis_expiration_time needs to be greater then expiration_time
488 488 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
489 489
490 490 #rc_cache.cache_repo.arguments.host = localhost
491 491 #rc_cache.cache_repo.arguments.port = 6379
492 492 #rc_cache.cache_repo.arguments.db = 1
493 493 #rc_cache.cache_repo.arguments.socket_timeout = 30
494 494 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
495 495 #rc_cache.cache_repo.arguments.distributed_lock = true
496 496
497 497 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
498 498 #rc_cache.cache_repo.arguments.lock_auto_renewal = true
499 499
500 500 ; ##############
501 501 ; BEAKER SESSION
502 502 ; ##############
503 503
504 504 ; beaker.session.type is type of storage options for the logged users sessions. Current allowed
505 505 ; types are file, ext:redis, ext:database, ext:memcached
506 506 ; Fastest ones are ext:redis and ext:database, DO NOT use memory type for session
507 507 #beaker.session.type = file
508 508 #beaker.session.data_dir = %(here)s/data/sessions
509 509
510 510 ; Redis based sessions
511 511 beaker.session.type = ext:redis
512 512 beaker.session.url = redis://redis:6379/2
513 513
514 514 ; DB based session, fast, and allows easy management over logged in users
515 515 #beaker.session.type = ext:database
516 516 #beaker.session.table_name = db_session
517 517 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
518 518 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
519 519 #beaker.session.sa.pool_recycle = 3600
520 520 #beaker.session.sa.echo = false
521 521
522 522 beaker.session.key = rhodecode
523 523 beaker.session.secret = production-rc-uytcxaz
524 524 beaker.session.lock_dir = /data_ramdisk/lock
525 525
526 526 ; Secure encrypted cookie. Requires AES and AES python libraries
527 527 ; you must disable beaker.session.secret to use this
528 528 #beaker.session.encrypt_key = key_for_encryption
529 529 #beaker.session.validate_key = validation_key
530 530
531 531 ; Sets session as invalid (also logging out user) if it haven not been
532 532 ; accessed for given amount of time in seconds
533 533 beaker.session.timeout = 2592000
534 534 beaker.session.httponly = true
535 535
536 536 ; Path to use for the cookie. Set to prefix if you use prefix middleware
537 537 #beaker.session.cookie_path = /custom_prefix
538 538
539 539 ; Set https secure cookie
540 540 beaker.session.secure = false
541 541
542 542 ; default cookie expiration time in seconds, set to `true` to set expire
543 543 ; at browser close
544 544 #beaker.session.cookie_expires = 3600
545 545
546 546 ; #############################
547 547 ; SEARCH INDEXING CONFIGURATION
548 548 ; #############################
549 549
550 550 ; Full text search indexer is available in rhodecode-tools under
551 551 ; `rhodecode-tools index` command
552 552
553 553 ; WHOOSH Backend, doesn't require additional services to run
554 554 ; it works good with few dozen repos
555 555 search.module = rhodecode.lib.index.whoosh
556 556 search.location = %(here)s/data/index
557 557
558 558 ; ####################
559 559 ; CHANNELSTREAM CONFIG
560 560 ; ####################
561 561
562 562 ; channelstream enables persistent connections and live notification
563 563 ; in the system. It's also used by the chat system
564 564
565 565 channelstream.enabled = true
566 566
567 567 ; server address for channelstream server on the backend
568 568 channelstream.server = channelstream:9800
569 569
570 570 ; location of the channelstream server from outside world
571 571 ; use ws:// for http or wss:// for https. This address needs to be handled
572 572 ; by external HTTP server such as Nginx or Apache
573 573 ; see Nginx/Apache configuration examples in our docs
574 574 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
575 575 channelstream.secret = ENV_GENERATED
576 576 channelstream.history.location = /var/opt/rhodecode_data/channelstream_history
577 577
578 578 ; Internal application path that Javascript uses to connect into.
579 579 ; If you use proxy-prefix the prefix should be added before /_channelstream
580 580 channelstream.proxy_path = /_channelstream
581 581
582 582
583 583 ; ##############################
584 584 ; MAIN RHODECODE DATABASE CONFIG
585 585 ; ##############################
586 586
587 587 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
588 588 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
589 589 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode?charset=utf8
590 590 ; pymysql is an alternative driver for MySQL, use in case of problems with default one
591 591 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
592 592
593 593 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
594 594
595 595 ; see sqlalchemy docs for other advanced settings
596 596 ; print the sql statements to output
597 597 sqlalchemy.db1.echo = false
598 598
599 599 ; recycle the connections after this amount of seconds
600 600 sqlalchemy.db1.pool_recycle = 3600
601 601
602 602 ; the number of connections to keep open inside the connection pool.
603 603 ; 0 indicates no limit
604 604 ; the general calculus with gevent is:
605 605 ; if your system allows 500 concurrent greenlets (max_connections) that all do database access,
606 606 ; then increase pool size + max overflow so that they add up to 500.
607 607 #sqlalchemy.db1.pool_size = 5
608 608
609 609 ; The number of connections to allow in connection pool "overflow", that is
610 610 ; connections that can be opened above and beyond the pool_size setting,
611 611 ; which defaults to five.
612 612 #sqlalchemy.db1.max_overflow = 10
613 613
614 614 ; Connection check ping, used to detect broken database connections
615 615 ; could be enabled to better handle cases if MySQL has gone away errors
616 616 #sqlalchemy.db1.ping_connection = true
617 617
618 618 ; ##########
619 619 ; VCS CONFIG
620 620 ; ##########
621 621 vcs.server.enable = true
622 622 vcs.server = vcsserver:10010
623 623
624 624 ; Web server connectivity protocol, responsible for web based VCS operations
625 625 ; Available protocols are:
626 626 ; `http` - use http-rpc backend (default)
627 627 vcs.server.protocol = http
628 628
629 629 ; Push/Pull operations protocol, available options are:
630 630 ; `http` - use http-rpc backend (default)
631 631 vcs.scm_app_implementation = http
632 632
633 633 ; Push/Pull operations hooks protocol, available options are:
634 634 ; `http` - use http-rpc backend (default)
635 635 ; `celery` - use celery based hooks
636 636 #DEPRECATED:vcs.hooks.protocol = http
637 637 vcs.hooks.protocol.v2 = celery
638 638
639 639 ; Host on which this instance is listening for hooks. vcsserver will call this host to pull/push hooks so it should be
640 640 ; accessible via network.
641 641 ; Use vcs.hooks.host = "*" to bind to current hostname (for Docker)
642 642 vcs.hooks.host = *
643 643
644 644 ; Start VCSServer with this instance as a subprocess, useful for development
645 645 vcs.start_server = false
646 646
647 647 ; List of enabled VCS backends, available options are:
648 648 ; `hg` - mercurial
649 649 ; `git` - git
650 650 ; `svn` - subversion
651 651 vcs.backends = hg, git, svn
652 652
653 653 ; Wait this number of seconds before killing connection to the vcsserver
654 654 vcs.connection_timeout = 3600
655 655
656 656 ; Cache flag to cache vcsserver remote calls locally
657 657 ; It uses cache_region `cache_repo`
658 658 vcs.methods.cache = true
659 659
660 ; Filesystem location where Git lfs objects should be stored
661 vcs.git.lfs.storage_location = /var/opt/rhodecode_repo_store/.cache/git_lfs_store
662
663 ; Filesystem location where Mercurial largefile objects should be stored
664 vcs.hg.largefiles.storage_location = /var/opt/rhodecode_repo_store/.cache/hg_largefiles_store
665
660 666 ; ####################################################
661 667 ; Subversion proxy support (mod_dav_svn)
662 668 ; Maps RhodeCode repo groups into SVN paths for Apache
663 669 ; ####################################################
664 670
665 671 ; Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
666 672 ; Set a numeric version for your current SVN e.g 1.8, or 1.12
667 673 ; Legacy available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
668 674 #vcs.svn.compatible_version = 1.8
669 675
670 676 ; Redis connection settings for svn integrations logic
671 677 ; This connection string needs to be the same on ce and vcsserver
672 678 vcs.svn.redis_conn = redis://redis:6379/0
673 679
674 680 ; Enable SVN proxy of requests over HTTP
675 681 vcs.svn.proxy.enabled = true
676 682
677 683 ; host to connect to running SVN subsystem
678 684 vcs.svn.proxy.host = http://svn:8090
679 685
680 686 ; Enable or disable the config file generation.
681 687 svn.proxy.generate_config = true
682 688
683 689 ; Generate config file with `SVNListParentPath` set to `On`.
684 690 svn.proxy.list_parent_path = true
685 691
686 692 ; Set location and file name of generated config file.
687 693 svn.proxy.config_file_path = /etc/rhodecode/conf/svn/mod_dav_svn.conf
688 694
689 695 ; alternative mod_dav config template. This needs to be a valid mako template
690 696 ; Example template can be found in the source code:
691 697 ; rhodecode/apps/svn_support/templates/mod-dav-svn.conf.mako
692 698 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
693 699
694 700 ; Used as a prefix to the `Location` block in the generated config file.
695 701 ; In most cases it should be set to `/`.
696 702 svn.proxy.location_root = /
697 703
698 704 ; Command to reload the mod dav svn configuration on change.
699 705 ; Example: `/etc/init.d/apache2 reload` or /home/USER/apache_reload.sh
700 706 ; Make sure user who runs RhodeCode process is allowed to reload Apache
701 707 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
702 708
703 709 ; If the timeout expires before the reload command finishes, the command will
704 710 ; be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
705 711 #svn.proxy.reload_timeout = 10
706 712
707 713 ; ####################
708 714 ; SSH Support Settings
709 715 ; ####################
710 716
711 717 ; Defines if a custom authorized_keys file should be created and written on
712 718 ; any change user ssh keys. Setting this to false also disables possibility
713 719 ; of adding SSH keys by users from web interface. Super admins can still
714 720 ; manage SSH Keys.
715 721 ssh.generate_authorized_keyfile = true
716 722
717 723 ; Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
718 724 # ssh.authorized_keys_ssh_opts =
719 725
720 726 ; Path to the authorized_keys file where the generate entries are placed.
721 727 ; It is possible to have multiple key files specified in `sshd_config` e.g.
722 728 ; AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
723 729 ssh.authorized_keys_file_path = /etc/rhodecode/conf/ssh/authorized_keys_rhodecode
724 730
725 731 ; Command to execute the SSH wrapper. The binary is available in the
726 732 ; RhodeCode installation directory.
727 733 ; legacy: /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper
728 734 ; new rewrite: /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper-v2
729 735 #DEPRECATED: ssh.wrapper_cmd = /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper
730 736 ssh.wrapper_cmd.v2 = /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper-v2
731 737
732 738 ; Allow shell when executing the ssh-wrapper command
733 739 ssh.wrapper_cmd_allow_shell = false
734 740
735 741 ; Enables logging, and detailed output send back to the client during SSH
736 742 ; operations. Useful for debugging, shouldn't be used in production.
737 743 ssh.enable_debug_logging = false
738 744
739 745 ; Paths to binary executable, by default they are the names, but we can
740 746 ; override them if we want to use a custom one
741 747 ssh.executable.hg = /usr/local/bin/rhodecode_bin/vcs_bin/hg
742 748 ssh.executable.git = /usr/local/bin/rhodecode_bin/vcs_bin/git
743 749 ssh.executable.svn = /usr/local/bin/rhodecode_bin/vcs_bin/svnserve
744 750
745 751 ; Enables SSH key generator web interface. Disabling this still allows users
746 752 ; to add their own keys.
747 753 ssh.enable_ui_key_generator = true
748 754
749 755 ; Statsd client config, this is used to send metrics to statsd
750 756 ; We recommend setting statsd_exported and scrape them using Prometheus
751 757 #statsd.enabled = false
752 758 #statsd.statsd_host = 0.0.0.0
753 759 #statsd.statsd_port = 8125
754 760 #statsd.statsd_prefix =
755 761 #statsd.statsd_ipv6 = false
756 762
757 763 ; configure logging automatically at server startup set to false
758 764 ; to use the below custom logging config.
759 765 ; RC_LOGGING_FORMATTER
760 766 ; RC_LOGGING_LEVEL
761 767 ; env variables can control the settings for logging in case of autoconfigure
762 768
763 769 #logging.autoconfigure = true
764 770
765 771 ; specify your own custom logging config file to configure logging
766 772 #logging.logging_conf_file = /path/to/custom_logging.ini
767 773
768 774 ; Dummy marker to add new entries after.
769 775 ; Add any custom entries below. Please don't remove this marker.
770 776 custom.conf = 1
771 777
772 778
773 779 ; #####################
774 780 ; LOGGING CONFIGURATION
775 781 ; #####################
776 782
777 783 [loggers]
778 784 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
779 785
780 786 [handlers]
781 787 keys = console, console_sql
782 788
783 789 [formatters]
784 790 keys = generic, json, color_formatter, color_formatter_sql
785 791
786 792 ; #######
787 793 ; LOGGERS
788 794 ; #######
789 795 [logger_root]
790 796 level = NOTSET
791 797 handlers = console
792 798
793 799 [logger_sqlalchemy]
794 800 level = INFO
795 801 handlers = console_sql
796 802 qualname = sqlalchemy.engine
797 803 propagate = 0
798 804
799 805 [logger_beaker]
800 806 level = DEBUG
801 807 handlers =
802 808 qualname = beaker.container
803 809 propagate = 1
804 810
805 811 [logger_rhodecode]
806 812 level = DEBUG
807 813 handlers =
808 814 qualname = rhodecode
809 815 propagate = 1
810 816
811 817 [logger_ssh_wrapper]
812 818 level = DEBUG
813 819 handlers =
814 820 qualname = ssh_wrapper
815 821 propagate = 1
816 822
817 823 [logger_celery]
818 824 level = DEBUG
819 825 handlers =
820 826 qualname = celery
821 827
822 828
823 829 ; ########
824 830 ; HANDLERS
825 831 ; ########
826 832
827 833 [handler_console]
828 834 class = StreamHandler
829 835 args = (sys.stderr, )
830 836 level = INFO
831 837 ; To enable JSON formatted logs replace 'generic/color_formatter' with 'json'
832 838 ; This allows sending properly formatted logs to grafana loki or elasticsearch
833 839 formatter = generic
834 840
835 841 [handler_console_sql]
836 842 ; "level = DEBUG" logs SQL queries and results.
837 843 ; "level = INFO" logs SQL queries.
838 844 ; "level = WARN" logs neither. (Recommended for production systems.)
839 845 class = StreamHandler
840 846 args = (sys.stderr, )
841 847 level = WARN
842 848 ; To enable JSON formatted logs replace 'generic/color_formatter_sql' with 'json'
843 849 ; This allows sending properly formatted logs to grafana loki or elasticsearch
844 850 formatter = generic
845 851
846 852 ; ##########
847 853 ; FORMATTERS
848 854 ; ##########
849 855
850 856 [formatter_generic]
851 857 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
852 858 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
853 859 datefmt = %Y-%m-%d %H:%M:%S
854 860
855 861 [formatter_color_formatter]
856 862 class = rhodecode.lib.logging_formatter.ColorFormatter
857 863 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
858 864 datefmt = %Y-%m-%d %H:%M:%S
859 865
860 866 [formatter_color_formatter_sql]
861 867 class = rhodecode.lib.logging_formatter.ColorFormatterSql
862 868 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
863 869 datefmt = %Y-%m-%d %H:%M:%S
864 870
865 871 [formatter_json]
866 872 format = %(timestamp)s %(levelname)s %(name)s %(message)s %(req_id)s
867 873 class = rhodecode.lib._vendor.jsonlogger.JsonFormatter
@@ -1,228 +1,231 b''
1 1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import os
20 20 import tempfile
21 21 import logging
22 22
23 23 from pyramid.settings import asbool
24 24
25 25 from rhodecode.config.settings_maker import SettingsMaker
26 26 from rhodecode.config import utils as config_utils
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 def sanitize_settings_and_apply_defaults(global_config, settings):
32 32 """
33 33 Applies settings defaults and does all type conversion.
34 34
35 35 We would move all settings parsing and preparation into this place, so that
36 36 we have only one place left which deals with this part. The remaining parts
37 37 of the application would start to rely fully on well-prepared settings.
38 38
39 39 This piece would later be split up per topic to avoid a big fat monster
40 40 function.
41 41 """
42 42 jn = os.path.join
43 43
44 44 global_settings_maker = SettingsMaker(global_config)
45 45 global_settings_maker.make_setting('debug', default=False, parser='bool')
46 46 debug_enabled = asbool(global_config.get('debug'))
47 47
48 48 settings_maker = SettingsMaker(settings)
49 49
50 50 settings_maker.make_setting(
51 51 'logging.autoconfigure',
52 52 default=False,
53 53 parser='bool')
54 54
55 55 logging_conf = jn(os.path.dirname(global_config.get('__file__')), 'logging.ini')
56 56 settings_maker.enable_logging(logging_conf, level='INFO' if debug_enabled else 'DEBUG')
57 57
58 58 # Default includes, possible to change as a user
59 59 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
60 60 log.debug(
61 61 "Using the following pyramid.includes: %s",
62 62 pyramid_includes)
63 63
64 64 settings_maker.make_setting('rhodecode.edition', 'Community Edition')
65 65 settings_maker.make_setting('rhodecode.edition_id', 'CE')
66 66
67 67 if 'mako.default_filters' not in settings:
68 68 # set custom default filters if we don't have it defined
69 69 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
70 70 settings['mako.default_filters'] = 'h_filter'
71 71
72 72 if 'mako.directories' not in settings:
73 73 mako_directories = settings.setdefault('mako.directories', [
74 74 # Base templates of the original application
75 75 'rhodecode:templates',
76 76 ])
77 77 log.debug(
78 78 "Using the following Mako template directories: %s",
79 79 mako_directories)
80 80
81 81 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
82 82 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
83 83 raw_url = settings['beaker.session.url']
84 84 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
85 85 settings['beaker.session.url'] = 'redis://' + raw_url
86 86
87 87 settings_maker.make_setting('__file__', global_config.get('__file__'))
88 88
89 89 # TODO: johbo: Re-think this, usually the call to config.include
90 90 # should allow to pass in a prefix.
91 91 settings_maker.make_setting('rhodecode.api.url', '/_admin/api')
92 92
93 93 # Sanitize generic settings.
94 94 settings_maker.make_setting('default_encoding', 'UTF-8', parser='list')
95 95 settings_maker.make_setting('gzip_responses', False, parser='bool')
96 96 settings_maker.make_setting('startup.import_repos', 'false', parser='bool')
97 97
98 98 # statsd
99 99 settings_maker.make_setting('statsd.enabled', False, parser='bool')
100 100 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
101 101 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
102 102 settings_maker.make_setting('statsd.statsd_prefix', '')
103 103 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
104 104
105 105 settings_maker.make_setting('vcs.svn.compatible_version', '')
106 106 settings_maker.make_setting('vcs.svn.redis_conn', 'redis://redis:6379/0')
107 107 settings_maker.make_setting('vcs.svn.proxy.enabled', True, parser='bool')
108 108 settings_maker.make_setting('vcs.svn.proxy.host', 'http://svn:8090', parser='string')
109 109 settings_maker.make_setting('vcs.hooks.protocol.v2', 'celery')
110 110 settings_maker.make_setting('vcs.hooks.host', '*')
111 111 settings_maker.make_setting('vcs.scm_app_implementation', 'http')
112 112 settings_maker.make_setting('vcs.server', '')
113 113 settings_maker.make_setting('vcs.server.protocol', 'http')
114 114 settings_maker.make_setting('vcs.server.enable', 'true', parser='bool')
115 115 settings_maker.make_setting('vcs.hooks.direct_calls', 'false', parser='bool')
116 116 settings_maker.make_setting('vcs.start_server', 'false', parser='bool')
117 117 settings_maker.make_setting('vcs.backends', 'hg, git, svn', parser='list')
118 118 settings_maker.make_setting('vcs.connection_timeout', 3600, parser='int')
119 settings_maker.make_setting('vcs.git.lfs.storage_location', '/var/opt/rhodecode_repo_store/.cache/git_lfs_store')
120 settings_maker.make_setting('vcs.hg.largefiles.storage_location',
121 '/var/opt/rhodecode_repo_store/.cache/hg_largefiles_store')
119 122
120 123 settings_maker.make_setting('vcs.methods.cache', True, parser='bool')
121 124
122 125 # repo_store path
123 126 settings_maker.make_setting('repo_store.path', '/var/opt/rhodecode_repo_store')
124 127 # Support legacy values of vcs.scm_app_implementation. Legacy
125 128 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
126 129 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
127 130 scm_app_impl = settings['vcs.scm_app_implementation']
128 131 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
129 132 settings['vcs.scm_app_implementation'] = 'http'
130 133
131 134 settings_maker.make_setting('appenlight', False, parser='bool')
132 135
133 136 temp_store = tempfile.gettempdir()
134 137 tmp_cache_dir = jn(temp_store, 'rc_cache')
135 138
136 139 # save default, cache dir, and use it for all backends later.
137 140 default_cache_dir = settings_maker.make_setting(
138 141 'cache_dir',
139 142 default=tmp_cache_dir, default_when_empty=True,
140 143 parser='dir:ensured')
141 144
142 145 # exception store cache
143 146 settings_maker.make_setting(
144 147 'exception_tracker.store_path',
145 148 default=jn(default_cache_dir, 'exc_store'), default_when_empty=True,
146 149 parser='dir:ensured'
147 150 )
148 151
149 152 settings_maker.make_setting(
150 153 'celerybeat-schedule.path',
151 154 default=jn(default_cache_dir, 'celerybeat_schedule', 'celerybeat-schedule.db'), default_when_empty=True,
152 155 parser='file:ensured'
153 156 )
154 157
155 158 # celery
156 159 broker_url = settings_maker.make_setting('celery.broker_url', 'redis://redis:6379/8')
157 160 settings_maker.make_setting('celery.result_backend', broker_url)
158 161
159 162 settings_maker.make_setting('exception_tracker.send_email', False, parser='bool')
160 163 settings_maker.make_setting('exception_tracker.email_prefix', '[RHODECODE ERROR]', default_when_empty=True)
161 164
162 165 # sessions, ensure file since no-value is memory
163 166 settings_maker.make_setting('beaker.session.type', 'file')
164 167 settings_maker.make_setting('beaker.session.data_dir', jn(default_cache_dir, 'session_data'))
165 168
166 169 # cache_general
167 170 settings_maker.make_setting('rc_cache.cache_general.backend', 'dogpile.cache.rc.file_namespace')
168 171 settings_maker.make_setting('rc_cache.cache_general.expiration_time', 60 * 60 * 12, parser='int')
169 172 settings_maker.make_setting('rc_cache.cache_general.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_general.db'))
170 173
171 174 # cache_perms
172 175 settings_maker.make_setting('rc_cache.cache_perms.backend', 'dogpile.cache.rc.file_namespace')
173 176 settings_maker.make_setting('rc_cache.cache_perms.expiration_time', 60 * 60, parser='int')
174 177 settings_maker.make_setting('rc_cache.cache_perms.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_perms_db'))
175 178
176 179 # cache_repo
177 180 settings_maker.make_setting('rc_cache.cache_repo.backend', 'dogpile.cache.rc.file_namespace')
178 181 settings_maker.make_setting('rc_cache.cache_repo.expiration_time', 60 * 60 * 24 * 30, parser='int')
179 182 settings_maker.make_setting('rc_cache.cache_repo.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_repo_db'))
180 183
181 184 # cache_license
182 185 settings_maker.make_setting('rc_cache.cache_license.backend', 'dogpile.cache.rc.file_namespace')
183 186 settings_maker.make_setting('rc_cache.cache_license.expiration_time', 60 * 5, parser='int')
184 187 settings_maker.make_setting('rc_cache.cache_license.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_license_db'))
185 188
186 189 # cache_repo_longterm memory, 96H
187 190 settings_maker.make_setting('rc_cache.cache_repo_longterm.backend', 'dogpile.cache.rc.memory_lru')
188 191 settings_maker.make_setting('rc_cache.cache_repo_longterm.expiration_time', 345600, parser='int')
189 192 settings_maker.make_setting('rc_cache.cache_repo_longterm.max_size', 10000, parser='int')
190 193
191 194 # sql_cache_short
192 195 settings_maker.make_setting('rc_cache.sql_cache_short.backend', 'dogpile.cache.rc.memory_lru')
193 196 settings_maker.make_setting('rc_cache.sql_cache_short.expiration_time', 30, parser='int')
194 197 settings_maker.make_setting('rc_cache.sql_cache_short.max_size', 10000, parser='int')
195 198
196 199 # archive_cache
197 200 settings_maker.make_setting('archive_cache.locking.url', 'redis://redis:6379/1')
198 201 settings_maker.make_setting('archive_cache.backend.type', 'filesystem')
199 202
200 203 settings_maker.make_setting('archive_cache.filesystem.store_dir', jn(default_cache_dir, 'archive_cache'), default_when_empty=True,)
201 204 settings_maker.make_setting('archive_cache.filesystem.cache_shards', 8, parser='int')
202 205 settings_maker.make_setting('archive_cache.filesystem.cache_size_gb', 10, parser='float')
203 206 settings_maker.make_setting('archive_cache.filesystem.eviction_policy', 'least-recently-stored')
204 207
205 208 settings_maker.make_setting('archive_cache.filesystem.retry', False, parser='bool')
206 209 settings_maker.make_setting('archive_cache.filesystem.retry_backoff', 1, parser='int')
207 210 settings_maker.make_setting('archive_cache.filesystem.retry_attempts', 10, parser='int')
208 211
209 212 settings_maker.make_setting('archive_cache.objectstore.url', 'http://s3-minio:9000', default_when_empty=True,)
210 213 settings_maker.make_setting('archive_cache.objectstore.key', '')
211 214 settings_maker.make_setting('archive_cache.objectstore.secret', '')
212 215 settings_maker.make_setting('archive_cache.objectstore.region', 'eu-central-1')
213 216 settings_maker.make_setting('archive_cache.objectstore.bucket', 'rhodecode-archive-cache', default_when_empty=True,)
214 217 settings_maker.make_setting('archive_cache.objectstore.bucket_shards', 8, parser='int')
215 218
216 219 settings_maker.make_setting('archive_cache.objectstore.cache_size_gb', 10, parser='float')
217 220 settings_maker.make_setting('archive_cache.objectstore.eviction_policy', 'least-recently-stored')
218 221
219 222 settings_maker.make_setting('archive_cache.objectstore.retry', False, parser='bool')
220 223 settings_maker.make_setting('archive_cache.objectstore.retry_backoff', 1, parser='int')
221 224 settings_maker.make_setting('archive_cache.objectstore.retry_attempts', 10, parser='int')
222 225
223 226 settings_maker.env_expand()
224 227
225 228 # configure instance id
226 229 config_utils.set_instance_id(settings)
227 230
228 231 return settings
@@ -1,827 +1,835 b''
1 1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 """
20 20 Utilities library for RhodeCode
21 21 """
22 22
23 23 import datetime
24 24
25 25 import decorator
26 26 import logging
27 27 import os
28 28 import re
29 29 import sys
30 30 import shutil
31 31 import socket
32 32 import tempfile
33 33 import traceback
34 34 import tarfile
35 35
36 36 from functools import wraps
37 37 from os.path import join as jn
38 38
39 39 import paste
40 40 import pkg_resources
41 41 from webhelpers2.text import collapse, strip_tags, convert_accented_entities, convert_misc_entities
42 42
43 43 from mako import exceptions
44 44
45 from rhodecode import ConfigGet
45 46 from rhodecode.lib.hash_utils import sha256_safe, md5, sha1
46 47 from rhodecode.lib.type_utils import AttributeDict
47 48 from rhodecode.lib.str_utils import safe_bytes, safe_str
48 49 from rhodecode.lib.vcs.backends.base import Config
49 50 from rhodecode.lib.vcs.exceptions import VCSError
50 51 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
51 52 from rhodecode.lib.ext_json import sjson as json
52 53 from rhodecode.model import meta
53 54 from rhodecode.model.db import (
54 55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 56 from rhodecode.model.meta import Session
56 57
57 58
58 59 log = logging.getLogger(__name__)
59 60
60 61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 62
62 63 # String which contains characters that are not allowed in slug names for
63 64 # repositories or repository groups. It is properly escaped to use it in
64 65 # regular expressions.
65 66 SLUG_BAD_CHARS = re.escape(r'`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66 67
67 68 # Regex that matches forbidden characters in repo/group slugs.
68 69 SLUG_BAD_CHAR_RE = re.compile(r'[{}\x00-\x08\x0b-\x0c\x0e-\x1f]'.format(SLUG_BAD_CHARS))
69 70
70 71 # Regex that matches allowed characters in repo/group slugs.
71 72 SLUG_GOOD_CHAR_RE = re.compile(r'[^{}]'.format(SLUG_BAD_CHARS))
72 73
73 74 # Regex that matches whole repo/group slugs.
74 75 SLUG_RE = re.compile(r'[^{}]+'.format(SLUG_BAD_CHARS))
75 76
76 77 _license_cache = None
77 78
78 79
79 80 def adopt_for_celery(func):
80 81 """
81 82 Decorator designed to adopt hooks (from rhodecode.lib.hooks_base)
82 83 for further usage as a celery tasks.
83 84 """
84 85 @wraps(func)
85 86 def wrapper(extras):
86 87 extras = AttributeDict(extras)
87 88 try:
88 89 # HooksResponse implements to_json method which must be used there.
89 90 return func(extras).to_json()
90 91 except Exception as e:
91 92 return {'status': 128, 'exception': type(e).__name__, 'exception_args': e.args}
92 93 return wrapper
93 94
94 95
95 96 def repo_name_slug(value):
96 97 """
97 98 Return slug of name of repository
98 99 This function is called on each creation/modification
99 100 of repository to prevent bad names in repo
100 101 """
101 102
102 103 replacement_char = '-'
103 104
104 105 slug = strip_tags(value)
105 106 slug = convert_accented_entities(slug)
106 107 slug = convert_misc_entities(slug)
107 108
108 109 slug = SLUG_BAD_CHAR_RE.sub('', slug)
109 110 slug = re.sub(r'[\s]+', '-', slug)
110 111 slug = collapse(slug, replacement_char)
111 112
112 113 return slug
113 114
114 115
115 116 #==============================================================================
116 117 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
117 118 #==============================================================================
118 119 def get_repo_slug(request):
119 120 _repo = ''
120 121
121 122 if hasattr(request, 'db_repo_name'):
122 123 # if our requests has set db reference use it for name, this
123 124 # translates the example.com/_<id> into proper repo names
124 125 _repo = request.db_repo_name
125 126 elif getattr(request, 'matchdict', None):
126 127 # pyramid
127 128 _repo = request.matchdict.get('repo_name')
128 129
129 130 if _repo:
130 131 _repo = _repo.rstrip('/')
131 132 return _repo
132 133
133 134
134 135 def get_repo_group_slug(request):
135 136 _group = ''
136 137 if hasattr(request, 'db_repo_group'):
137 138 # if our requests has set db reference use it for name, this
138 139 # translates the example.com/_<id> into proper repo group names
139 140 _group = request.db_repo_group.group_name
140 141 elif getattr(request, 'matchdict', None):
141 142 # pyramid
142 143 _group = request.matchdict.get('repo_group_name')
143 144
144 145 if _group:
145 146 _group = _group.rstrip('/')
146 147 return _group
147 148
148 149
149 150 def get_user_group_slug(request):
150 151 _user_group = ''
151 152
152 153 if hasattr(request, 'db_user_group'):
153 154 _user_group = request.db_user_group.users_group_name
154 155 elif getattr(request, 'matchdict', None):
155 156 # pyramid
156 157 _user_group = request.matchdict.get('user_group_id')
157 158 _user_group_name = request.matchdict.get('user_group_name')
158 159 try:
159 160 if _user_group:
160 161 _user_group = UserGroup.get(_user_group)
161 162 elif _user_group_name:
162 163 _user_group = UserGroup.get_by_group_name(_user_group_name)
163 164
164 165 if _user_group:
165 166 _user_group = _user_group.users_group_name
166 167 except Exception:
167 168 log.exception('Failed to get user group by id and name')
168 169 # catch all failures here
169 170 return None
170 171
171 172 return _user_group
172 173
173 174
174 175 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
175 176 """
176 177 Scans given path for repos and return (name,(type,path)) tuple
177 178
178 179 :param path: path to scan for repositories
179 180 :param recursive: recursive search and return names with subdirs in front
180 181 """
181 182
182 183 # remove ending slash for better results
183 184 path = path.rstrip(os.sep)
184 185 log.debug('now scanning in %s location recursive:%s...', path, recursive)
185 186
186 187 def _get_repos(p):
187 188 dirpaths = get_dirpaths(p)
188 189 if not _is_dir_writable(p):
189 190 log.warning('repo path without write access: %s', p)
190 191
191 192 for dirpath in dirpaths:
192 193 if os.path.isfile(os.path.join(p, dirpath)):
193 194 continue
194 195 cur_path = os.path.join(p, dirpath)
195 196
196 197 # skip removed repos
197 198 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
198 199 continue
199 200
200 201 #skip .<somethin> dirs
201 202 if dirpath.startswith('.'):
202 203 continue
203 204
204 205 try:
205 206 scm_info = get_scm(cur_path)
206 207 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
207 208 except VCSError:
208 209 if not recursive:
209 210 continue
210 211 #check if this dir containts other repos for recursive scan
211 212 rec_path = os.path.join(p, dirpath)
212 213 if os.path.isdir(rec_path):
213 214 yield from _get_repos(rec_path)
214 215
215 216 return _get_repos(path)
216 217
217 218
218 219 def get_dirpaths(p: str) -> list:
219 220 try:
220 221 # OS-independable way of checking if we have at least read-only
221 222 # access or not.
222 223 dirpaths = os.listdir(p)
223 224 except OSError:
224 225 log.warning('ignoring repo path without read access: %s', p)
225 226 return []
226 227
227 228 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
228 229 # decode paths and suddenly returns unicode objects itself. The items it
229 230 # cannot decode are returned as strings and cause issues.
230 231 #
231 232 # Those paths are ignored here until a solid solution for path handling has
232 233 # been built.
233 234 expected_type = type(p)
234 235
235 236 def _has_correct_type(item):
236 237 if type(item) is not expected_type:
237 238 log.error(
238 239 "Ignoring path %s since it cannot be decoded into str.",
239 240 # Using "repr" to make sure that we see the byte value in case
240 241 # of support.
241 242 repr(item))
242 243 return False
243 244 return True
244 245
245 246 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
246 247
247 248 return dirpaths
248 249
249 250
250 251 def _is_dir_writable(path):
251 252 """
252 253 Probe if `path` is writable.
253 254
254 255 Due to trouble on Cygwin / Windows, this is actually probing if it is
255 256 possible to create a file inside of `path`, stat does not produce reliable
256 257 results in this case.
257 258 """
258 259 try:
259 260 with tempfile.TemporaryFile(dir=path):
260 261 pass
261 262 except OSError:
262 263 return False
263 264 return True
264 265
265 266
266 267 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None, config=None):
267 268 """
268 269 Returns True if given path is a valid repository False otherwise.
269 270 If expect_scm param is given also, compare if given scm is the same
270 271 as expected from scm parameter. If explicit_scm is given don't try to
271 272 detect the scm, just use the given one to check if repo is valid
272 273
273 274 :param repo_name:
274 275 :param base_path:
275 276 :param expect_scm:
276 277 :param explicit_scm:
277 278 :param config:
278 279
279 280 :return True: if given path is a valid repository
280 281 """
281 282 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
282 283 log.debug('Checking if `%s` is a valid path for repository. '
283 284 'Explicit type: %s', repo_name, explicit_scm)
284 285
285 286 try:
286 287 if explicit_scm:
287 288 detected_scms = [get_scm_backend(explicit_scm)(
288 289 full_path, config=config).alias]
289 290 else:
290 291 detected_scms = get_scm(full_path)
291 292
292 293 if expect_scm:
293 294 return detected_scms[0] == expect_scm
294 295 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
295 296 return True
296 297 except VCSError:
297 298 log.debug('path: %s is not a valid repo !', full_path)
298 299 return False
299 300
300 301
301 302 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
302 303 """
303 304 Returns True if a given path is a repository group, False otherwise
304 305
305 306 :param repo_group_name:
306 307 :param base_path:
307 308 """
308 309 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
309 310 log.debug('Checking if `%s` is a valid path for repository group',
310 311 repo_group_name)
311 312
312 313 # check if it's not a repo
313 314 if is_valid_repo(repo_group_name, base_path):
314 315 log.debug('Repo called %s exist, it is not a valid repo group', repo_group_name)
315 316 return False
316 317
317 318 try:
318 319 # we need to check bare git repos at higher level
319 320 # since we might match branches/hooks/info/objects or possible
320 321 # other things inside bare git repo
321 322 maybe_repo = os.path.dirname(full_path)
322 323 if maybe_repo == base_path:
323 324 # skip root level repo check; we know root location CANNOT BE a repo group
324 325 return False
325 326
326 327 scm_ = get_scm(maybe_repo)
327 328 log.debug('path: %s is a vcs object:%s, not valid repo group', full_path, scm_)
328 329 return False
329 330 except VCSError:
330 331 pass
331 332
332 333 # check if it's a valid path
333 334 if skip_path_check or os.path.isdir(full_path):
334 335 log.debug('path: %s is a valid repo group !', full_path)
335 336 return True
336 337
337 338 log.debug('path: %s is not a valid repo group !', full_path)
338 339 return False
339 340
340 341
341 342 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
342 343 while True:
343 344 ok = input(prompt)
344 345 if ok.lower() in ('y', 'ye', 'yes'):
345 346 return True
346 347 if ok.lower() in ('n', 'no', 'nop', 'nope'):
347 348 return False
348 349 retries = retries - 1
349 350 if retries < 0:
350 351 raise OSError
351 352 print(complaint)
352 353
353 354 # propagated from mercurial documentation
354 355 ui_sections = [
355 356 'alias', 'auth',
356 357 'decode/encode', 'defaults',
357 358 'diff', 'email',
358 359 'extensions', 'format',
359 360 'merge-patterns', 'merge-tools',
360 361 'hooks', 'http_proxy',
361 362 'smtp', 'patch',
362 363 'paths', 'profiling',
363 364 'server', 'trusted',
364 365 'ui', 'web', ]
365 366
366 367
367 def config_data_from_db(clear_session=True, repo=None):
368 def prepare_config_data(clear_session=True, repo=None):
368 369 """
369 Read the configuration data from the database and return configuration
370 Read the configuration data from the database, *.ini files and return configuration
370 371 tuples.
371 372 """
372 373 from rhodecode.model.settings import VcsSettingsModel
373 374
374 375 config = []
375 376
376 377 sa = meta.Session()
377 378 settings_model = VcsSettingsModel(repo=repo, sa=sa)
378 379
379 380 ui_settings = settings_model.get_ui_settings()
380 381
381 382 ui_data = []
382 383 for setting in ui_settings:
384 # Todo: remove this section once transition to *.ini files will be completed
385 if setting.section in ('largefiles', 'vcs_git_lfs'):
386 if setting.key != 'enabled':
387 continue
383 388 if setting.active:
384 389 ui_data.append((setting.section, setting.key, setting.value))
385 390 config.append((
386 391 safe_str(setting.section), safe_str(setting.key),
387 392 safe_str(setting.value)))
388 393 if setting.key == 'push_ssl':
389 394 # force set push_ssl requirement to False, rhodecode
390 395 # handles that
391 396 config.append((
392 397 safe_str(setting.section), safe_str(setting.key), False))
398 config_getter = ConfigGet()
399 config.append(('vcs_git_lfs', 'store_location', config_getter.get_str('vcs.git.lfs.storage_location')))
400 config.append(('largefiles', 'usercache', config_getter.get_str('vcs.hg.largefiles.storage_location')))
393 401 log.debug(
394 402 'settings ui from db@repo[%s]: %s',
395 403 repo,
396 404 ','.join(['[{}] {}={}'.format(*s) for s in ui_data]))
397 405 if clear_session:
398 406 meta.Session.remove()
399 407
400 408 # TODO: mikhail: probably it makes no sense to re-read hooks information.
401 409 # It's already there and activated/deactivated
402 410 skip_entries = []
403 411 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
404 412 if 'pull' not in enabled_hook_classes:
405 413 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
406 414 if 'push' not in enabled_hook_classes:
407 415 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
408 416 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
409 417 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
410 418
411 419 config = [entry for entry in config if entry[:2] not in skip_entries]
412 420
413 421 return config
414 422
415 423
416 424 def make_db_config(clear_session=True, repo=None):
417 425 """
418 426 Create a :class:`Config` instance based on the values in the database.
419 427 """
420 428 config = Config()
421 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
429 config_data = prepare_config_data(clear_session=clear_session, repo=repo)
422 430 for section, option, value in config_data:
423 431 config.set(section, option, value)
424 432 return config
425 433
426 434
427 435 def get_enabled_hook_classes(ui_settings):
428 436 """
429 437 Return the enabled hook classes.
430 438
431 439 :param ui_settings: List of ui_settings as returned
432 440 by :meth:`VcsSettingsModel.get_ui_settings`
433 441
434 442 :return: a list with the enabled hook classes. The order is not guaranteed.
435 443 :rtype: list
436 444 """
437 445 enabled_hooks = []
438 446 active_hook_keys = [
439 447 key for section, key, value, active in ui_settings
440 448 if section == 'hooks' and active]
441 449
442 450 hook_names = {
443 451 RhodeCodeUi.HOOK_PUSH: 'push',
444 452 RhodeCodeUi.HOOK_PULL: 'pull',
445 453 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
446 454 }
447 455
448 456 for key in active_hook_keys:
449 457 hook = hook_names.get(key)
450 458 if hook:
451 459 enabled_hooks.append(hook)
452 460
453 461 return enabled_hooks
454 462
455 463
456 464 def set_rhodecode_config(config):
457 465 """
458 466 Updates pyramid config with new settings from database
459 467
460 468 :param config:
461 469 """
462 470 from rhodecode.model.settings import SettingsModel
463 471 app_settings = SettingsModel().get_all_settings()
464 472
465 473 for k, v in list(app_settings.items()):
466 474 config[k] = v
467 475
468 476
469 477 def get_rhodecode_realm():
470 478 """
471 479 Return the rhodecode realm from database.
472 480 """
473 481 from rhodecode.model.settings import SettingsModel
474 482 realm = SettingsModel().get_setting_by_name('realm')
475 483 return safe_str(realm.app_settings_value)
476 484
477 485
478 486 def get_rhodecode_repo_store_path():
479 487 """
480 488 Returns the base path. The base path is the filesystem path which points
481 489 to the repository store.
482 490 """
483 491
484 492 import rhodecode
485 493 return rhodecode.CONFIG['repo_store.path']
486 494
487 495
488 496 def map_groups(path):
489 497 """
490 498 Given a full path to a repository, create all nested groups that this
491 499 repo is inside. This function creates parent-child relationships between
492 500 groups and creates default perms for all new groups.
493 501
494 502 :param paths: full path to repository
495 503 """
496 504 from rhodecode.model.repo_group import RepoGroupModel
497 505 sa = meta.Session()
498 506 groups = path.split(Repository.NAME_SEP)
499 507 parent = None
500 508 group = None
501 509
502 510 # last element is repo in nested groups structure
503 511 groups = groups[:-1]
504 512 rgm = RepoGroupModel(sa)
505 513 owner = User.get_first_super_admin()
506 514 for lvl, group_name in enumerate(groups):
507 515 group_name = '/'.join(groups[:lvl] + [group_name])
508 516 group = RepoGroup.get_by_group_name(group_name)
509 517 desc = '%s group' % group_name
510 518
511 519 # skip folders that are now removed repos
512 520 if REMOVED_REPO_PAT.match(group_name):
513 521 break
514 522
515 523 if group is None:
516 524 log.debug('creating group level: %s group_name: %s',
517 525 lvl, group_name)
518 526 group = RepoGroup(group_name, parent)
519 527 group.group_description = desc
520 528 group.user = owner
521 529 sa.add(group)
522 530 perm_obj = rgm._create_default_perms(group)
523 531 sa.add(perm_obj)
524 532 sa.flush()
525 533
526 534 parent = group
527 535 return group
528 536
529 537
530 538 def repo2db_mapper(initial_repo_list, remove_obsolete=False, force_hooks_rebuild=False):
531 539 """
532 540 maps all repos given in initial_repo_list, non existing repositories
533 541 are created, if remove_obsolete is True it also checks for db entries
534 542 that are not in initial_repo_list and removes them.
535 543
536 544 :param initial_repo_list: list of repositories found by scanning methods
537 545 :param remove_obsolete: check for obsolete entries in database
538 546 """
539 547 from rhodecode.model.repo import RepoModel
540 548 from rhodecode.model.repo_group import RepoGroupModel
541 549 from rhodecode.model.settings import SettingsModel
542 550
543 551 sa = meta.Session()
544 552 repo_model = RepoModel()
545 553 user = User.get_first_super_admin()
546 554 added = []
547 555
548 556 # creation defaults
549 557 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
550 558 enable_statistics = defs.get('repo_enable_statistics')
551 559 enable_locking = defs.get('repo_enable_locking')
552 560 enable_downloads = defs.get('repo_enable_downloads')
553 561 private = defs.get('repo_private')
554 562
555 563 for name, repo in list(initial_repo_list.items()):
556 564 group = map_groups(name)
557 565 str_name = safe_str(name)
558 566 db_repo = repo_model.get_by_repo_name(str_name)
559 567
560 568 # found repo that is on filesystem not in RhodeCode database
561 569 if not db_repo:
562 570 log.info('repository `%s` not found in the database, creating now', name)
563 571 added.append(name)
564 572 desc = (repo.description
565 573 if repo.description != 'unknown'
566 574 else '%s repository' % name)
567 575
568 576 db_repo = repo_model._create_repo(
569 577 repo_name=name,
570 578 repo_type=repo.alias,
571 579 description=desc,
572 580 repo_group=getattr(group, 'group_id', None),
573 581 owner=user,
574 582 enable_locking=enable_locking,
575 583 enable_downloads=enable_downloads,
576 584 enable_statistics=enable_statistics,
577 585 private=private,
578 586 state=Repository.STATE_CREATED
579 587 )
580 588 sa.commit()
581 589 # we added that repo just now, and make sure we updated server info
582 590 if db_repo.repo_type == 'git':
583 591 git_repo = db_repo.scm_instance()
584 592 # update repository server-info
585 593 log.debug('Running update server info')
586 594 git_repo._update_server_info(force=True)
587 595
588 596 db_repo.update_commit_cache(recursive=False)
589 597
590 598 config = db_repo._config
591 599 config.set('extensions', 'largefiles', '')
592 600 repo = db_repo.scm_instance(config=config)
593 601 repo.install_hooks(force=force_hooks_rebuild)
594 602
595 603 removed = []
596 604 if remove_obsolete:
597 605 # remove from database those repositories that are not in the filesystem
598 606 for repo in sa.query(Repository).all():
599 607 if repo.repo_name not in list(initial_repo_list.keys()):
600 608 log.debug("Removing non-existing repository found in db `%s`",
601 609 repo.repo_name)
602 610 try:
603 611 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
604 612 sa.commit()
605 613 removed.append(repo.repo_name)
606 614 except Exception:
607 615 # don't hold further removals on error
608 616 log.error(traceback.format_exc())
609 617 sa.rollback()
610 618
611 619 def splitter(full_repo_name):
612 620 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
613 621 gr_name = None
614 622 if len(_parts) == 2:
615 623 gr_name = _parts[0]
616 624 return gr_name
617 625
618 626 initial_repo_group_list = [splitter(x) for x in
619 627 list(initial_repo_list.keys()) if splitter(x)]
620 628
621 629 # remove from database those repository groups that are not in the
622 630 # filesystem due to parent child relationships we need to delete them
623 631 # in a specific order of most nested first
624 632 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
625 633 def nested_sort(gr):
626 634 return len(gr.split('/'))
627 635 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
628 636 if group_name not in initial_repo_group_list:
629 637 repo_group = RepoGroup.get_by_group_name(group_name)
630 638 if (repo_group.children.all() or
631 639 not RepoGroupModel().check_exist_filesystem(
632 640 group_name=group_name, exc_on_failure=False)):
633 641 continue
634 642
635 643 log.info(
636 644 'Removing non-existing repository group found in db `%s`',
637 645 group_name)
638 646 try:
639 647 RepoGroupModel(sa).delete(group_name, fs_remove=False)
640 648 sa.commit()
641 649 removed.append(group_name)
642 650 except Exception:
643 651 # don't hold further removals on error
644 652 log.exception(
645 653 'Unable to remove repository group `%s`',
646 654 group_name)
647 655 sa.rollback()
648 656 raise
649 657
650 658 return added, removed
651 659
652 660
653 661 def load_rcextensions(root_path):
654 662 import rhodecode
655 663 from rhodecode.config import conf
656 664
657 665 path = os.path.join(root_path)
658 666 sys.path.append(path)
659 667
660 668 try:
661 669 rcextensions = __import__('rcextensions')
662 670 except ImportError:
663 671 if os.path.isdir(os.path.join(path, 'rcextensions')):
664 672 log.warning('Unable to load rcextensions from %s', path)
665 673 rcextensions = None
666 674
667 675 if rcextensions:
668 676 log.info('Loaded rcextensions from %s...', rcextensions)
669 677 rhodecode.EXTENSIONS = rcextensions
670 678
671 679 # Additional mappings that are not present in the pygments lexers
672 680 conf.LANGUAGES_EXTENSIONS_MAP.update(
673 681 getattr(rhodecode.EXTENSIONS, 'EXTRA_MAPPINGS', {}))
674 682
675 683
676 684 def get_custom_lexer(extension):
677 685 """
678 686 returns a custom lexer if it is defined in rcextensions module, or None
679 687 if there's no custom lexer defined
680 688 """
681 689 import rhodecode
682 690 from pygments import lexers
683 691
684 692 # custom override made by RhodeCode
685 693 if extension in ['mako']:
686 694 return lexers.get_lexer_by_name('html+mako')
687 695
688 696 # check if we didn't define this extension as other lexer
689 697 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
690 698 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
691 699 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
692 700 return lexers.get_lexer_by_name(_lexer_name)
693 701
694 702
695 703 #==============================================================================
696 704 # TEST FUNCTIONS AND CREATORS
697 705 #==============================================================================
698 706 def create_test_index(repo_location, config):
699 707 """
700 708 Makes default test index.
701 709 """
702 710 try:
703 711 import rc_testdata
704 712 except ImportError:
705 713 raise ImportError('Failed to import rc_testdata, '
706 714 'please make sure this package is installed from requirements_test.txt')
707 715 rc_testdata.extract_search_index(
708 716 'vcs_search_index', os.path.dirname(config['search.location']))
709 717
710 718
711 719 def create_test_directory(test_path):
712 720 """
713 721 Create test directory if it doesn't exist.
714 722 """
715 723 if not os.path.isdir(test_path):
716 724 log.debug('Creating testdir %s', test_path)
717 725 os.makedirs(test_path)
718 726
719 727
720 728 def create_test_database(test_path, config):
721 729 """
722 730 Makes a fresh database.
723 731 """
724 732 from rhodecode.lib.db_manage import DbManage
725 733 from rhodecode.lib.utils2 import get_encryption_key
726 734
727 735 # PART ONE create db
728 736 dbconf = config['sqlalchemy.db1.url']
729 737 enc_key = get_encryption_key(config)
730 738
731 739 log.debug('making test db %s', dbconf)
732 740
733 741 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
734 742 tests=True, cli_args={'force_ask': True}, enc_key=enc_key)
735 743 dbmanage.create_tables(override=True)
736 744 dbmanage.set_db_version()
737 745 # for tests dynamically set new root paths based on generated content
738 746 dbmanage.create_settings(dbmanage.config_prompt(test_path))
739 747 dbmanage.create_default_user()
740 748 dbmanage.create_test_admin_and_users()
741 749 dbmanage.create_permissions()
742 750 dbmanage.populate_default_permissions()
743 751 Session().commit()
744 752
745 753
746 754 def create_test_repositories(test_path, config):
747 755 """
748 756 Creates test repositories in the temporary directory. Repositories are
749 757 extracted from archives within the rc_testdata package.
750 758 """
751 759 import rc_testdata
752 760 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
753 761
754 762 log.debug('making test vcs repositories')
755 763
756 764 idx_path = config['search.location']
757 765 data_path = config['cache_dir']
758 766
759 767 # clean index and data
760 768 if idx_path and os.path.exists(idx_path):
761 769 log.debug('remove %s', idx_path)
762 770 shutil.rmtree(idx_path)
763 771
764 772 if data_path and os.path.exists(data_path):
765 773 log.debug('remove %s', data_path)
766 774 shutil.rmtree(data_path)
767 775
768 776 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
769 777 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
770 778
771 779 # Note: Subversion is in the process of being integrated with the system,
772 780 # until we have a properly packed version of the test svn repository, this
773 781 # tries to copy over the repo from a package "rc_testdata"
774 782 svn_repo_path = rc_testdata.get_svn_repo_archive()
775 783 with tarfile.open(svn_repo_path) as tar:
776 784 tar.extractall(jn(test_path, SVN_REPO))
777 785
778 786
779 787 def password_changed(auth_user, session):
780 788 # Never report password change in case of default user or anonymous user.
781 789 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
782 790 return False
783 791
784 792 password_hash = md5(safe_bytes(auth_user.password)) if auth_user.password else None
785 793 rhodecode_user = session.get('rhodecode_user', {})
786 794 session_password_hash = rhodecode_user.get('password', '')
787 795 return password_hash != session_password_hash
788 796
789 797
790 798 def read_opensource_licenses():
791 799 global _license_cache
792 800
793 801 if not _license_cache:
794 802 licenses = pkg_resources.resource_string(
795 803 'rhodecode', 'config/licenses.json')
796 804 _license_cache = json.loads(licenses)
797 805
798 806 return _license_cache
799 807
800 808
801 809 def generate_platform_uuid():
802 810 """
803 811 Generates platform UUID based on it's name
804 812 """
805 813 import platform
806 814
807 815 try:
808 816 uuid_list = [platform.platform()]
809 817 return sha256_safe(':'.join(uuid_list))
810 818 except Exception as e:
811 819 log.error('Failed to generate host uuid: %s', e)
812 820 return 'UNDEFINED'
813 821
814 822
815 823 def send_test_email(recipients, email_body='TEST EMAIL'):
816 824 """
817 825 Simple code for generating test emails.
818 826 Usage::
819 827
820 828 from rhodecode.lib import utils
821 829 utils.send_test_email()
822 830 """
823 831 from rhodecode.lib.celerylib import tasks, run_task
824 832
825 833 email_body = email_body_plaintext = email_body
826 834 subject = f'SUBJECT FROM: {socket.gethostname()}'
827 835 tasks.send_email(recipients, subject, email_body_plaintext, email_body)
@@ -1,669 +1,663 b''
1 1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 """
20 20 this is forms validation classes
21 21 http://formencode.org/module-formencode.validators.html
22 22 for list off all availible validators
23 23
24 24 we can create our own validators
25 25
26 26 The table below outlines the options which can be used in a schema in addition to the validators themselves
27 27 pre_validators [] These validators will be applied before the schema
28 28 chained_validators [] These validators will be applied after the schema
29 29 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
30 30 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
31 31 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
32 32 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
33 33
34 34
35 35 <name> = formencode.validators.<name of validator>
36 36 <name> must equal form name
37 37 list=[1,2,3,4,5]
38 38 for SELECT use formencode.All(OneOf(list), Int())
39 39
40 40 """
41 41
42 42 import deform
43 43 import logging
44 44 import formencode
45 45
46 46 from pkg_resources import resource_filename
47 47 from formencode import All, Pipe
48 48
49 49 from pyramid.threadlocal import get_current_request
50 50
51 51 from rhodecode import BACKENDS
52 52 from rhodecode.lib import helpers
53 53 from rhodecode.model import validators as v
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 deform_templates = resource_filename('deform', 'templates')
59 59 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
60 60 search_path = (rhodecode_templates, deform_templates)
61 61
62 62
63 63 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
64 64 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
65 65 def __call__(self, template_name, **kw):
66 66 kw['h'] = helpers
67 67 kw['request'] = get_current_request()
68 68 return self.load(template_name)(**kw)
69 69
70 70
71 71 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
72 72 deform.Form.set_default_renderer(form_renderer)
73 73
74 74
75 75 def LoginForm(localizer):
76 76 _ = localizer
77 77
78 78 class _LoginForm(formencode.Schema):
79 79 allow_extra_fields = True
80 80 filter_extra_fields = True
81 81 username = v.UnicodeString(
82 82 strip=True,
83 83 min=1,
84 84 not_empty=True,
85 85 messages={
86 86 'empty': _('Please enter a login'),
87 87 'tooShort': _('Enter a value %(min)i characters long or more')
88 88 }
89 89 )
90 90
91 91 password = v.UnicodeString(
92 92 strip=False,
93 93 min=3,
94 94 max=72,
95 95 not_empty=True,
96 96 messages={
97 97 'empty': _('Please enter a password'),
98 98 'tooShort': _('Enter %(min)i characters or more')}
99 99 )
100 100
101 101 remember = v.StringBoolean(if_missing=False)
102 102
103 103 chained_validators = [v.ValidAuth(localizer)]
104 104 return _LoginForm
105 105
106 106
107 107 def TOTPForm(localizer, user, allow_recovery_code_use=False):
108 108 _ = localizer
109 109
110 110 class _TOTPForm(formencode.Schema):
111 111 allow_extra_fields = True
112 112 filter_extra_fields = False
113 113 totp = v.Regex(r'^(?:\d{6}|[A-Z0-9]{32})$')
114 114 secret_totp = v.String()
115 115
116 116 def to_python(self, value, state=None):
117 117 validation_checks = [user.is_totp_valid]
118 118 if allow_recovery_code_use:
119 119 validation_checks.append(user.is_2fa_recovery_code_valid)
120 120 form_data = super().to_python(value, state)
121 121 received_code = form_data['totp']
122 122 secret = form_data.get('secret_totp')
123 123
124 124 if not any(map(lambda func: func(received_code, secret), validation_checks)):
125 125 error_msg = _('Code is invalid. Try again!')
126 126 raise formencode.Invalid(error_msg, v, state, error_dict={'totp': error_msg})
127 127 return form_data
128 128
129 129 return _TOTPForm
130 130
131 131
132 132 def WhitelistedVcsClientsForm(localizer):
133 133 _ = localizer
134 134
135 135 class _WhitelistedVcsClientsForm(formencode.Schema):
136 136 regexp = r'^(?:\s*[<>=~^!]*\s*\d{1,2}\.\d{1,2}(?:\.\d{1,2})?\s*|\*)\s*(?:,\s*[<>=~^!]*\s*\d{1,2}\.\d{1,2}(?:\.\d{1,2})?\s*|\s*\*\s*)*$'
137 137 allow_extra_fields = True
138 138 filter_extra_fields = True
139 139 git = v.Regex(regexp)
140 140 hg = v.Regex(regexp)
141 141 svn = v.Regex(regexp)
142 142
143 143 return _WhitelistedVcsClientsForm
144 144
145 145
146 146 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
147 147 old_data = old_data or {}
148 148 available_languages = available_languages or []
149 149 _ = localizer
150 150
151 151 class _UserForm(formencode.Schema):
152 152 allow_extra_fields = True
153 153 filter_extra_fields = True
154 154 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
155 155 v.ValidUsername(localizer, edit, old_data))
156 156 if edit:
157 157 new_password = All(
158 158 v.ValidPassword(localizer),
159 159 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
160 160 )
161 161 password_confirmation = All(
162 162 v.ValidPassword(localizer),
163 163 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
164 164 )
165 165 admin = v.StringBoolean(if_missing=False)
166 166 else:
167 167 password = All(
168 168 v.ValidPassword(localizer),
169 169 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
170 170 )
171 171 password_confirmation = All(
172 172 v.ValidPassword(localizer),
173 173 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
174 174 )
175 175
176 176 password_change = v.StringBoolean(if_missing=False)
177 177 create_repo_group = v.StringBoolean(if_missing=False)
178 178
179 179 active = v.StringBoolean(if_missing=False)
180 180 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
181 181 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
182 182 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
183 183 description = v.UnicodeString(strip=True, min=1, max=250, not_empty=False,
184 184 if_missing='')
185 185 extern_name = v.UnicodeString(strip=True)
186 186 extern_type = v.UnicodeString(strip=True)
187 187 language = v.OneOf(available_languages, hideList=False,
188 188 testValueList=True, if_missing=None)
189 189 chained_validators = [v.ValidPasswordsMatch(localizer)]
190 190 return _UserForm
191 191
192 192
193 193 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
194 194 old_data = old_data or {}
195 195 _ = localizer
196 196
197 197 class _UserGroupForm(formencode.Schema):
198 198 allow_extra_fields = True
199 199 filter_extra_fields = True
200 200
201 201 users_group_name = All(
202 202 v.UnicodeString(strip=True, min=1, not_empty=True),
203 203 v.ValidUserGroup(localizer, edit, old_data)
204 204 )
205 205 user_group_description = v.UnicodeString(strip=True, min=1,
206 206 not_empty=False)
207 207
208 208 users_group_active = v.StringBoolean(if_missing=False)
209 209
210 210 if edit:
211 211 # this is user group owner
212 212 user = All(
213 213 v.UnicodeString(not_empty=True),
214 214 v.ValidRepoUser(localizer, allow_disabled))
215 215 return _UserGroupForm
216 216
217 217
218 218 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
219 219 can_create_in_root=False, allow_disabled=False):
220 220 _ = localizer
221 221 old_data = old_data or {}
222 222 available_groups = available_groups or []
223 223
224 224 class _RepoGroupForm(formencode.Schema):
225 225 allow_extra_fields = True
226 226 filter_extra_fields = False
227 227
228 228 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
229 229 v.SlugifyName(localizer),)
230 230 group_description = v.UnicodeString(strip=True, min=1,
231 231 not_empty=False)
232 232 group_copy_permissions = v.StringBoolean(if_missing=False)
233 233
234 234 group_parent_id = v.OneOf(available_groups, hideList=False,
235 235 testValueList=True, not_empty=True)
236 236 enable_locking = v.StringBoolean(if_missing=False)
237 237 chained_validators = [
238 238 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
239 239
240 240 if edit:
241 241 # this is repo group owner
242 242 user = All(
243 243 v.UnicodeString(not_empty=True),
244 244 v.ValidRepoUser(localizer, allow_disabled))
245 245 return _RepoGroupForm
246 246
247 247
248 248 def RegisterForm(localizer, edit=False, old_data=None):
249 249 _ = localizer
250 250 old_data = old_data or {}
251 251
252 252 class _RegisterForm(formencode.Schema):
253 253 allow_extra_fields = True
254 254 filter_extra_fields = True
255 255 username = All(
256 256 v.ValidUsername(localizer, edit, old_data),
257 257 v.UnicodeString(strip=True, min=1, not_empty=True)
258 258 )
259 259 password = All(
260 260 v.ValidPassword(localizer),
261 261 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
262 262 )
263 263 password_confirmation = All(
264 264 v.ValidPassword(localizer),
265 265 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
266 266 )
267 267 active = v.StringBoolean(if_missing=False)
268 268 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
269 269 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
270 270 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
271 271
272 272 chained_validators = [v.ValidPasswordsMatch(localizer)]
273 273 return _RegisterForm
274 274
275 275
276 276 def PasswordResetForm(localizer):
277 277 _ = localizer
278 278
279 279 class _PasswordResetForm(formencode.Schema):
280 280 allow_extra_fields = True
281 281 filter_extra_fields = True
282 282 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
283 283 return _PasswordResetForm
284 284
285 285
286 286 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None, allow_disabled=False):
287 287 _ = localizer
288 288 old_data = old_data or {}
289 289 repo_groups = repo_groups or []
290 290 supported_backends = BACKENDS.keys()
291 291
292 292 class _RepoForm(formencode.Schema):
293 293 allow_extra_fields = True
294 294 filter_extra_fields = False
295 295 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
296 296 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
297 297 repo_group = All(v.CanWriteGroup(localizer, old_data),
298 298 v.OneOf(repo_groups, hideList=True))
299 299 repo_type = v.OneOf(supported_backends, required=False,
300 300 if_missing=old_data.get('repo_type'))
301 301 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
302 302 repo_private = v.StringBoolean(if_missing=False)
303 303 repo_copy_permissions = v.StringBoolean(if_missing=False)
304 304 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
305 305
306 306 repo_enable_statistics = v.StringBoolean(if_missing=False)
307 307 repo_enable_downloads = v.StringBoolean(if_missing=False)
308 308 repo_enable_locking = v.StringBoolean(if_missing=False)
309 309
310 310 if edit:
311 311 # this is repo owner
312 312 user = All(
313 313 v.UnicodeString(not_empty=True),
314 314 v.ValidRepoUser(localizer, allow_disabled))
315 315 clone_uri_change = v.UnicodeString(
316 316 not_empty=False, if_missing=v.Missing)
317 317
318 318 chained_validators = [v.ValidCloneUri(localizer),
319 319 v.ValidRepoName(localizer, edit, old_data)]
320 320 return _RepoForm
321 321
322 322
323 323 def RepoPermsForm(localizer):
324 324 _ = localizer
325 325
326 326 class _RepoPermsForm(formencode.Schema):
327 327 allow_extra_fields = True
328 328 filter_extra_fields = False
329 329 chained_validators = [v.ValidPerms(localizer, type_='repo')]
330 330 return _RepoPermsForm
331 331
332 332
333 333 def RepoGroupPermsForm(localizer, valid_recursive_choices):
334 334 _ = localizer
335 335
336 336 class _RepoGroupPermsForm(formencode.Schema):
337 337 allow_extra_fields = True
338 338 filter_extra_fields = False
339 339 recursive = v.OneOf(valid_recursive_choices)
340 340 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
341 341 return _RepoGroupPermsForm
342 342
343 343
344 344 def UserGroupPermsForm(localizer):
345 345 _ = localizer
346 346
347 347 class _UserPermsForm(formencode.Schema):
348 348 allow_extra_fields = True
349 349 filter_extra_fields = False
350 350 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
351 351 return _UserPermsForm
352 352
353 353
354 354 def RepoFieldForm(localizer):
355 355 _ = localizer
356 356
357 357 class _RepoFieldForm(formencode.Schema):
358 358 filter_extra_fields = True
359 359 allow_extra_fields = True
360 360
361 361 new_field_key = All(v.FieldKey(localizer),
362 362 v.UnicodeString(strip=True, min=3, not_empty=True))
363 363 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
364 364 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
365 365 if_missing='str')
366 366 new_field_label = v.UnicodeString(not_empty=False)
367 367 new_field_desc = v.UnicodeString(not_empty=False)
368 368 return _RepoFieldForm
369 369
370 370
371 371 def RepoForkForm(localizer, edit=False, old_data=None,
372 372 supported_backends=BACKENDS.keys(), repo_groups=None):
373 373 _ = localizer
374 374 old_data = old_data or {}
375 375 repo_groups = repo_groups or []
376 376
377 377 class _RepoForkForm(formencode.Schema):
378 378 allow_extra_fields = True
379 379 filter_extra_fields = False
380 380 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
381 381 v.SlugifyName(localizer))
382 382 repo_group = All(v.CanWriteGroup(localizer, ),
383 383 v.OneOf(repo_groups, hideList=True))
384 384 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
385 385 description = v.UnicodeString(strip=True, min=1, not_empty=True)
386 386 private = v.StringBoolean(if_missing=False)
387 387 copy_permissions = v.StringBoolean(if_missing=False)
388 388 fork_parent_id = v.UnicodeString()
389 389 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
390 390 return _RepoForkForm
391 391
392 392
393 393 def ApplicationSettingsForm(localizer):
394 394 _ = localizer
395 395
396 396 class _ApplicationSettingsForm(formencode.Schema):
397 397 allow_extra_fields = True
398 398 filter_extra_fields = False
399 399 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
400 400 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
401 401 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
402 402 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
403 403 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
404 404 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
405 405 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
406 406 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
407 407 return _ApplicationSettingsForm
408 408
409 409
410 410 def ApplicationVisualisationForm(localizer):
411 411 from rhodecode.model.db import Repository
412 412 _ = localizer
413 413
414 414 class _ApplicationVisualisationForm(formencode.Schema):
415 415 allow_extra_fields = True
416 416 filter_extra_fields = False
417 417 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
418 418 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
419 419 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
420 420
421 421 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
422 422 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
423 423 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
424 424 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
425 425 rhodecode_show_version = v.StringBoolean(if_missing=False)
426 426 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
427 427 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
428 428 rhodecode_gravatar_url = v.UnicodeString(min=3)
429 429 rhodecode_clone_uri_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI)
430 430 rhodecode_clone_uri_id_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_ID)
431 431 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_SSH)
432 432 rhodecode_support_url = v.UnicodeString()
433 433 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
434 434 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
435 435 return _ApplicationVisualisationForm
436 436
437 437
438 438 class _BaseVcsSettingsForm(formencode.Schema):
439 439
440 440 allow_extra_fields = True
441 441 filter_extra_fields = False
442 442 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
443 443 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
444 444 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
445 445
446 446 # PR/Code-review
447 447 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
448 448 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
449 449
450 450 # hg
451 451 extensions_largefiles = v.StringBoolean(if_missing=False)
452 452 extensions_evolve = v.StringBoolean(if_missing=False)
453 453 phases_publish = v.StringBoolean(if_missing=False)
454 454
455 455 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
456 456 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
457 457
458 458 # git
459 459 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
460 460 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
461 461 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
462 462
463 463 # cache
464 464 rhodecode_diff_cache = v.StringBoolean(if_missing=False)
465 465
466 466
467 467 def ApplicationUiSettingsForm(localizer):
468 468 _ = localizer
469 469
470 470 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
471 471 web_push_ssl = v.StringBoolean(if_missing=False)
472 largefiles_usercache = All(
473 v.ValidPath(localizer),
474 v.UnicodeString(strip=True, min=2, not_empty=True))
475 vcs_git_lfs_store_location = All(
476 v.ValidPath(localizer),
477 v.UnicodeString(strip=True, min=2, not_empty=True))
478 472 extensions_hggit = v.StringBoolean(if_missing=False)
479 473 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
480 474 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
481 475 return _ApplicationUiSettingsForm
482 476
483 477
484 478 def RepoVcsSettingsForm(localizer, repo_name):
485 479 _ = localizer
486 480
487 481 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
488 482 inherit_global_settings = v.StringBoolean(if_missing=False)
489 483 new_svn_branch = v.ValidSvnPattern(localizer,
490 484 section='vcs_svn_branch', repo_name=repo_name)
491 485 new_svn_tag = v.ValidSvnPattern(localizer,
492 486 section='vcs_svn_tag', repo_name=repo_name)
493 487 return _RepoVcsSettingsForm
494 488
495 489
496 490 def LabsSettingsForm(localizer):
497 491 _ = localizer
498 492
499 493 class _LabSettingsForm(formencode.Schema):
500 494 allow_extra_fields = True
501 495 filter_extra_fields = False
502 496 return _LabSettingsForm
503 497
504 498
505 499 def ApplicationPermissionsForm(
506 500 localizer, register_choices, password_reset_choices,
507 501 extern_activate_choices):
508 502 _ = localizer
509 503
510 504 class _DefaultPermissionsForm(formencode.Schema):
511 505 allow_extra_fields = True
512 506 filter_extra_fields = True
513 507
514 508 anonymous = v.StringBoolean(if_missing=False)
515 509 default_register = v.OneOf(register_choices)
516 510 default_register_message = v.UnicodeString()
517 511 default_password_reset = v.OneOf(password_reset_choices)
518 512 default_extern_activate = v.OneOf(extern_activate_choices)
519 513 return _DefaultPermissionsForm
520 514
521 515
522 516 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
523 517 user_group_perms_choices):
524 518 _ = localizer
525 519
526 520 class _ObjectPermissionsForm(formencode.Schema):
527 521 allow_extra_fields = True
528 522 filter_extra_fields = True
529 523 overwrite_default_repo = v.StringBoolean(if_missing=False)
530 524 overwrite_default_group = v.StringBoolean(if_missing=False)
531 525 overwrite_default_user_group = v.StringBoolean(if_missing=False)
532 526
533 527 default_repo_perm = v.OneOf(repo_perms_choices)
534 528 default_group_perm = v.OneOf(group_perms_choices)
535 529 default_user_group_perm = v.OneOf(user_group_perms_choices)
536 530
537 531 return _ObjectPermissionsForm
538 532
539 533
540 534 def BranchPermissionsForm(localizer, branch_perms_choices):
541 535 _ = localizer
542 536
543 537 class _BranchPermissionsForm(formencode.Schema):
544 538 allow_extra_fields = True
545 539 filter_extra_fields = True
546 540 overwrite_default_branch = v.StringBoolean(if_missing=False)
547 541 default_branch_perm = v.OneOf(branch_perms_choices)
548 542
549 543 return _BranchPermissionsForm
550 544
551 545
552 546 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
553 547 repo_group_create_choices, user_group_create_choices,
554 548 fork_choices, inherit_default_permissions_choices):
555 549 _ = localizer
556 550
557 551 class _DefaultPermissionsForm(formencode.Schema):
558 552 allow_extra_fields = True
559 553 filter_extra_fields = True
560 554
561 555 anonymous = v.StringBoolean(if_missing=False)
562 556
563 557 default_repo_create = v.OneOf(create_choices)
564 558 default_repo_create_on_write = v.OneOf(create_on_write_choices)
565 559 default_user_group_create = v.OneOf(user_group_create_choices)
566 560 default_repo_group_create = v.OneOf(repo_group_create_choices)
567 561 default_fork_create = v.OneOf(fork_choices)
568 562 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
569 563 return _DefaultPermissionsForm
570 564
571 565
572 566 def UserIndividualPermissionsForm(localizer):
573 567 _ = localizer
574 568
575 569 class _DefaultPermissionsForm(formencode.Schema):
576 570 allow_extra_fields = True
577 571 filter_extra_fields = True
578 572
579 573 inherit_default_permissions = v.StringBoolean(if_missing=False)
580 574 return _DefaultPermissionsForm
581 575
582 576
583 577 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
584 578 _ = localizer
585 579 old_data = old_data or {}
586 580
587 581 class _DefaultsForm(formencode.Schema):
588 582 allow_extra_fields = True
589 583 filter_extra_fields = True
590 584 default_repo_type = v.OneOf(supported_backends)
591 585 default_repo_private = v.StringBoolean(if_missing=False)
592 586 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
593 587 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
594 588 default_repo_enable_locking = v.StringBoolean(if_missing=False)
595 589 return _DefaultsForm
596 590
597 591
598 592 def AuthSettingsForm(localizer):
599 593 _ = localizer
600 594
601 595 class _AuthSettingsForm(formencode.Schema):
602 596 allow_extra_fields = True
603 597 filter_extra_fields = True
604 598 auth_plugins = All(v.ValidAuthPlugins(localizer),
605 599 v.UniqueListFromString(localizer)(not_empty=True))
606 600 return _AuthSettingsForm
607 601
608 602
609 603 def UserExtraEmailForm(localizer):
610 604 _ = localizer
611 605
612 606 class _UserExtraEmailForm(formencode.Schema):
613 607 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
614 608 return _UserExtraEmailForm
615 609
616 610
617 611 def UserExtraIpForm(localizer):
618 612 _ = localizer
619 613
620 614 class _UserExtraIpForm(formencode.Schema):
621 615 ip = v.ValidIp(localizer)(not_empty=True)
622 616 return _UserExtraIpForm
623 617
624 618
625 619 def PullRequestForm(localizer, repo_id):
626 620 _ = localizer
627 621
628 622 class ReviewerForm(formencode.Schema):
629 623 user_id = v.Int(not_empty=True)
630 624 reasons = All()
631 625 rules = All(v.UniqueList(localizer, convert=int)())
632 626 mandatory = v.StringBoolean()
633 627 role = v.String(if_missing='reviewer')
634 628
635 629 class ObserverForm(formencode.Schema):
636 630 user_id = v.Int(not_empty=True)
637 631 reasons = All()
638 632 rules = All(v.UniqueList(localizer, convert=int)())
639 633 mandatory = v.StringBoolean()
640 634 role = v.String(if_missing='observer')
641 635
642 636 class _PullRequestForm(formencode.Schema):
643 637 allow_extra_fields = True
644 638 filter_extra_fields = True
645 639
646 640 common_ancestor = v.UnicodeString(strip=True, required=True)
647 641 source_repo = v.UnicodeString(strip=True, required=True)
648 642 source_ref = v.UnicodeString(strip=True, required=True)
649 643 target_repo = v.UnicodeString(strip=True, required=True)
650 644 target_ref = v.UnicodeString(strip=True, required=True)
651 645 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
652 646 v.UniqueList(localizer)(not_empty=True))
653 647 review_members = formencode.ForEach(ReviewerForm())
654 648 observer_members = formencode.ForEach(ObserverForm())
655 649 pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
656 650 pullrequest_desc = v.UnicodeString(strip=True, required=False)
657 651 description_renderer = v.UnicodeString(strip=True, required=False)
658 652
659 653 return _PullRequestForm
660 654
661 655
662 656 def IssueTrackerPatternsForm(localizer):
663 657 _ = localizer
664 658
665 659 class _IssueTrackerPatternsForm(formencode.Schema):
666 660 allow_extra_fields = True
667 661 filter_extra_fields = False
668 662 chained_validators = [v.ValidPattern(localizer)]
669 663 return _IssueTrackerPatternsForm
@@ -1,902 +1,893 b''
1 1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import os
20 20 import re
21 21 import logging
22 22 import time
23 23 import functools
24 24 from collections import namedtuple
25 25
26 26 from pyramid.threadlocal import get_current_request
27 27
28 28 from rhodecode.lib import rc_cache
29 29 from rhodecode.lib.hash_utils import sha1_safe
30 30 from rhodecode.lib.html_filters import sanitize_html
31 31 from rhodecode.lib.utils2 import (
32 32 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
33 33 from rhodecode.lib.vcs.backends import base
34 34 from rhodecode.lib.statsd_client import StatsdClient
35 35 from rhodecode.model import BaseModel
36 36 from rhodecode.model.db import (
37 37 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
38 38 from rhodecode.model.meta import Session
39 39
40 40
41 41 log = logging.getLogger(__name__)
42 42
43 43
44 44 UiSetting = namedtuple(
45 45 'UiSetting', ['section', 'key', 'value', 'active'])
46 46
47 47 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
48 48
49 49
50 50 class SettingNotFound(Exception):
51 51 def __init__(self, setting_id):
52 52 msg = f'Setting `{setting_id}` is not found'
53 53 super().__init__(msg)
54 54
55 55
56 56 class SettingsModel(BaseModel):
57 57 BUILTIN_HOOKS = (
58 58 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
59 59 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
60 60 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
61 61 RhodeCodeUi.HOOK_PUSH_KEY,)
62 62 HOOKS_SECTION = 'hooks'
63 63
64 64 def __init__(self, sa=None, repo=None):
65 65 self.repo = repo
66 66 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
67 67 self.SettingsDbModel = (
68 68 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
69 69 super().__init__(sa)
70 70
71 71 def get_keyname(self, key_name, prefix='rhodecode_'):
72 72 return f'{prefix}{key_name}'
73 73
74 74 def get_ui_by_key(self, key):
75 75 q = self.UiDbModel.query()
76 76 q = q.filter(self.UiDbModel.ui_key == key)
77 77 q = self._filter_by_repo(RepoRhodeCodeUi, q)
78 78 return q.scalar()
79 79
80 80 def get_ui_by_section(self, section):
81 81 q = self.UiDbModel.query()
82 82 q = q.filter(self.UiDbModel.ui_section == section)
83 83 q = self._filter_by_repo(RepoRhodeCodeUi, q)
84 84 return q.all()
85 85
86 86 def get_ui_by_section_and_key(self, section, key):
87 87 q = self.UiDbModel.query()
88 88 q = q.filter(self.UiDbModel.ui_section == section)
89 89 q = q.filter(self.UiDbModel.ui_key == key)
90 90 q = self._filter_by_repo(RepoRhodeCodeUi, q)
91 91 return q.scalar()
92 92
93 93 def get_ui(self, section=None, key=None):
94 94 q = self.UiDbModel.query()
95 95 q = self._filter_by_repo(RepoRhodeCodeUi, q)
96 96
97 97 if section:
98 98 q = q.filter(self.UiDbModel.ui_section == section)
99 99 if key:
100 100 q = q.filter(self.UiDbModel.ui_key == key)
101 101
102 102 # TODO: mikhail: add caching
103 103 result = [
104 104 UiSetting(
105 105 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
106 106 value=safe_str(r.ui_value), active=r.ui_active
107 107 )
108 108 for r in q.all()
109 109 ]
110 110 return result
111 111
112 112 def get_builtin_hooks(self):
113 113 q = self.UiDbModel.query()
114 114 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
115 115 return self._get_hooks(q)
116 116
117 117 def get_custom_hooks(self):
118 118 q = self.UiDbModel.query()
119 119 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
120 120 return self._get_hooks(q)
121 121
122 122 def create_ui_section_value(self, section, val, key=None, active=True):
123 123 new_ui = self.UiDbModel()
124 124 new_ui.ui_section = section
125 125 new_ui.ui_value = val
126 126 new_ui.ui_active = active
127 127
128 128 repository_id = ''
129 129 if self.repo:
130 130 repo = self._get_repo(self.repo)
131 131 repository_id = repo.repo_id
132 132 new_ui.repository_id = repository_id
133 133
134 134 if not key:
135 135 # keys are unique so they need appended info
136 136 if self.repo:
137 137 key = sha1_safe(f'{section}{val}{repository_id}')
138 138 else:
139 139 key = sha1_safe(f'{section}{val}')
140 140
141 141 new_ui.ui_key = key
142 142
143 143 Session().add(new_ui)
144 144 return new_ui
145 145
146 146 def create_or_update_hook(self, key, value):
147 147 ui = (
148 148 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
149 149 self.UiDbModel())
150 150 ui.ui_section = self.HOOKS_SECTION
151 151 ui.ui_active = True
152 152 ui.ui_key = key
153 153 ui.ui_value = value
154 154
155 155 if self.repo:
156 156 repo = self._get_repo(self.repo)
157 157 repository_id = repo.repo_id
158 158 ui.repository_id = repository_id
159 159
160 160 Session().add(ui)
161 161 return ui
162 162
163 163 def delete_ui(self, id_):
164 164 ui = self.UiDbModel.get(id_)
165 165 if not ui:
166 166 raise SettingNotFound(id_)
167 167 Session().delete(ui)
168 168
169 169 def get_setting_by_name(self, name):
170 170 q = self._get_settings_query()
171 171 q = q.filter(self.SettingsDbModel.app_settings_name == name)
172 172 return q.scalar()
173 173
174 174 def create_or_update_setting(
175 175 self, name, val: Optional | str = Optional(''), type_: Optional | str = Optional('unicode')):
176 176 """
177 177 Creates or updates RhodeCode setting. If updates are triggered, it will
178 178 only update parameters that are explicitly set Optional instance will
179 179 be skipped
180 180
181 181 :param name:
182 182 :param val:
183 183 :param type_:
184 184 :return:
185 185 """
186 186
187 187 res = self.get_setting_by_name(name)
188 188 repo = self._get_repo(self.repo) if self.repo else None
189 189
190 190 if not res:
191 191 val = Optional.extract(val)
192 192 type_ = Optional.extract(type_)
193 193
194 194 args = (
195 195 (repo.repo_id, name, val, type_)
196 196 if repo else (name, val, type_))
197 197 res = self.SettingsDbModel(*args)
198 198
199 199 else:
200 200 if self.repo:
201 201 res.repository_id = repo.repo_id
202 202
203 203 res.app_settings_name = name
204 204 if not isinstance(type_, Optional):
205 205 # update if set
206 206 res.app_settings_type = type_
207 207 if not isinstance(val, Optional):
208 208 # update if set
209 209 res.app_settings_value = val
210 210
211 211 Session().add(res)
212 212 return res
213 213
214 214 def get_cache_region(self):
215 215 repo = self._get_repo(self.repo) if self.repo else None
216 216 cache_key = f"repo.v1.{repo.repo_id}" if repo else "repo.v1.ALL"
217 217 cache_namespace_uid = f'cache_settings.{cache_key}'
218 218 region = rc_cache.get_or_create_region('cache_general', cache_namespace_uid)
219 219 return region, cache_namespace_uid
220 220
221 221 def invalidate_settings_cache(self, hard=False):
222 222 region, namespace_key = self.get_cache_region()
223 223 log.debug('Invalidation cache [%s] region %s for cache_key: %s',
224 224 'invalidate_settings_cache', region, namespace_key)
225 225
226 226 # we use hard cleanup if invalidation is sent
227 227 rc_cache.clear_cache_namespace(region, namespace_key, method=rc_cache.CLEAR_DELETE)
228 228
229 229 def get_cache_call_method(self, cache=True):
230 230 region, cache_key = self.get_cache_region()
231 231
232 232 @region.conditional_cache_on_arguments(condition=cache)
233 233 def _get_all_settings(name, key):
234 234 q = self._get_settings_query()
235 235 if not q:
236 236 raise Exception('Could not get application settings !')
237 237
238 238 settings = {
239 239 self.get_keyname(res.app_settings_name): res.app_settings_value
240 240 for res in q
241 241 }
242 242 return settings
243 243 return _get_all_settings
244 244
245 245 def get_all_settings(self, cache=False, from_request=True):
246 246 # defines if we use GLOBAL, or PER_REPO
247 247 repo = self._get_repo(self.repo) if self.repo else None
248 248
249 249 # initially try the request context; this is the fastest
250 250 # we only fetch global config, NOT for repo-specific
251 251 if from_request and not repo:
252 252 request = get_current_request()
253 253
254 254 if request and hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
255 255 rc_config = request.call_context.rc_config
256 256 if rc_config:
257 257 return rc_config
258 258
259 259 _region, cache_key = self.get_cache_region()
260 260 _get_all_settings = self.get_cache_call_method(cache=cache)
261 261
262 262 start = time.time()
263 263 result = _get_all_settings('rhodecode_settings', cache_key)
264 264 compute_time = time.time() - start
265 265 log.debug('cached method:%s took %.4fs', _get_all_settings.__name__, compute_time)
266 266
267 267 statsd = StatsdClient.statsd
268 268 if statsd:
269 269 elapsed_time_ms = round(1000.0 * compute_time) # use ms only
270 270 statsd.timing("rhodecode_settings_timing.histogram", elapsed_time_ms,
271 271 use_decimals=False)
272 272
273 273 log.debug('Fetching app settings for key: %s took: %.4fs: cache: %s', cache_key, compute_time, cache)
274 274
275 275 return result
276 276
277 277 def get_auth_settings(self):
278 278 q = self._get_settings_query()
279 279 q = q.filter(
280 280 self.SettingsDbModel.app_settings_name.startswith('auth_'))
281 281 rows = q.all()
282 282 auth_settings = {
283 283 row.app_settings_name: row.app_settings_value for row in rows}
284 284 return auth_settings
285 285
286 286 def get_auth_plugins(self):
287 287 auth_plugins = self.get_setting_by_name("auth_plugins")
288 288 return auth_plugins.app_settings_value
289 289
290 290 def get_default_repo_settings(self, strip_prefix=False):
291 291 q = self._get_settings_query()
292 292 q = q.filter(
293 293 self.SettingsDbModel.app_settings_name.startswith('default_'))
294 294 rows = q.all()
295 295
296 296 result = {}
297 297 for row in rows:
298 298 key = row.app_settings_name
299 299 if strip_prefix:
300 300 key = remove_prefix(key, prefix='default_')
301 301 result.update({key: row.app_settings_value})
302 302 return result
303 303
304 304 def get_repo(self):
305 305 repo = self._get_repo(self.repo)
306 306 if not repo:
307 307 raise Exception(
308 308 f'Repository `{self.repo}` cannot be found inside the database')
309 309 return repo
310 310
311 311 def _filter_by_repo(self, model, query):
312 312 if self.repo:
313 313 repo = self.get_repo()
314 314 query = query.filter(model.repository_id == repo.repo_id)
315 315 return query
316 316
317 317 def _get_hooks(self, query):
318 318 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
319 319 query = self._filter_by_repo(RepoRhodeCodeUi, query)
320 320 return query.all()
321 321
322 322 def _get_settings_query(self):
323 323 q = self.SettingsDbModel.query()
324 324 return self._filter_by_repo(RepoRhodeCodeSetting, q)
325 325
326 326 def list_enabled_social_plugins(self, settings):
327 327 enabled = []
328 328 for plug in SOCIAL_PLUGINS_LIST:
329 329 if str2bool(settings.get(f'rhodecode_auth_{plug}_enabled')):
330 330 enabled.append(plug)
331 331 return enabled
332 332
333 333
334 334 def assert_repo_settings(func):
335 335 @functools.wraps(func)
336 336 def _wrapper(self, *args, **kwargs):
337 337 if not self.repo_settings:
338 338 raise Exception('Repository is not specified')
339 339 return func(self, *args, **kwargs)
340 340 return _wrapper
341 341
342 342
343 343 class IssueTrackerSettingsModel(object):
344 344 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
345 345 SETTINGS_PREFIX = 'issuetracker_'
346 346
347 347 def __init__(self, sa=None, repo=None):
348 348 self.global_settings = SettingsModel(sa=sa)
349 349 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
350 350
351 351 @property
352 352 def inherit_global_settings(self):
353 353 if not self.repo_settings:
354 354 return True
355 355 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
356 356 return setting.app_settings_value if setting else True
357 357
358 358 @inherit_global_settings.setter
359 359 def inherit_global_settings(self, value):
360 360 if self.repo_settings:
361 361 settings = self.repo_settings.create_or_update_setting(
362 362 self.INHERIT_SETTINGS, value, type_='bool')
363 363 Session().add(settings)
364 364
365 365 def _get_keyname(self, key, uid, prefix='rhodecode_'):
366 366 return f'{prefix}{self.SETTINGS_PREFIX}{key}_{uid}'
367 367
368 368 def _make_dict_for_settings(self, qs):
369 369 prefix_match = self._get_keyname('pat', '',)
370 370
371 371 issuetracker_entries = {}
372 372 # create keys
373 373 for k, v in qs.items():
374 374 if k.startswith(prefix_match):
375 375 uid = k[len(prefix_match):]
376 376 issuetracker_entries[uid] = None
377 377
378 378 def url_cleaner(input_str):
379 379 input_str = input_str.replace('"', '').replace("'", '')
380 380 input_str = sanitize_html(input_str, strip=True)
381 381 return input_str
382 382
383 383 # populate
384 384 for uid in issuetracker_entries:
385 385 url_data = qs.get(self._get_keyname('url', uid))
386 386
387 387 pat = qs.get(self._get_keyname('pat', uid))
388 388 try:
389 389 pat_compiled = re.compile(r'%s' % pat)
390 390 except re.error:
391 391 pat_compiled = None
392 392
393 393 issuetracker_entries[uid] = AttributeDict({
394 394 'pat': pat,
395 395 'pat_compiled': pat_compiled,
396 396 'url': url_cleaner(
397 397 qs.get(self._get_keyname('url', uid)) or ''),
398 398 'pref': sanitize_html(
399 399 qs.get(self._get_keyname('pref', uid)) or ''),
400 400 'desc': qs.get(
401 401 self._get_keyname('desc', uid)),
402 402 })
403 403
404 404 return issuetracker_entries
405 405
406 406 def get_global_settings(self, cache=False):
407 407 """
408 408 Returns list of global issue tracker settings
409 409 """
410 410 defaults = self.global_settings.get_all_settings(cache=cache)
411 411 settings = self._make_dict_for_settings(defaults)
412 412 return settings
413 413
414 414 def get_repo_settings(self, cache=False):
415 415 """
416 416 Returns list of issue tracker settings per repository
417 417 """
418 418 if not self.repo_settings:
419 419 raise Exception('Repository is not specified')
420 420 all_settings = self.repo_settings.get_all_settings(cache=cache)
421 421 settings = self._make_dict_for_settings(all_settings)
422 422 return settings
423 423
424 424 def get_settings(self, cache=False):
425 425 if self.inherit_global_settings:
426 426 return self.get_global_settings(cache=cache)
427 427 else:
428 428 return self.get_repo_settings(cache=cache)
429 429
430 430 def delete_entries(self, uid):
431 431 if self.repo_settings:
432 432 all_patterns = self.get_repo_settings()
433 433 settings_model = self.repo_settings
434 434 else:
435 435 all_patterns = self.get_global_settings()
436 436 settings_model = self.global_settings
437 437 entries = all_patterns.get(uid, [])
438 438
439 439 for del_key in entries:
440 440 setting_name = self._get_keyname(del_key, uid, prefix='')
441 441 entry = settings_model.get_setting_by_name(setting_name)
442 442 if entry:
443 443 Session().delete(entry)
444 444
445 445 Session().commit()
446 446
447 447 def create_or_update_setting(
448 448 self, name, val=Optional(''), type_=Optional('unicode')):
449 449 if self.repo_settings:
450 450 setting = self.repo_settings.create_or_update_setting(
451 451 name, val, type_)
452 452 else:
453 453 setting = self.global_settings.create_or_update_setting(
454 454 name, val, type_)
455 455 return setting
456 456
457 457
458 458 class VcsSettingsModel(object):
459 459
460 460 INHERIT_SETTINGS = 'inherit_vcs_settings'
461 461 GENERAL_SETTINGS = (
462 462 'use_outdated_comments',
463 463 'pr_merge_enabled',
464 464 'hg_use_rebase_for_merging',
465 465 'hg_close_branch_before_merging',
466 466 'git_use_rebase_for_merging',
467 467 'git_close_branch_before_merging',
468 468 'diff_cache',
469 469 )
470 470
471 471 HOOKS_SETTINGS = (
472 472 ('hooks', 'changegroup.repo_size'),
473 473 ('hooks', 'changegroup.push_logger'),
474 474 ('hooks', 'outgoing.pull_logger'),
475 475 )
476 476 HG_SETTINGS = (
477 477 ('extensions', 'largefiles'),
478 478 ('phases', 'publish'),
479 479 ('extensions', 'evolve'),
480 480 ('extensions', 'topic'),
481 481 ('experimental', 'evolution'),
482 482 ('experimental', 'evolution.exchange'),
483 483 )
484 484 GIT_SETTINGS = (
485 485 ('vcs_git_lfs', 'enabled'),
486 486 )
487 487 GLOBAL_HG_SETTINGS = (
488 488 ('extensions', 'largefiles'),
489 ('largefiles', 'usercache'),
490 489 ('phases', 'publish'),
491 490 ('extensions', 'evolve'),
492 491 ('extensions', 'topic'),
493 492 ('experimental', 'evolution'),
494 493 ('experimental', 'evolution.exchange'),
495 494 )
496 495
497 496 GLOBAL_GIT_SETTINGS = (
498 497 ('vcs_git_lfs', 'enabled'),
499 ('vcs_git_lfs', 'store_location')
500 498 )
501 499
502 500 SVN_BRANCH_SECTION = 'vcs_svn_branch'
503 501 SVN_TAG_SECTION = 'vcs_svn_tag'
504 502 SSL_SETTING = ('web', 'push_ssl')
505 503 PATH_SETTING = ('paths', '/')
506 504
507 505 def __init__(self, sa=None, repo=None):
508 506 self.global_settings = SettingsModel(sa=sa)
509 507 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
510 508 self._ui_settings = (
511 509 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
512 510 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
513 511
514 512 @property
515 513 @assert_repo_settings
516 514 def inherit_global_settings(self):
517 515 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
518 516 return setting.app_settings_value if setting else True
519 517
520 518 @inherit_global_settings.setter
521 519 @assert_repo_settings
522 520 def inherit_global_settings(self, value):
523 521 self.repo_settings.create_or_update_setting(
524 522 self.INHERIT_SETTINGS, value, type_='bool')
525 523
526 524 def get_keyname(self, key_name, prefix='rhodecode_'):
527 525 return f'{prefix}{key_name}'
528 526
529 527 def get_global_svn_branch_patterns(self):
530 528 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
531 529
532 530 @assert_repo_settings
533 531 def get_repo_svn_branch_patterns(self):
534 532 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
535 533
536 534 def get_global_svn_tag_patterns(self):
537 535 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
538 536
539 537 @assert_repo_settings
540 538 def get_repo_svn_tag_patterns(self):
541 539 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
542 540
543 541 def get_global_settings(self):
544 542 return self._collect_all_settings(global_=True)
545 543
546 544 @assert_repo_settings
547 545 def get_repo_settings(self):
548 546 return self._collect_all_settings(global_=False)
549 547
550 548 @assert_repo_settings
551 549 def get_repo_settings_inherited(self):
552 550 global_settings = self.get_global_settings()
553 551 global_settings.update(self.get_repo_settings())
554 552 return global_settings
555 553
556 554 @assert_repo_settings
557 555 def create_or_update_repo_settings(
558 556 self, data, inherit_global_settings=False):
559 557 from rhodecode.model.scm import ScmModel
560 558
561 559 self.inherit_global_settings = inherit_global_settings
562 560
563 561 repo = self.repo_settings.get_repo()
564 562 if not inherit_global_settings:
565 563 if repo.repo_type == 'svn':
566 564 self.create_repo_svn_settings(data)
567 565 else:
568 566 self.create_or_update_repo_hook_settings(data)
569 567 self.create_or_update_repo_pr_settings(data)
570 568
571 569 if repo.repo_type == 'hg':
572 570 self.create_or_update_repo_hg_settings(data)
573 571
574 572 if repo.repo_type == 'git':
575 573 self.create_or_update_repo_git_settings(data)
576 574
577 575 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
578 576
579 577 @assert_repo_settings
580 578 def create_or_update_repo_hook_settings(self, data):
581 579 for section, key in self.HOOKS_SETTINGS:
582 580 data_key = self._get_form_ui_key(section, key)
583 581 if data_key not in data:
584 582 raise ValueError(
585 583 f'The given data does not contain {data_key} key')
586 584
587 585 active = data.get(data_key)
588 586 repo_setting = self.repo_settings.get_ui_by_section_and_key(
589 587 section, key)
590 588 if not repo_setting:
591 589 global_setting = self.global_settings.\
592 590 get_ui_by_section_and_key(section, key)
593 591 self.repo_settings.create_ui_section_value(
594 592 section, global_setting.ui_value, key=key, active=active)
595 593 else:
596 594 repo_setting.ui_active = active
597 595 Session().add(repo_setting)
598 596
599 597 def update_global_hook_settings(self, data):
600 598 for section, key in self.HOOKS_SETTINGS:
601 599 data_key = self._get_form_ui_key(section, key)
602 600 if data_key not in data:
603 601 raise ValueError(
604 602 f'The given data does not contain {data_key} key')
605 603 active = data.get(data_key)
606 604 repo_setting = self.global_settings.get_ui_by_section_and_key(
607 605 section, key)
608 606 repo_setting.ui_active = active
609 607 Session().add(repo_setting)
610 608
611 609 @assert_repo_settings
612 610 def create_or_update_repo_pr_settings(self, data):
613 611 return self._create_or_update_general_settings(
614 612 self.repo_settings, data)
615 613
616 614 def create_or_update_global_pr_settings(self, data):
617 615 return self._create_or_update_general_settings(
618 616 self.global_settings, data)
619 617
620 618 @assert_repo_settings
621 619 def create_repo_svn_settings(self, data):
622 620 return self._create_svn_settings(self.repo_settings, data)
623 621
624 622 def _set_evolution(self, settings, is_enabled):
625 623 if is_enabled:
626 624 # if evolve is active set evolution=all
627 625
628 626 self._create_or_update_ui(
629 627 settings, *('experimental', 'evolution'), value='all',
630 628 active=True)
631 629 self._create_or_update_ui(
632 630 settings, *('experimental', 'evolution.exchange'), value='yes',
633 631 active=True)
634 632 # if evolve is active set topics server support
635 633 self._create_or_update_ui(
636 634 settings, *('extensions', 'topic'), value='',
637 635 active=True)
638 636
639 637 else:
640 638 self._create_or_update_ui(
641 639 settings, *('experimental', 'evolution'), value='',
642 640 active=False)
643 641 self._create_or_update_ui(
644 642 settings, *('experimental', 'evolution.exchange'), value='no',
645 643 active=False)
646 644 self._create_or_update_ui(
647 645 settings, *('extensions', 'topic'), value='',
648 646 active=False)
649 647
650 648 @assert_repo_settings
651 649 def create_or_update_repo_hg_settings(self, data):
652 650 largefiles, phases, evolve = \
653 651 self.HG_SETTINGS[:3]
654 652 largefiles_key, phases_key, evolve_key = \
655 653 self._get_settings_keys(self.HG_SETTINGS[:3], data)
656 654
657 655 self._create_or_update_ui(
658 656 self.repo_settings, *largefiles, value='',
659 657 active=data[largefiles_key])
660 658 self._create_or_update_ui(
661 659 self.repo_settings, *evolve, value='',
662 660 active=data[evolve_key])
663 661 self._set_evolution(self.repo_settings, is_enabled=data[evolve_key])
664 662
665 663 self._create_or_update_ui(
666 664 self.repo_settings, *phases, value=safe_str(data[phases_key]))
667 665
668 666 def create_or_update_global_hg_settings(self, data):
669 opts_len = 4
670 largefiles, largefiles_store, phases, evolve \
667 opts_len = 3
668 largefiles, phases, evolve \
671 669 = self.GLOBAL_HG_SETTINGS[:opts_len]
672 largefiles_key, largefiles_store_key, phases_key, evolve_key \
670 largefiles_key, phases_key, evolve_key \
673 671 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS[:opts_len], data)
674 672
675 673 self._create_or_update_ui(
676 674 self.global_settings, *largefiles, value='',
677 675 active=data[largefiles_key])
678 676 self._create_or_update_ui(
679 self.global_settings, *largefiles_store, value=data[largefiles_store_key])
680 self._create_or_update_ui(
681 677 self.global_settings, *phases, value=safe_str(data[phases_key]))
682 678 self._create_or_update_ui(
683 679 self.global_settings, *evolve, value='',
684 680 active=data[evolve_key])
685 681 self._set_evolution(self.global_settings, is_enabled=data[evolve_key])
686 682
687 683 def create_or_update_repo_git_settings(self, data):
688 684 # NOTE(marcink): # comma makes unpack work properly
689 685 lfs_enabled, \
690 686 = self.GIT_SETTINGS
691 687
692 688 lfs_enabled_key, \
693 689 = self._get_settings_keys(self.GIT_SETTINGS, data)
694 690
695 691 self._create_or_update_ui(
696 692 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
697 693 active=data[lfs_enabled_key])
698 694
699 695 def create_or_update_global_git_settings(self, data):
700 lfs_enabled, lfs_store_location \
701 = self.GLOBAL_GIT_SETTINGS
702 lfs_enabled_key, lfs_store_location_key \
703 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
696 lfs_enabled = self.GLOBAL_GIT_SETTINGS[0]
697 lfs_enabled_key = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)[0]
704 698
705 699 self._create_or_update_ui(
706 700 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
707 701 active=data[lfs_enabled_key])
708 self._create_or_update_ui(
709 self.global_settings, *lfs_store_location,
710 value=data[lfs_store_location_key])
711 702
712 703 def create_or_update_global_svn_settings(self, data):
713 704 # branch/tags patterns
714 705 self._create_svn_settings(self.global_settings, data)
715 706
716 707 def update_global_ssl_setting(self, value):
717 708 self._create_or_update_ui(
718 709 self.global_settings, *self.SSL_SETTING, value=value)
719 710
720 711 @assert_repo_settings
721 712 def delete_repo_svn_pattern(self, id_):
722 713 ui = self.repo_settings.UiDbModel.get(id_)
723 714 if ui and ui.repository.repo_name == self.repo_settings.repo:
724 715 # only delete if it's the same repo as initialized settings
725 716 self.repo_settings.delete_ui(id_)
726 717 else:
727 718 # raise error as if we wouldn't find this option
728 719 self.repo_settings.delete_ui(-1)
729 720
730 721 def delete_global_svn_pattern(self, id_):
731 722 self.global_settings.delete_ui(id_)
732 723
733 724 @assert_repo_settings
734 725 def get_repo_ui_settings(self, section=None, key=None):
735 726 global_uis = self.global_settings.get_ui(section, key)
736 727 repo_uis = self.repo_settings.get_ui(section, key)
737 728
738 729 filtered_repo_uis = self._filter_ui_settings(repo_uis)
739 730 filtered_repo_uis_keys = [
740 731 (s.section, s.key) for s in filtered_repo_uis]
741 732
742 733 def _is_global_ui_filtered(ui):
743 734 return (
744 735 (ui.section, ui.key) in filtered_repo_uis_keys
745 736 or ui.section in self._svn_sections)
746 737
747 738 filtered_global_uis = [
748 739 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
749 740
750 741 return filtered_global_uis + filtered_repo_uis
751 742
752 743 def get_global_ui_settings(self, section=None, key=None):
753 744 return self.global_settings.get_ui(section, key)
754 745
755 746 def get_ui_settings_as_config_obj(self, section=None, key=None):
756 747 config = base.Config()
757 748
758 749 ui_settings = self.get_ui_settings(section=section, key=key)
759 750
760 751 for entry in ui_settings:
761 752 config.set(entry.section, entry.key, entry.value)
762 753
763 754 return config
764 755
765 756 def get_ui_settings(self, section=None, key=None):
766 757 if not self.repo_settings or self.inherit_global_settings:
767 758 return self.get_global_ui_settings(section, key)
768 759 else:
769 760 return self.get_repo_ui_settings(section, key)
770 761
771 762 def get_svn_patterns(self, section=None):
772 763 if not self.repo_settings:
773 764 return self.get_global_ui_settings(section)
774 765 else:
775 766 return self.get_repo_ui_settings(section)
776 767
777 768 @assert_repo_settings
778 769 def get_repo_general_settings(self):
779 770 global_settings = self.global_settings.get_all_settings()
780 771 repo_settings = self.repo_settings.get_all_settings()
781 772 filtered_repo_settings = self._filter_general_settings(repo_settings)
782 773 global_settings.update(filtered_repo_settings)
783 774 return global_settings
784 775
785 776 def get_global_general_settings(self):
786 777 return self.global_settings.get_all_settings()
787 778
788 779 def get_general_settings(self):
789 780 if not self.repo_settings or self.inherit_global_settings:
790 781 return self.get_global_general_settings()
791 782 else:
792 783 return self.get_repo_general_settings()
793 784
794 785 def _filter_ui_settings(self, settings):
795 786 filtered_settings = [
796 787 s for s in settings if self._should_keep_setting(s)]
797 788 return filtered_settings
798 789
799 790 def _should_keep_setting(self, setting):
800 791 keep = (
801 792 (setting.section, setting.key) in self._ui_settings or
802 793 setting.section in self._svn_sections)
803 794 return keep
804 795
805 796 def _filter_general_settings(self, settings):
806 797 keys = [self.get_keyname(key) for key in self.GENERAL_SETTINGS]
807 798 return {
808 799 k: settings[k]
809 800 for k in settings if k in keys}
810 801
811 802 def _collect_all_settings(self, global_=False):
812 803 settings = self.global_settings if global_ else self.repo_settings
813 804 result = {}
814 805
815 806 for section, key in self._ui_settings:
816 807 ui = settings.get_ui_by_section_and_key(section, key)
817 808 result_key = self._get_form_ui_key(section, key)
818 809
819 810 if ui:
820 811 if section in ('hooks', 'extensions'):
821 812 result[result_key] = ui.ui_active
822 813 elif result_key in ['vcs_git_lfs_enabled']:
823 814 result[result_key] = ui.ui_active
824 815 else:
825 816 result[result_key] = ui.ui_value
826 817
827 818 for name in self.GENERAL_SETTINGS:
828 819 setting = settings.get_setting_by_name(name)
829 820 if setting:
830 821 result_key = self.get_keyname(name)
831 822 result[result_key] = setting.app_settings_value
832 823
833 824 return result
834 825
835 826 def _get_form_ui_key(self, section, key):
836 827 return '{section}_{key}'.format(
837 828 section=section, key=key.replace('.', '_'))
838 829
839 830 def _create_or_update_ui(
840 831 self, settings, section, key, value=None, active=None):
841 832 ui = settings.get_ui_by_section_and_key(section, key)
842 833 if not ui:
843 834 active = True if active is None else active
844 835 settings.create_ui_section_value(
845 836 section, value, key=key, active=active)
846 837 else:
847 838 if active is not None:
848 839 ui.ui_active = active
849 840 if value is not None:
850 841 ui.ui_value = value
851 842 Session().add(ui)
852 843
853 844 def _create_svn_settings(self, settings, data):
854 845 svn_settings = {
855 846 'new_svn_branch': self.SVN_BRANCH_SECTION,
856 847 'new_svn_tag': self.SVN_TAG_SECTION
857 848 }
858 849 for key in svn_settings:
859 850 if data.get(key):
860 851 settings.create_ui_section_value(svn_settings[key], data[key])
861 852
862 853 def _create_or_update_general_settings(self, settings, data):
863 854 for name in self.GENERAL_SETTINGS:
864 855 data_key = self.get_keyname(name)
865 856 if data_key not in data:
866 857 raise ValueError(
867 858 f'The given data does not contain {data_key} key')
868 859 setting = settings.create_or_update_setting(
869 860 name, data[data_key], 'bool')
870 861 Session().add(setting)
871 862
872 863 def _get_settings_keys(self, settings, data):
873 864 data_keys = [self._get_form_ui_key(*s) for s in settings]
874 865 for data_key in data_keys:
875 866 if data_key not in data:
876 867 raise ValueError(
877 868 f'The given data does not contain {data_key} key')
878 869 return data_keys
879 870
880 871 def create_largeobjects_dirs_if_needed(self, repo_store_path):
881 872 """
882 873 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
883 874 does a repository scan if enabled in the settings.
884 875 """
885 876
886 877 from rhodecode.lib.vcs.backends.hg import largefiles_store
887 878 from rhodecode.lib.vcs.backends.git import lfs_store
888 879
889 880 paths = [
890 881 largefiles_store(repo_store_path),
891 882 lfs_store(repo_store_path)]
892 883
893 884 for path in paths:
894 885 if os.path.isdir(path):
895 886 continue
896 887 if os.path.isfile(path):
897 888 continue
898 889 # not a file nor dir, we try to create it
899 890 try:
900 891 os.makedirs(path)
901 892 except Exception:
902 893 log.warning('Failed to create largefiles dir:%s', path)
@@ -1,345 +1,323 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, **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 or repo_type in ['git', 'hg']:
27 27 <div class="panel panel-default">
28 28 <div class="panel-heading" id="vcs-hooks-options">
29 29 <h3 class="panel-title">${_('Internal Hooks')}<a class="permalink" href="#vcs-hooks-options"> ΒΆ</a></h3>
30 30 </div>
31 31 <div class="panel-body">
32 32 <div class="field">
33 33 <div class="checkbox">
34 34 ${h.checkbox('hooks_changegroup_repo_size' + suffix, 'True', **kwargs)}
35 35 <label for="hooks_changegroup_repo_size${suffix}">${_('Show repository size after push')}</label>
36 36 </div>
37 37
38 38 <div class="label">
39 39 <span class="help-block">${_('Trigger a hook that calculates repository size after each push.')}</span>
40 40 </div>
41 41 <div class="checkbox">
42 42 ${h.checkbox('hooks_changegroup_push_logger' + suffix, 'True', **kwargs)}
43 43 <label for="hooks_changegroup_push_logger${suffix}">${_('Execute pre/post push hooks')}</label>
44 44 </div>
45 45 <div class="label">
46 46 <span class="help-block">${_('Execute Built in pre/post push hooks. This also executes rcextensions hooks.')}</span>
47 47 </div>
48 48 <div class="checkbox">
49 49 ${h.checkbox('hooks_outgoing_pull_logger' + suffix, 'True', **kwargs)}
50 50 <label for="hooks_outgoing_pull_logger${suffix}">${_('Execute pre/post pull hooks')}</label>
51 51 </div>
52 52 <div class="label">
53 53 <span class="help-block">${_('Execute Built in pre/post pull hooks. This also executes rcextensions hooks.')}</span>
54 54 </div>
55 55 </div>
56 56 </div>
57 57 </div>
58 58 % endif
59 59
60 60 % if display_globals or repo_type in ['hg']:
61 61 <div class="panel panel-default">
62 62 <div class="panel-heading" id="vcs-hg-options">
63 63 <h3 class="panel-title">${_('Mercurial Settings')}<a class="permalink" href="#vcs-hg-options"> ΒΆ</a></h3>
64 64 </div>
65 65 <div class="panel-body">
66 66 <div class="checkbox">
67 67 ${h.checkbox('extensions_largefiles' + suffix, 'True', **kwargs)}
68 68 <label for="extensions_largefiles${suffix}">${_('Enable largefiles extension')}</label>
69 69 </div>
70 70 <div class="label">
71 71 % if display_globals:
72 72 <span class="help-block">${_('Enable Largefiles extensions for all repositories.')}</span>
73 73 % else:
74 74 <span class="help-block">${_('Enable Largefiles extensions for this repository.')}</span>
75 75 % endif
76 76 </div>
77 77
78 % if display_globals:
79 <div class="field">
80 <div class="input">
81 ${h.text('largefiles_usercache' + suffix, size=59)}
82 </div>
83 </div>
84 <div class="label">
85 <span class="help-block">${_('Filesystem location where Mercurial largefile objects should be stored.')}</span>
86 </div>
87 % endif
88
89 78 <div class="checkbox">
90 79 ${h.checkbox('phases_publish' + suffix, 'True', **kwargs)}
91 80 <label for="phases_publish${suffix}">${_('Set repositories as publishing') if display_globals else _('Set repository as publishing')}</label>
92 81 </div>
93 82 <div class="label">
94 83 <span class="help-block">${_('When this is enabled all commits in the repository are seen as public commits by clients.')}</span>
95 84 </div>
96 85
97 86 <div class="checkbox">
98 87 ${h.checkbox('extensions_evolve' + suffix, 'True', **kwargs)}
99 88 <label for="extensions_evolve${suffix}">${_('Enable Evolve and Topic extension')}</label>
100 89 </div>
101 90 <div class="label">
102 91 % if display_globals:
103 92 <span class="help-block">${_('Enable Evolve and Topic extensions for all repositories.')}</span>
104 93 % else:
105 94 <span class="help-block">${_('Enable Evolve and Topic extensions for this repository.')}</span>
106 95 % endif
107 96 </div>
108 97
109 98 </div>
110 99 </div>
111 100 % endif
112 101
113 102 % if display_globals or repo_type in ['git']:
114 103 <div class="panel panel-default">
115 104 <div class="panel-heading" id="vcs-git-options">
116 105 <h3 class="panel-title">${_('Git Settings')}<a class="permalink" href="#vcs-git-options"> ΒΆ</a></h3>
117 106 </div>
118 107 <div class="panel-body">
119 108 <div class="checkbox">
120 109 ${h.checkbox('vcs_git_lfs_enabled' + suffix, 'True', **kwargs)}
121 110 <label for="vcs_git_lfs_enabled${suffix}">${_('Enable lfs extension')}</label>
122 111 </div>
123 112 <div class="label">
124 113 % if display_globals:
125 114 <span class="help-block">${_('Enable lfs extensions for all repositories.')}</span>
126 115 % else:
127 116 <span class="help-block">${_('Enable lfs extensions for this repository.')}</span>
128 117 % endif
129 118 </div>
130
131 % if display_globals:
132 <div class="field">
133 <div class="input">
134 ${h.text('vcs_git_lfs_store_location' + suffix, size=59)}
135 </div>
136 </div>
137 <div class="label">
138 <span class="help-block">${_('Filesystem location where Git lfs objects should be stored.')}</span>
139 </div>
140 % endif
141 119 </div>
142 120 </div>
143 121 % endif
144 122
145 123 % if display_globals or repo_type in ['svn']:
146 124 <div class="panel panel-default">
147 125 <div class="panel-heading" id="vcs-svn-options">
148 126 <h3 class="panel-title">${_('Subversion Settings')}<a class="permalink" href="#vcs-svn-options"> ΒΆ</a></h3>
149 127 </div>
150 128 <div class="panel-body">
151 129 % if display_globals:
152 130 <div class="field">
153 131 <div class="content" >
154 132 <label>${_('mod_dav config')}</label><br/>
155 133 <code>path: ${c.svn_config_path}</code>
156 134 </div>
157 135 <br/>
158 136
159 137 <div>
160 138
161 139 % if c.svn_generate_config:
162 140 <span class="buttons">
163 141 <button class="btn btn-primary" id="vcs_svn_generate_cfg">${_('Re-generate Apache Config')}</button>
164 142 </span>
165 143 % endif
166 144 </div>
167 145 </div>
168 146 % endif
169 147
170 148 <div class="field">
171 149 <div class="content" >
172 150 <label>${_('Repository patterns')}</label><br/>
173 151 </div>
174 152 </div>
175 153 <div class="label">
176 154 <span class="help-block">${_('Patterns for identifying SVN branches and tags. For recursive search, use "*". Eg.: "/branches/*"')}</span>
177 155 </div>
178 156
179 157 <div class="field branch_patterns">
180 158 <div class="input" >
181 159 <label>${_('Branches')}:</label><br/>
182 160 </div>
183 161 % if svn_branch_patterns:
184 162 % for branch in svn_branch_patterns:
185 163 <div class="input adjacent" id="${'id%s' % branch.ui_id}">
186 164 ${h.hidden('branch_ui_key' + suffix, branch.ui_key)}
187 165 ${h.text('branch_value_%d' % branch.ui_id + suffix, branch.ui_value, size=59, readonly="readonly", class_='disabled')}
188 166 % if kwargs.get('disabled') != 'disabled':
189 167 <span class="btn btn-x" onclick="ajaxDeletePattern(${branch.ui_id},'${'id%s' % branch.ui_id}')">
190 168 ${_('Delete')}
191 169 </span>
192 170 % endif
193 171 </div>
194 172 % endfor
195 173 %endif
196 174 </div>
197 175 % if kwargs.get('disabled') != 'disabled':
198 176 <div class="field branch_patterns">
199 177 <div class="input" >
200 178 ${h.text('new_svn_branch',size=59,placeholder='New branch pattern')}
201 179 </div>
202 180 </div>
203 181 % endif
204 182 <div class="field tag_patterns">
205 183 <div class="input" >
206 184 <label>${_('Tags')}:</label><br/>
207 185 </div>
208 186 % if svn_tag_patterns:
209 187 % for tag in svn_tag_patterns:
210 188 <div class="input" id="${'id%s' % tag.ui_id + suffix}">
211 189 ${h.hidden('tag_ui_key' + suffix, tag.ui_key)}
212 190 ${h.text('tag_ui_value_new_%d' % tag.ui_id + suffix, tag.ui_value, size=59, readonly="readonly", class_='disabled tag_input')}
213 191 % if kwargs.get('disabled') != 'disabled':
214 192 <span class="btn btn-x" onclick="ajaxDeletePattern(${tag.ui_id},'${'id%s' % tag.ui_id}')">
215 193 ${_('Delete')}
216 194 </span>
217 195 %endif
218 196 </div>
219 197 % endfor
220 198 % endif
221 199 </div>
222 200 % if kwargs.get('disabled') != 'disabled':
223 201 <div class="field tag_patterns">
224 202 <div class="input" >
225 203 ${h.text('new_svn_tag' + suffix, size=59, placeholder='New tag pattern')}
226 204 </div>
227 205 </div>
228 206 %endif
229 207 </div>
230 208 </div>
231 209 % else:
232 210 ${h.hidden('new_svn_branch' + suffix, '')}
233 211 ${h.hidden('new_svn_tag' + suffix, '')}
234 212 % endif
235 213
236 214
237 215 % if display_globals or repo_type in ['hg', 'git']:
238 216 <div class="panel panel-default">
239 217 <div class="panel-heading" id="vcs-pull-requests-options">
240 218 <h3 class="panel-title">${_('Pull Request Settings')}<a class="permalink" href="#vcs-pull-requests-options"> ΒΆ</a></h3>
241 219 </div>
242 220 <div class="panel-body">
243 221 <div class="checkbox">
244 222 ${h.checkbox('rhodecode_pr_merge_enabled' + suffix, 'True', **kwargs)}
245 223 <label for="rhodecode_pr_merge_enabled${suffix}">${_('Enable server-side merge for pull requests')}</label>
246 224 </div>
247 225 <div class="label">
248 226 <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>
249 227 </div>
250 228 <div class="checkbox">
251 229 ${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
252 230 <label for="rhodecode_use_outdated_comments${suffix}">${_('Invalidate and relocate inline comments during update')}</label>
253 231 </div>
254 232 <div class="label">
255 233 <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>
256 234 </div>
257 235 </div>
258 236 </div>
259 237 % endif
260 238
261 239 % if display_globals or repo_type in ['hg', 'git', 'svn']:
262 240 <div class="panel panel-default">
263 241 <div class="panel-heading" id="vcs-pull-requests-options">
264 242 <h3 class="panel-title">${_('Diff cache')}<a class="permalink" href="#vcs-pull-requests-options"> ΒΆ</a></h3>
265 243 </div>
266 244 <div class="panel-body">
267 245 <div class="checkbox">
268 246 ${h.checkbox('rhodecode_diff_cache' + suffix, 'True', **kwargs)}
269 247 <label for="rhodecode_diff_cache${suffix}">${_('Enable caching diffs for pull requests cache and commits')}</label>
270 248 </div>
271 249 </div>
272 250 </div>
273 251 % endif
274 252
275 253 % if display_globals or repo_type in ['hg',]:
276 254 <div class="panel panel-default">
277 255 <div class="panel-heading" id="vcs-pull-requests-options">
278 256 <h3 class="panel-title">${_('Mercurial Pull Request Settings')}<a class="permalink" href="#vcs-hg-pull-requests-options"> ΒΆ</a></h3>
279 257 </div>
280 258 <div class="panel-body">
281 259 ## Specific HG settings
282 260 <div class="checkbox">
283 261 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
284 262 <label for="rhodecode_hg_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
285 263 </div>
286 264 <div class="label">
287 265 <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
288 266 </div>
289 267
290 268 <div class="checkbox">
291 269 ${h.checkbox('rhodecode_hg_close_branch_before_merging' + suffix, 'True', **kwargs)}
292 270 <label for="rhodecode_hg_close_branch_before_merging{suffix}">${_('Close branch before merging it')}</label>
293 271 </div>
294 272 <div class="label">
295 273 <span class="help-block">${_('Close branch before merging it into destination branch. No effect when rebase strategy is use.')}</span>
296 274 </div>
297 275
298 276
299 277 </div>
300 278 </div>
301 279 % endif
302 280
303 281 % if display_globals or repo_type in ['git']:
304 282 <div class="panel panel-default">
305 283 <div class="panel-heading" id="vcs-pull-requests-options">
306 284 <h3 class="panel-title">${_('Git Pull Request Settings')}<a class="permalink" href="#vcs-git-pull-requests-options"> ΒΆ</a></h3>
307 285 </div>
308 286 <div class="panel-body">
309 287 ## <div class="checkbox">
310 288 ## ${h.checkbox('rhodecode_git_use_rebase_for_merging' + suffix, 'True', **kwargs)}
311 289 ## <label for="rhodecode_git_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
312 290 ## </div>
313 291 ## <div class="label">
314 292 ## <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
315 293 ## </div>
316 294
317 295 <div class="checkbox">
318 296 ${h.checkbox('rhodecode_git_close_branch_before_merging' + suffix, 'True', **kwargs)}
319 297 <label for="rhodecode_git_close_branch_before_merging{suffix}">${_('Delete branch after merging it')}</label>
320 298 </div>
321 299 <div class="label">
322 300 <span class="help-block">${_('Delete branch after merging it into destination branch.')}</span>
323 301 </div>
324 302 </div>
325 303 </div>
326 304 % endif
327 305
328 306 <script type="text/javascript">
329 307
330 308 $(document).ready(function() {
331 309 /* On click handler for the `Generate Apache Config` button. It sends a
332 310 POST request to trigger the (re)generation of the mod_dav_svn config. */
333 311 $('#vcs_svn_generate_cfg').on('click', function(event) {
334 312 event.preventDefault();
335 313 var url = "${h.route_path('admin_settings_vcs_svn_generate_cfg')}";
336 314 var jqxhr = $.post(url, {'csrf_token': CSRF_TOKEN});
337 315 jqxhr.done(function(data) {
338 316 $.Topic('/notifications').publish(data);
339 317 });
340 318 });
341 319 });
342 320
343 321 </script>
344 322 </%def>
345 323
@@ -1,489 +1,490 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import multiprocessing
21 21 import os
22 22
23 23 import mock
24 24 import py
25 25 import pytest
26 26
27 27 from rhodecode.lib import caching_query
28 28 from rhodecode.lib import utils
29 29 from rhodecode.lib.str_utils import safe_bytes
30 30 from rhodecode.model import settings
31 31 from rhodecode.model import db
32 32 from rhodecode.model import meta
33 33 from rhodecode.model.repo import RepoModel
34 34 from rhodecode.model.repo_group import RepoGroupModel
35 35 from rhodecode.model.settings import UiSetting, SettingsModel
36 36 from rhodecode.tests.fixture import Fixture
37 37 from rhodecode_tools.lib.hash_utils import md5_safe
38 38 from rhodecode.lib.ext_json import json
39 39
40 40 fixture = Fixture()
41 41
42 42
43 43 def extract_hooks(config):
44 44 """Return a dictionary with the hook entries of the given config."""
45 45 hooks = {}
46 46 config_items = config.serialize()
47 47 for section, name, value in config_items:
48 48 if section != 'hooks':
49 49 continue
50 50 hooks[name] = value
51 51
52 52 return hooks
53 53
54 54
55 55 def disable_hooks(request, hooks):
56 56 """Disables the given hooks from the UI settings."""
57 57 session = meta.Session()
58 58
59 59 model = SettingsModel()
60 60 for hook_key in hooks:
61 61 sett = model.get_ui_by_key(hook_key)
62 62 sett.ui_active = False
63 63 session.add(sett)
64 64
65 65 # Invalidate cache
66 66 ui_settings = session.query(db.RhodeCodeUi).options(
67 67 caching_query.FromCache('sql_cache_short', 'get_hg_ui_settings'))
68 68
69 69 meta.cache.invalidate(
70 70 ui_settings, {},
71 71 caching_query.FromCache('sql_cache_short', 'get_hg_ui_settings'))
72 72
73 73 ui_settings = session.query(db.RhodeCodeUi).options(
74 74 caching_query.FromCache('sql_cache_short', 'get_hook_settings'))
75 75
76 76 meta.cache.invalidate(
77 77 ui_settings, {},
78 78 caching_query.FromCache('sql_cache_short', 'get_hook_settings'))
79 79
80 80 @request.addfinalizer
81 81 def rollback():
82 82 session.rollback()
83 83
84 84
85 85 HOOK_PRE_PUSH = db.RhodeCodeUi.HOOK_PRE_PUSH
86 86 HOOK_PRETX_PUSH = db.RhodeCodeUi.HOOK_PRETX_PUSH
87 87 HOOK_PUSH = db.RhodeCodeUi.HOOK_PUSH
88 88 HOOK_PRE_PULL = db.RhodeCodeUi.HOOK_PRE_PULL
89 89 HOOK_PULL = db.RhodeCodeUi.HOOK_PULL
90 90 HOOK_REPO_SIZE = db.RhodeCodeUi.HOOK_REPO_SIZE
91 91 HOOK_PUSH_KEY = db.RhodeCodeUi.HOOK_PUSH_KEY
92 92
93 93 HG_HOOKS = frozenset(
94 94 (HOOK_PRE_PULL, HOOK_PULL, HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH,
95 95 HOOK_REPO_SIZE, HOOK_PUSH_KEY))
96 96
97 97
98 98 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
99 99 ([], HG_HOOKS),
100 100 (HG_HOOKS, []),
101 101
102 102 ([HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_REPO_SIZE, HOOK_PUSH_KEY], [HOOK_PRE_PULL, HOOK_PULL, HOOK_PUSH]),
103 103
104 104 # When a pull/push hook is disabled, its pre-pull/push counterpart should
105 105 # be disabled too.
106 106 ([HOOK_PUSH], [HOOK_PRE_PULL, HOOK_PULL, HOOK_REPO_SIZE]),
107 107 ([HOOK_PULL], [HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH, HOOK_REPO_SIZE,
108 108 HOOK_PUSH_KEY]),
109 109 ])
110 110 def test_make_db_config_hg_hooks(baseapp, request, disabled_hooks,
111 111 expected_hooks):
112 112 disable_hooks(request, disabled_hooks)
113 113
114 114 config = utils.make_db_config()
115 115 hooks = extract_hooks(config)
116 116
117 117 assert set(hooks.keys()).intersection(HG_HOOKS) == set(expected_hooks)
118 118
119 119
120 120 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
121 121 ([], ['pull', 'push']),
122 122 ([HOOK_PUSH], ['pull']),
123 123 ([HOOK_PULL], ['push']),
124 124 ([HOOK_PULL, HOOK_PUSH], []),
125 125 ])
126 126 def test_get_enabled_hook_classes(disabled_hooks, expected_hooks):
127 127 hook_keys = (HOOK_PUSH, HOOK_PULL)
128 128 ui_settings = [
129 129 ('hooks', key, 'some value', key not in disabled_hooks)
130 130 for key in hook_keys]
131 131
132 132 result = utils.get_enabled_hook_classes(ui_settings)
133 133 assert sorted(result) == expected_hooks
134 134
135 135
136 136 def test_get_filesystem_repos_finds_repos(tmpdir, baseapp):
137 137 _stub_git_repo(tmpdir.ensure('repo', dir=True))
138 138 repos = list(utils.get_filesystem_repos(str(tmpdir)))
139 139 assert repos == [('repo', ('git', tmpdir.join('repo')))]
140 140
141 141
142 142 def test_get_filesystem_repos_skips_directories(tmpdir, baseapp):
143 143 tmpdir.ensure('not-a-repo', dir=True)
144 144 repos = list(utils.get_filesystem_repos(str(tmpdir)))
145 145 assert repos == []
146 146
147 147
148 148 def test_get_filesystem_repos_skips_directories_with_repos(tmpdir, baseapp):
149 149 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
150 150 repos = list(utils.get_filesystem_repos(str(tmpdir)))
151 151 assert repos == []
152 152
153 153
154 154 def test_get_filesystem_repos_finds_repos_in_subdirectories(tmpdir, baseapp):
155 155 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
156 156 repos = list(utils.get_filesystem_repos(str(tmpdir), recursive=True))
157 157 assert repos == [('subdir/repo', ('git', tmpdir.join('subdir', 'repo')))]
158 158
159 159
160 160 def test_get_filesystem_repos_skips_names_starting_with_dot(tmpdir):
161 161 _stub_git_repo(tmpdir.ensure('.repo', dir=True))
162 162 repos = list(utils.get_filesystem_repos(str(tmpdir)))
163 163 assert repos == []
164 164
165 165
166 166 def test_get_filesystem_repos_skips_files(tmpdir):
167 167 tmpdir.ensure('test-file')
168 168 repos = list(utils.get_filesystem_repos(str(tmpdir)))
169 169 assert repos == []
170 170
171 171
172 172 def test_get_filesystem_repos_skips_removed_repositories(tmpdir):
173 173 removed_repo_name = 'rm__00000000_000000_000000__.stub'
174 174 assert utils.REMOVED_REPO_PAT.match(removed_repo_name)
175 175 _stub_git_repo(tmpdir.ensure(removed_repo_name, dir=True))
176 176 repos = list(utils.get_filesystem_repos(str(tmpdir)))
177 177 assert repos == []
178 178
179 179
180 180 def _stub_git_repo(repo_path):
181 181 """
182 182 Make `repo_path` look like a Git repository.
183 183 """
184 184 repo_path.ensure('.git', dir=True)
185 185
186 186
187 187 def test_get_dirpaths_returns_all_paths_on_str(tmpdir):
188 188 tmpdir.ensure('test-file')
189 189 tmpdir.ensure('test-file-1')
190 190 tmp_path = str(tmpdir)
191 191 dirpaths = utils.get_dirpaths(tmp_path)
192 192 assert list(sorted(dirpaths)) == ['test-file', 'test-file-1']
193 193
194 194
195 195 def test_get_dirpaths_returns_all_paths_on_bytes(tmpdir):
196 196 tmpdir.ensure('test-file-bytes')
197 197 tmp_path = str(tmpdir)
198 198 dirpaths = utils.get_dirpaths(safe_bytes(tmp_path))
199 199 assert list(sorted(dirpaths)) == [b'test-file-bytes']
200 200
201 201
202 202 def test_get_dirpaths_returns_all_paths_bytes(
203 203 tmpdir, platform_encodes_filenames):
204 204 if platform_encodes_filenames:
205 205 pytest.skip("This platform seems to encode filenames.")
206 206 tmpdir.ensure('repo-a-umlaut-\xe4')
207 207 dirpaths = utils.get_dirpaths(str(tmpdir))
208 208 assert dirpaths == ['repo-a-umlaut-\xe4']
209 209
210 210
211 211 def test_get_dirpaths_skips_paths_it_cannot_decode(
212 212 tmpdir, platform_encodes_filenames):
213 213 if platform_encodes_filenames:
214 214 pytest.skip("This platform seems to encode filenames.")
215 215 path_with_latin1 = 'repo-a-umlaut-\xe4'
216 216 tmp_path = str(tmpdir.ensure(path_with_latin1))
217 217 dirpaths = utils.get_dirpaths(tmp_path)
218 218 assert dirpaths == []
219 219
220 220
221 221 @pytest.fixture(scope='session')
222 222 def platform_encodes_filenames():
223 223 """
224 224 Boolean indicator if the current platform changes filename encodings.
225 225 """
226 226 path_with_latin1 = 'repo-a-umlaut-\xe4'
227 227 tmpdir = py.path.local.mkdtemp()
228 228 tmpdir.ensure(path_with_latin1)
229 229 read_path = tmpdir.listdir()[0].basename
230 230 tmpdir.remove()
231 231 return path_with_latin1 != read_path
232 232
233 233
234 234 def test_repo2db_mapper_groups(repo_groups):
235 235 session = meta.Session()
236 236 zombie_group, parent_group, child_group = repo_groups
237 237 zombie_path = os.path.join(
238 238 RepoGroupModel().repos_path, zombie_group.full_path)
239 239 os.rmdir(zombie_path)
240 240
241 241 # Avoid removing test repos when calling repo2db_mapper
242 242 repo_list = {
243 243 repo.repo_name: 'test' for repo in session.query(db.Repository).all()
244 244 }
245 245 utils.repo2db_mapper(repo_list, remove_obsolete=True)
246 246
247 247 groups_in_db = session.query(db.RepoGroup).all()
248 248 assert child_group in groups_in_db
249 249 assert parent_group in groups_in_db
250 250 assert zombie_path not in groups_in_db
251 251
252 252
253 253 def test_repo2db_mapper_enables_largefiles(backend):
254 254 repo = backend.create_repo()
255 255 repo_list = {repo.repo_name: 'test'}
256 256 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm_mock:
257 257 utils.repo2db_mapper(repo_list, remove_obsolete=False)
258 258 _, kwargs = scm_mock.call_args
259 259 assert kwargs['config'].get('extensions', 'largefiles') == ''
260 260
261 261
262 262 @pytest.mark.backends("git", "svn")
263 263 def test_repo2db_mapper_installs_hooks_for_repos_in_db(backend):
264 264 repo = backend.create_repo()
265 265 repo_list = {repo.repo_name: 'test'}
266 266 utils.repo2db_mapper(repo_list, remove_obsolete=False)
267 267
268 268
269 269 @pytest.mark.backends("git", "svn")
270 270 def test_repo2db_mapper_installs_hooks_for_newly_added_repos(backend):
271 271 repo = backend.create_repo()
272 272 RepoModel().delete(repo, fs_remove=False)
273 273 meta.Session().commit()
274 274 repo_list = {repo.repo_name: repo.scm_instance()}
275 275 utils.repo2db_mapper(repo_list, remove_obsolete=False)
276 276
277 277
278 278 class TestPasswordChanged(object):
279 279
280 280 def setup_method(self):
281 281 self.session = {
282 282 'rhodecode_user': {
283 283 'password': '0cc175b9c0f1b6a831c399e269772661'
284 284 }
285 285 }
286 286 self.auth_user = mock.Mock()
287 287 self.auth_user.userame = 'test'
288 288 self.auth_user.password = 'abc123'
289 289
290 290 def test_returns_false_for_default_user(self):
291 291 self.auth_user.username = db.User.DEFAULT_USER
292 292 result = utils.password_changed(self.auth_user, self.session)
293 293 assert result is False
294 294
295 295 def test_returns_false_if_password_was_not_changed(self):
296 296 self.session['rhodecode_user']['password'] = md5_safe(
297 297 self.auth_user.password)
298 298 result = utils.password_changed(self.auth_user, self.session)
299 299 assert result is False
300 300
301 301 def test_returns_true_if_password_was_changed(self):
302 302 result = utils.password_changed(self.auth_user, self.session)
303 303 assert result is True
304 304
305 305 def test_returns_true_if_auth_user_password_is_empty(self):
306 306 self.auth_user.password = None
307 307 result = utils.password_changed(self.auth_user, self.session)
308 308 assert result is True
309 309
310 310 def test_returns_true_if_session_password_is_empty(self):
311 311 self.session['rhodecode_user'].pop('password')
312 312 result = utils.password_changed(self.auth_user, self.session)
313 313 assert result is True
314 314
315 315
316 316 class TestReadOpenSourceLicenses(object):
317 317 def test_success(self):
318 318 utils._license_cache = None
319 319 json_data = '''
320 320 {
321 321 "python2.7-pytest-2.7.1": {"UNKNOWN": null},
322 322 "python2.7-Markdown-2.6.2": {
323 323 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
324 324 }
325 325 }
326 326 '''
327 327 resource_string_patch = mock.patch.object(
328 328 utils.pkg_resources, 'resource_string', return_value=json_data)
329 329 with resource_string_patch:
330 330 result = utils.read_opensource_licenses()
331 331 assert result == json.loads(json_data)
332 332
333 333 def test_caching(self):
334 334 utils._license_cache = {
335 335 "python2.7-pytest-2.7.1": {
336 336 "UNKNOWN": None
337 337 },
338 338 "python2.7-Markdown-2.6.2": {
339 339 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
340 340 }
341 341 }
342 342 resource_patch = mock.patch.object(
343 343 utils.pkg_resources, 'resource_string', side_effect=Exception)
344 344 json_patch = mock.patch.object(
345 345 utils.json, 'loads', side_effect=Exception)
346 346
347 347 with resource_patch as resource_mock, json_patch as json_mock:
348 348 result = utils.read_opensource_licenses()
349 349
350 350 assert resource_mock.call_count == 0
351 351 assert json_mock.call_count == 0
352 352 assert result == utils._license_cache
353 353
354 354 def test_licenses_file_contains_no_unknown_licenses(self):
355 355 utils._license_cache = None
356 356 result = utils.read_opensource_licenses()
357 357
358 358 for license_data in result:
359 359 if isinstance(license_data["license"], list):
360 360 for lic_data in license_data["license"]:
361 361 assert 'UNKNOWN' not in lic_data["fullName"]
362 362 else:
363 363 full_name = license_data.get("fullName") or license_data
364 364 assert 'UNKNOWN' not in full_name
365 365
366 366
367 367 class TestMakeDbConfig(object):
368 368 def test_data_from_config_data_from_db_returned(self):
369 369 test_data = [
370 370 ('section1', 'option1', 'value1'),
371 371 ('section2', 'option2', 'value2'),
372 372 ('section3', 'option3', 'value3'),
373 373 ]
374 with mock.patch.object(utils, 'config_data_from_db') as config_mock:
374 with mock.patch.object(utils, 'prepare_config_data') as config_mock:
375 375 config_mock.return_value = test_data
376 376 kwargs = {'clear_session': False, 'repo': 'test_repo'}
377 377 result = utils.make_db_config(**kwargs)
378 378 config_mock.assert_called_once_with(**kwargs)
379 379 for section, option, expected_value in test_data:
380 380 value = result.get(section, option)
381 381 assert value == expected_value
382 382
383 383
384 class TestConfigDataFromDb(object):
385 def test_config_data_from_db_returns_active_settings(self):
384 class TestPrepareConfigData(object):
385 def test_prepare_config_data_returns_active_settings(self):
386 386 test_data = [
387 387 UiSetting('section1', 'option1', 'value1', True),
388 388 UiSetting('section2', 'option2', 'value2', True),
389 389 UiSetting('section3', 'option3', 'value3', False),
390 390 ]
391 391 repo_name = 'test_repo'
392 392
393 393 model_patch = mock.patch.object(settings, 'VcsSettingsModel')
394 394 hooks_patch = mock.patch.object(
395 395 utils, 'get_enabled_hook_classes',
396 396 return_value=['pull', 'push', 'repo_size'])
397 397 with model_patch as model_mock, hooks_patch:
398 398 instance_mock = mock.Mock()
399 399 model_mock.return_value = instance_mock
400 400 instance_mock.get_ui_settings.return_value = test_data
401 result = utils.config_data_from_db(
401 result = utils.prepare_config_data(
402 402 clear_session=False, repo=repo_name)
403 403
404 404 self._assert_repo_name_passed(model_mock, repo_name)
405 405
406 406 expected_result = [
407 407 ('section1', 'option1', 'value1'),
408 408 ('section2', 'option2', 'value2'),
409 409 ]
410 assert result == expected_result
410 # We have extra config items returned, so we're ignoring two last items
411 assert result[:2] == expected_result
411 412
412 413 def _assert_repo_name_passed(self, model_mock, repo_name):
413 414 assert model_mock.call_count == 1
414 415 call_args, call_kwargs = model_mock.call_args
415 416 assert call_kwargs['repo'] == repo_name
416 417
417 418
418 419 class TestIsDirWritable(object):
419 420 def test_returns_false_when_not_writable(self):
420 421 with mock.patch('builtins.open', side_effect=OSError):
421 422 assert not utils._is_dir_writable('/stub-path')
422 423
423 424 def test_returns_true_when_writable(self, tmpdir):
424 425 assert utils._is_dir_writable(str(tmpdir))
425 426
426 427 def test_is_safe_against_race_conditions(self, tmpdir):
427 428 workers = multiprocessing.Pool()
428 429 directories = [str(tmpdir)] * 10
429 430 workers.map(utils._is_dir_writable, directories)
430 431
431 432
432 433 class TestGetEnabledHooks(object):
433 434 def test_only_active_hooks_are_enabled(self):
434 435 ui_settings = [
435 436 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
436 437 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
437 438 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', False)
438 439 ]
439 440 result = utils.get_enabled_hook_classes(ui_settings)
440 441 assert result == ['push', 'repo_size']
441 442
442 443 def test_all_hooks_are_enabled(self):
443 444 ui_settings = [
444 445 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
445 446 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
446 447 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', True)
447 448 ]
448 449 result = utils.get_enabled_hook_classes(ui_settings)
449 450 assert result == ['push', 'repo_size', 'pull']
450 451
451 452 def test_no_enabled_hooks_when_no_hook_settings_are_found(self):
452 453 ui_settings = []
453 454 result = utils.get_enabled_hook_classes(ui_settings)
454 455 assert result == []
455 456
456 457
457 458 def test_obfuscate_url_pw():
458 459 from rhodecode.lib.utils2 import obfuscate_url_pw
459 460 engine = u'/home/repos/malmΓΆ'
460 461 assert obfuscate_url_pw(engine)
461 462
462 463
463 464 @pytest.mark.parametrize("test_ua, expected", [
464 465 ("", ""),
465 466 ('"quoted"', 'quoted'),
466 467 ('internal-merge', 'internal-merge'),
467 468 ('hg/internal-merge', 'hg/internal-merge'),
468 469 ('git/internal-merge', 'git/internal-merge'),
469 470
470 471 # git
471 472 ('git/2.10.1 (Apple Git-78)', 'git/2.10.1'),
472 473 ('GiT/2.37.2.windows.2', 'git/2.37.2'),
473 474 ('git/2.35.1 (Microsoft Windows NT 10.0.19044.0; Win32NT x64) CLR/4.0.30319 VS16/16.0.0', 'git/2.35.1'),
474 475 ('ssh-user-agent', 'ssh-user-agent'),
475 476 ('git/ssh-user-agent', 'git/ssh-user-agent'),
476 477
477 478
478 479 # hg
479 480 ('mercurial/proto-1.0 (Mercurial 4.2)', 'mercurial/4.2'),
480 481 ('mercurial/proto-1.0', ''),
481 482 ('mercurial/proto-1.0 (Mercurial 3.9.2)', 'mercurial/3.9.2'),
482 483 ('mercurial/ssh-user-agent', 'mercurial/ssh-user-agent'),
483 484 ('mercurial/proto-1.0 (Mercurial 5.8rc0)', 'mercurial/5.8rc0'),
484 485
485 486
486 487 ])
487 488 def test_user_agent_normalizer(test_ua, expected):
488 489 from rhodecode.lib.utils2 import user_agent_normalizer
489 490 assert user_agent_normalizer(test_ua, safe=False) == expected
@@ -1,1114 +1,1108 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.lib.utils2 import str2bool
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.settings import VcsSettingsModel, UiSetting
26 26
27 27
28 28 HOOKS_FORM_DATA = {
29 29 'hooks_changegroup_repo_size': True,
30 30 'hooks_changegroup_push_logger': True,
31 31 'hooks_outgoing_pull_logger': True
32 32 }
33 33
34 34 SVN_FORM_DATA = {
35 35 'new_svn_branch': 'test-branch',
36 36 'new_svn_tag': 'test-tag'
37 37 }
38 38
39 39 GENERAL_FORM_DATA = {
40 40 'rhodecode_pr_merge_enabled': True,
41 41 'rhodecode_use_outdated_comments': True,
42 42 'rhodecode_hg_use_rebase_for_merging': True,
43 43 'rhodecode_hg_close_branch_before_merging': True,
44 44 'rhodecode_git_use_rebase_for_merging': True,
45 45 'rhodecode_git_close_branch_before_merging': True,
46 46 'rhodecode_diff_cache': True,
47 47 }
48 48
49 49
50 50 class TestInheritGlobalSettingsProperty(object):
51 51 def test_get_raises_exception_when_repository_not_specified(self):
52 52 model = VcsSettingsModel()
53 53 with pytest.raises(Exception) as exc_info:
54 54 model.inherit_global_settings
55 55 assert str(exc_info.value) == 'Repository is not specified'
56 56
57 57 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
58 58 model = VcsSettingsModel(repo=repo_stub.repo_name)
59 59 assert model.inherit_global_settings is True
60 60
61 61 def test_value_is_returned(self, repo_stub, settings_util):
62 62 model = VcsSettingsModel(repo=repo_stub.repo_name)
63 63 settings_util.create_repo_rhodecode_setting(
64 64 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
65 65 assert model.inherit_global_settings is False
66 66
67 67 def test_value_is_set(self, repo_stub):
68 68 model = VcsSettingsModel(repo=repo_stub.repo_name)
69 69 model.inherit_global_settings = False
70 70 setting = model.repo_settings.get_setting_by_name(
71 71 VcsSettingsModel.INHERIT_SETTINGS)
72 72 try:
73 73 assert setting.app_settings_type == 'bool'
74 74 assert setting.app_settings_value is False
75 75 finally:
76 76 Session().delete(setting)
77 77 Session().commit()
78 78
79 79 def test_set_raises_exception_when_repository_not_specified(self):
80 80 model = VcsSettingsModel()
81 81 with pytest.raises(Exception) as exc_info:
82 82 model.inherit_global_settings = False
83 83 assert str(exc_info.value) == 'Repository is not specified'
84 84
85 85
86 86 class TestVcsSettingsModel(object):
87 87 def test_global_svn_branch_patterns(self):
88 88 model = VcsSettingsModel()
89 89 expected_result = {'test': 'test'}
90 90 with mock.patch.object(model, 'global_settings') as settings_mock:
91 91 get_settings = settings_mock.get_ui_by_section
92 92 get_settings.return_value = expected_result
93 93 settings_mock.return_value = expected_result
94 94 result = model.get_global_svn_branch_patterns()
95 95
96 96 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
97 97 assert expected_result == result
98 98
99 99 def test_repo_svn_branch_patterns(self):
100 100 model = VcsSettingsModel()
101 101 expected_result = {'test': 'test'}
102 102 with mock.patch.object(model, 'repo_settings') as settings_mock:
103 103 get_settings = settings_mock.get_ui_by_section
104 104 get_settings.return_value = expected_result
105 105 settings_mock.return_value = expected_result
106 106 result = model.get_repo_svn_branch_patterns()
107 107
108 108 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
109 109 assert expected_result == result
110 110
111 111 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
112 112 self):
113 113 model = VcsSettingsModel()
114 114 with pytest.raises(Exception) as exc_info:
115 115 model.get_repo_svn_branch_patterns()
116 116 assert str(exc_info.value) == 'Repository is not specified'
117 117
118 118 def test_global_svn_tag_patterns(self):
119 119 model = VcsSettingsModel()
120 120 expected_result = {'test': 'test'}
121 121 with mock.patch.object(model, 'global_settings') as settings_mock:
122 122 get_settings = settings_mock.get_ui_by_section
123 123 get_settings.return_value = expected_result
124 124 settings_mock.return_value = expected_result
125 125 result = model.get_global_svn_tag_patterns()
126 126
127 127 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
128 128 assert expected_result == result
129 129
130 130 def test_repo_svn_tag_patterns(self):
131 131 model = VcsSettingsModel()
132 132 expected_result = {'test': 'test'}
133 133 with mock.patch.object(model, 'repo_settings') as settings_mock:
134 134 get_settings = settings_mock.get_ui_by_section
135 135 get_settings.return_value = expected_result
136 136 settings_mock.return_value = expected_result
137 137 result = model.get_repo_svn_tag_patterns()
138 138
139 139 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
140 140 assert expected_result == result
141 141
142 142 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
143 143 model = VcsSettingsModel()
144 144 with pytest.raises(Exception) as exc_info:
145 145 model.get_repo_svn_tag_patterns()
146 146 assert str(exc_info.value) == 'Repository is not specified'
147 147
148 148 def test_get_global_settings(self):
149 149 expected_result = {'test': 'test'}
150 150 model = VcsSettingsModel()
151 151 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
152 152 collect_mock.return_value = expected_result
153 153 result = model.get_global_settings()
154 154
155 155 collect_mock.assert_called_once_with(global_=True)
156 156 assert result == expected_result
157 157
158 158 def test_get_repo_settings(self, repo_stub):
159 159 model = VcsSettingsModel(repo=repo_stub.repo_name)
160 160 expected_result = {'test': 'test'}
161 161 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
162 162 collect_mock.return_value = expected_result
163 163 result = model.get_repo_settings()
164 164
165 165 collect_mock.assert_called_once_with(global_=False)
166 166 assert result == expected_result
167 167
168 168 @pytest.mark.parametrize('settings, global_', [
169 169 ('global_settings', True),
170 170 ('repo_settings', False)
171 171 ])
172 172 def test_collect_all_settings(self, settings, global_):
173 173 model = VcsSettingsModel()
174 174 result_mock = self._mock_result()
175 175
176 176 settings_patch = mock.patch.object(model, settings)
177 177 with settings_patch as settings_mock:
178 178 settings_mock.get_ui_by_section_and_key.return_value = result_mock
179 179 settings_mock.get_setting_by_name.return_value = result_mock
180 180 result = model._collect_all_settings(global_=global_)
181 181
182 182 ui_settings = model.HG_SETTINGS + model.GIT_SETTINGS + model.HOOKS_SETTINGS
183 183 self._assert_get_settings_calls(
184 184 settings_mock, ui_settings, model.GENERAL_SETTINGS)
185 185 self._assert_collect_all_settings_result(
186 186 ui_settings, model.GENERAL_SETTINGS, result)
187 187
188 188 @pytest.mark.parametrize('settings, global_', [
189 189 ('global_settings', True),
190 190 ('repo_settings', False)
191 191 ])
192 192 def test_collect_all_settings_without_empty_value(self, settings, global_):
193 193 model = VcsSettingsModel()
194 194
195 195 settings_patch = mock.patch.object(model, settings)
196 196 with settings_patch as settings_mock:
197 197 settings_mock.get_ui_by_section_and_key.return_value = None
198 198 settings_mock.get_setting_by_name.return_value = None
199 199 result = model._collect_all_settings(global_=global_)
200 200
201 201 assert result == {}
202 202
203 203 def _mock_result(self):
204 204 result_mock = mock.Mock()
205 205 result_mock.ui_value = 'ui_value'
206 206 result_mock.ui_active = True
207 207 result_mock.app_settings_value = 'setting_value'
208 208 return result_mock
209 209
210 210 def _assert_get_settings_calls(
211 211 self, settings_mock, ui_settings, general_settings):
212 212 assert (
213 213 settings_mock.get_ui_by_section_and_key.call_count ==
214 214 len(ui_settings))
215 215 assert (
216 216 settings_mock.get_setting_by_name.call_count ==
217 217 len(general_settings))
218 218
219 219 for section, key in ui_settings:
220 220 expected_call = mock.call(section, key)
221 221 assert (
222 222 expected_call in
223 223 settings_mock.get_ui_by_section_and_key.call_args_list)
224 224
225 225 for name in general_settings:
226 226 expected_call = mock.call(name)
227 227 assert (
228 228 expected_call in
229 229 settings_mock.get_setting_by_name.call_args_list)
230 230
231 231 def _assert_collect_all_settings_result(
232 232 self, ui_settings, general_settings, result):
233 233 expected_result = {}
234 234 for section, key in ui_settings:
235 235 key = '{}_{}'.format(section, key.replace('.', '_'))
236 236
237 237 if section in ('extensions', 'hooks'):
238 238 value = True
239 239 elif key in ['vcs_git_lfs_enabled']:
240 240 value = True
241 241 else:
242 242 value = 'ui_value'
243 243 expected_result[key] = value
244 244
245 245 for name in general_settings:
246 246 key = 'rhodecode_' + name
247 247 expected_result[key] = 'setting_value'
248 248
249 249 assert expected_result == result
250 250
251 251
252 252 class TestCreateOrUpdateRepoHookSettings(object):
253 253 def test_create_when_no_repo_object_found(self, repo_stub):
254 254 model = VcsSettingsModel(repo=repo_stub.repo_name)
255 255
256 256 self._create_settings(model, HOOKS_FORM_DATA)
257 257
258 258 cleanup = []
259 259 try:
260 260 for section, key in model.HOOKS_SETTINGS:
261 261 ui = model.repo_settings.get_ui_by_section_and_key(
262 262 section, key)
263 263 assert ui.ui_active is True
264 264 cleanup.append(ui)
265 265 finally:
266 266 for ui in cleanup:
267 267 Session().delete(ui)
268 268 Session().commit()
269 269
270 270 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
271 271 model = VcsSettingsModel(repo=repo_stub.repo_name)
272 272
273 273 deleted_key = 'hooks_changegroup_repo_size'
274 274 data = HOOKS_FORM_DATA.copy()
275 275 data.pop(deleted_key)
276 276
277 277 with pytest.raises(ValueError) as exc_info:
278 278 model.create_or_update_repo_hook_settings(data)
279 279 Session().commit()
280 280
281 281 msg = 'The given data does not contain {} key'.format(deleted_key)
282 282 assert str(exc_info.value) == msg
283 283
284 284 def test_update_when_repo_object_found(self, repo_stub, settings_util):
285 285 model = VcsSettingsModel(repo=repo_stub.repo_name)
286 286 for section, key in model.HOOKS_SETTINGS:
287 287 settings_util.create_repo_rhodecode_ui(
288 288 repo_stub, section, None, key=key, active=False)
289 289 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
290 290 Session().commit()
291 291
292 292 for section, key in model.HOOKS_SETTINGS:
293 293 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
294 294 assert ui.ui_active is True
295 295
296 296 def _create_settings(self, model, data):
297 297 global_patch = mock.patch.object(model, 'global_settings')
298 298 global_setting = mock.Mock()
299 299 global_setting.ui_value = 'Test value'
300 300 with global_patch as global_mock:
301 301 global_mock.get_ui_by_section_and_key.return_value = global_setting
302 302 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
303 303 Session().commit()
304 304
305 305
306 306 class TestUpdateGlobalHookSettings(object):
307 307 def test_update_raises_exception_when_data_incomplete(self):
308 308 model = VcsSettingsModel()
309 309
310 310 deleted_key = 'hooks_changegroup_repo_size'
311 311 data = HOOKS_FORM_DATA.copy()
312 312 data.pop(deleted_key)
313 313
314 314 with pytest.raises(ValueError) as exc_info:
315 315 model.update_global_hook_settings(data)
316 316 Session().commit()
317 317
318 318 msg = 'The given data does not contain {} key'.format(deleted_key)
319 319 assert str(exc_info.value) == msg
320 320
321 321 def test_update_global_hook_settings(self, settings_util):
322 322 model = VcsSettingsModel()
323 323 setting_mock = mock.MagicMock()
324 324 setting_mock.ui_active = False
325 325 get_settings_patcher = mock.patch.object(
326 326 model.global_settings, 'get_ui_by_section_and_key',
327 327 return_value=setting_mock)
328 328 session_patcher = mock.patch('rhodecode.model.settings.Session')
329 329 with get_settings_patcher as get_settings_mock, session_patcher:
330 330 model.update_global_hook_settings(HOOKS_FORM_DATA)
331 331 Session().commit()
332 332
333 333 assert setting_mock.ui_active is True
334 334 assert get_settings_mock.call_count == 3
335 335
336 336
337 337 class TestCreateOrUpdateRepoGeneralSettings(object):
338 338 def test_calls_create_or_update_general_settings(self, repo_stub):
339 339 model = VcsSettingsModel(repo=repo_stub.repo_name)
340 340 create_patch = mock.patch.object(
341 341 model, '_create_or_update_general_settings')
342 342 with create_patch as create_mock:
343 343 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
344 344 Session().commit()
345 345
346 346 create_mock.assert_called_once_with(
347 347 model.repo_settings, GENERAL_FORM_DATA)
348 348
349 349 def test_raises_exception_when_repository_is_not_specified(self):
350 350 model = VcsSettingsModel()
351 351 with pytest.raises(Exception) as exc_info:
352 352 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
353 353 assert str(exc_info.value) == 'Repository is not specified'
354 354
355 355
356 356 class TestCreateOrUpdatGlobalGeneralSettings(object):
357 357 def test_calls_create_or_update_general_settings(self):
358 358 model = VcsSettingsModel()
359 359 create_patch = mock.patch.object(
360 360 model, '_create_or_update_general_settings')
361 361 with create_patch as create_mock:
362 362 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
363 363 create_mock.assert_called_once_with(
364 364 model.global_settings, GENERAL_FORM_DATA)
365 365
366 366
367 367 class TestCreateOrUpdateGeneralSettings(object):
368 368 def test_create_when_no_repo_settings_found(self, repo_stub):
369 369 model = VcsSettingsModel(repo=repo_stub.repo_name)
370 370 model._create_or_update_general_settings(
371 371 model.repo_settings, GENERAL_FORM_DATA)
372 372
373 373 cleanup = []
374 374 try:
375 375 for name in model.GENERAL_SETTINGS:
376 376 setting = model.repo_settings.get_setting_by_name(name)
377 377 assert setting.app_settings_value is True
378 378 cleanup.append(setting)
379 379 finally:
380 380 for setting in cleanup:
381 381 Session().delete(setting)
382 382 Session().commit()
383 383
384 384 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
385 385 model = VcsSettingsModel(repo=repo_stub.repo_name)
386 386
387 387 deleted_key = 'rhodecode_pr_merge_enabled'
388 388 data = GENERAL_FORM_DATA.copy()
389 389 data.pop(deleted_key)
390 390
391 391 with pytest.raises(ValueError) as exc_info:
392 392 model._create_or_update_general_settings(model.repo_settings, data)
393 393 Session().commit()
394 394
395 395 msg = 'The given data does not contain {} key'.format(deleted_key)
396 396 assert str(exc_info.value) == msg
397 397
398 398 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
399 399 model = VcsSettingsModel(repo=repo_stub.repo_name)
400 400 for name in model.GENERAL_SETTINGS:
401 401 settings_util.create_repo_rhodecode_setting(
402 402 repo_stub, name, False, 'bool')
403 403
404 404 model._create_or_update_general_settings(
405 405 model.repo_settings, GENERAL_FORM_DATA)
406 406 Session().commit()
407 407
408 408 for name in model.GENERAL_SETTINGS:
409 409 setting = model.repo_settings.get_setting_by_name(name)
410 410 assert setting.app_settings_value is True
411 411
412 412
413 413 class TestCreateRepoSvnSettings(object):
414 414 def test_calls_create_svn_settings(self, repo_stub):
415 415 model = VcsSettingsModel(repo=repo_stub.repo_name)
416 416 with mock.patch.object(model, '_create_svn_settings') as create_mock:
417 417 model.create_repo_svn_settings(SVN_FORM_DATA)
418 418 Session().commit()
419 419
420 420 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
421 421
422 422 def test_raises_exception_when_repository_is_not_specified(self):
423 423 model = VcsSettingsModel()
424 424 with pytest.raises(Exception) as exc_info:
425 425 model.create_repo_svn_settings(SVN_FORM_DATA)
426 426 Session().commit()
427 427
428 428 assert str(exc_info.value) == 'Repository is not specified'
429 429
430 430
431 431 class TestCreateSvnSettings(object):
432 432 def test_create(self, repo_stub):
433 433 model = VcsSettingsModel(repo=repo_stub.repo_name)
434 434 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
435 435 Session().commit()
436 436
437 437 branch_ui = model.repo_settings.get_ui_by_section(
438 438 model.SVN_BRANCH_SECTION)
439 439 tag_ui = model.repo_settings.get_ui_by_section(
440 440 model.SVN_TAG_SECTION)
441 441
442 442 try:
443 443 assert len(branch_ui) == 1
444 444 assert len(tag_ui) == 1
445 445 finally:
446 446 Session().delete(branch_ui[0])
447 447 Session().delete(tag_ui[0])
448 448 Session().commit()
449 449
450 450 def test_create_tag(self, repo_stub):
451 451 model = VcsSettingsModel(repo=repo_stub.repo_name)
452 452 data = SVN_FORM_DATA.copy()
453 453 data.pop('new_svn_branch')
454 454 model._create_svn_settings(model.repo_settings, data)
455 455 Session().commit()
456 456
457 457 branch_ui = model.repo_settings.get_ui_by_section(
458 458 model.SVN_BRANCH_SECTION)
459 459 tag_ui = model.repo_settings.get_ui_by_section(
460 460 model.SVN_TAG_SECTION)
461 461
462 462 try:
463 463 assert len(branch_ui) == 0
464 464 assert len(tag_ui) == 1
465 465 finally:
466 466 Session().delete(tag_ui[0])
467 467 Session().commit()
468 468
469 469 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
470 470 model = VcsSettingsModel(repo=repo_stub.repo_name)
471 471 model._create_svn_settings(model.repo_settings, {})
472 472 Session().commit()
473 473
474 474 branch_ui = model.repo_settings.get_ui_by_section(
475 475 model.SVN_BRANCH_SECTION)
476 476 tag_ui = model.repo_settings.get_ui_by_section(
477 477 model.SVN_TAG_SECTION)
478 478
479 479 assert len(branch_ui) == 0
480 480 assert len(tag_ui) == 0
481 481
482 482 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
483 483 model = VcsSettingsModel(repo=repo_stub.repo_name)
484 484 data = {
485 485 'new_svn_branch': '',
486 486 'new_svn_tag': ''
487 487 }
488 488 model._create_svn_settings(model.repo_settings, data)
489 489 Session().commit()
490 490
491 491 branch_ui = model.repo_settings.get_ui_by_section(
492 492 model.SVN_BRANCH_SECTION)
493 493 tag_ui = model.repo_settings.get_ui_by_section(
494 494 model.SVN_TAG_SECTION)
495 495
496 496 assert len(branch_ui) == 0
497 497 assert len(tag_ui) == 0
498 498
499 499
500 500 class TestCreateOrUpdateUi(object):
501 501 def test_create(self, repo_stub):
502 502 model = VcsSettingsModel(repo=repo_stub.repo_name)
503 503 model._create_or_update_ui(
504 504 model.repo_settings, 'test-section', 'test-key', active=False,
505 505 value='False')
506 506 Session().commit()
507 507
508 508 created_ui = model.repo_settings.get_ui_by_section_and_key(
509 509 'test-section', 'test-key')
510 510
511 511 try:
512 512 assert created_ui.ui_active is False
513 513 assert str2bool(created_ui.ui_value) is False
514 514 finally:
515 515 Session().delete(created_ui)
516 516 Session().commit()
517 517
518 518 def test_update(self, repo_stub, settings_util):
519 519 model = VcsSettingsModel(repo=repo_stub.repo_name)
520 520 # care about only 3 first settings
521 521 largefiles, phases, evolve = model.HG_SETTINGS[:3]
522 522
523 523 section = 'test-section'
524 524 key = 'test-key'
525 525 settings_util.create_repo_rhodecode_ui(
526 526 repo_stub, section, 'True', key=key, active=True)
527 527
528 528 model._create_or_update_ui(
529 529 model.repo_settings, section, key, active=False, value='False')
530 530 Session().commit()
531 531
532 532 created_ui = model.repo_settings.get_ui_by_section_and_key(
533 533 section, key)
534 534 assert created_ui.ui_active is False
535 535 assert str2bool(created_ui.ui_value) is False
536 536
537 537
538 538 class TestCreateOrUpdateRepoHgSettings(object):
539 539 FORM_DATA = {
540 540 'extensions_largefiles': False,
541 541 'extensions_evolve': False,
542 542 'phases_publish': False
543 543 }
544 544
545 545 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
546 546 model = VcsSettingsModel(repo=repo_stub.repo_name)
547 547 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
548 548 model.create_or_update_repo_hg_settings(self.FORM_DATA)
549 549 expected_calls = [
550 550 mock.call(model.repo_settings, 'extensions', 'largefiles', active=False, value=''),
551 551 mock.call(model.repo_settings, 'extensions', 'evolve', active=False, value=''),
552 552 mock.call(model.repo_settings, 'experimental', 'evolution', active=False, value=''),
553 553 mock.call(model.repo_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
554 554 mock.call(model.repo_settings, 'extensions', 'topic', active=False, value=''),
555 555 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
556 556 ]
557 557 assert expected_calls == create_mock.call_args_list
558 558
559 559 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
560 560 def test_key_is_not_found(self, repo_stub, field_to_remove):
561 561 model = VcsSettingsModel(repo=repo_stub.repo_name)
562 562 data = self.FORM_DATA.copy()
563 563 data.pop(field_to_remove)
564 564 with pytest.raises(ValueError) as exc_info:
565 565 model.create_or_update_repo_hg_settings(data)
566 566 Session().commit()
567 567
568 568 expected_message = 'The given data does not contain {} key'.format(
569 569 field_to_remove)
570 570 assert str(exc_info.value) == expected_message
571 571
572 572 def test_create_raises_exception_when_repository_not_specified(self):
573 573 model = VcsSettingsModel()
574 574 with pytest.raises(Exception) as exc_info:
575 575 model.create_or_update_repo_hg_settings(self.FORM_DATA)
576 576 Session().commit()
577 577
578 578 assert str(exc_info.value) == 'Repository is not specified'
579 579
580 580
581 581 class TestUpdateGlobalSslSetting(object):
582 582 def test_updates_global_hg_settings(self):
583 583 model = VcsSettingsModel()
584 584 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
585 585 model.update_global_ssl_setting('False')
586 586 Session().commit()
587 587
588 588 create_mock.assert_called_once_with(
589 589 model.global_settings, 'web', 'push_ssl', value='False')
590 590
591 591
592 592 class TestCreateOrUpdateGlobalHgSettings(object):
593 593 FORM_DATA = {
594 594 'extensions_largefiles': False,
595 'largefiles_usercache': '/example/largefiles-store',
596 595 'phases_publish': False,
597 596 'extensions_evolve': False
598 597 }
599 598
600 599 def test_creates_repo_hg_settings_when_data_is_correct(self):
601 600 model = VcsSettingsModel()
602 601 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
603 602 model.create_or_update_global_hg_settings(self.FORM_DATA)
604 603 Session().commit()
605 604
606 605 expected_calls = [
607 606 mock.call(model.global_settings, 'extensions', 'largefiles', active=False, value=''),
608 mock.call(model.global_settings, 'largefiles', 'usercache', value='/example/largefiles-store'),
609 607 mock.call(model.global_settings, 'phases', 'publish', value='False'),
610 608 mock.call(model.global_settings, 'extensions', 'evolve', active=False, value=''),
611 609 mock.call(model.global_settings, 'experimental', 'evolution', active=False, value=''),
612 610 mock.call(model.global_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
613 611 mock.call(model.global_settings, 'extensions', 'topic', active=False, value=''),
614 612 ]
615 613
616 614 assert expected_calls == create_mock.call_args_list
617 615
618 616 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
619 617 def test_key_is_not_found(self, repo_stub, field_to_remove):
620 618 model = VcsSettingsModel(repo=repo_stub.repo_name)
621 619 data = self.FORM_DATA.copy()
622 620 data.pop(field_to_remove)
623 621 with pytest.raises(Exception) as exc_info:
624 622 model.create_or_update_global_hg_settings(data)
625 623 Session().commit()
626 624
627 625 expected_message = 'The given data does not contain {} key'.format(
628 626 field_to_remove)
629 627 assert str(exc_info.value) == expected_message
630 628
631 629
632 630 class TestCreateOrUpdateGlobalGitSettings(object):
633 631 FORM_DATA = {
634 632 'vcs_git_lfs_enabled': False,
635 'vcs_git_lfs_store_location': '/example/lfs-store',
636 633 }
637 634
638 635 def test_creates_repo_hg_settings_when_data_is_correct(self):
639 636 model = VcsSettingsModel()
640 637 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
641 638 model.create_or_update_global_git_settings(self.FORM_DATA)
642 639 Session().commit()
643 640
644 641 expected_calls = [
645 642 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled', active=False, value=False),
646 mock.call(model.global_settings, 'vcs_git_lfs', 'store_location', value='/example/lfs-store'),
647 643 ]
648 644 assert expected_calls == create_mock.call_args_list
649 645
650 646
651 647 class TestDeleteRepoSvnPattern(object):
652 648 def test_success_when_repo_is_set(self, backend_svn, settings_util):
653 649 repo = backend_svn.create_repo()
654 650 repo_name = repo.repo_name
655 651
656 652 model = VcsSettingsModel(repo=repo_name)
657 653 entry = settings_util.create_repo_rhodecode_ui(
658 654 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
659 655 Session().commit()
660 656
661 657 model.delete_repo_svn_pattern(entry.ui_id)
662 658
663 659 def test_fail_when_delete_id_from_other_repo(self, backend_svn):
664 660 repo_name = backend_svn.repo_name
665 661 model = VcsSettingsModel(repo=repo_name)
666 662 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
667 663 with delete_ui_patch as delete_ui_mock:
668 664 model.delete_repo_svn_pattern(123)
669 665 Session().commit()
670 666
671 667 delete_ui_mock.assert_called_once_with(-1)
672 668
673 669 def test_raises_exception_when_repository_is_not_specified(self):
674 670 model = VcsSettingsModel()
675 671 with pytest.raises(Exception) as exc_info:
676 672 model.delete_repo_svn_pattern(123)
677 673 assert str(exc_info.value) == 'Repository is not specified'
678 674
679 675
680 676 class TestDeleteGlobalSvnPattern(object):
681 677 def test_delete_global_svn_pattern_calls_delete_ui(self):
682 678 model = VcsSettingsModel()
683 679 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
684 680 with delete_ui_patch as delete_ui_mock:
685 681 model.delete_global_svn_pattern(123)
686 682 delete_ui_mock.assert_called_once_with(123)
687 683
688 684
689 685 class TestFilterUiSettings(object):
690 686 def test_settings_are_filtered(self):
691 687 model = VcsSettingsModel()
692 688 repo_settings = [
693 689 UiSetting('extensions', 'largefiles', '', True),
694 690 UiSetting('phases', 'publish', 'True', True),
695 691 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
696 692 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
697 693 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
698 694 UiSetting(
699 695 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
700 696 'test_branch', True),
701 697 UiSetting(
702 698 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
703 699 'test_tag', True),
704 700 ]
705 701 non_repo_settings = [
706 702 UiSetting('largefiles', 'usercache', '/example/largefiles-store', True),
707 703 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
708 704 UiSetting('hooks', 'test2', 'hook', True),
709 705 UiSetting(
710 706 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
711 707 'test_tag', True),
712 708 ]
713 709 settings = repo_settings + non_repo_settings
714 710 filtered_settings = model._filter_ui_settings(settings)
715 711 assert sorted(filtered_settings) == sorted(repo_settings)
716 712
717 713
718 714 class TestFilterGeneralSettings(object):
719 715 def test_settings_are_filtered(self):
720 716 model = VcsSettingsModel()
721 717 settings = {
722 718 'rhodecode_abcde': 'value1',
723 719 'rhodecode_vwxyz': 'value2',
724 720 }
725 721 general_settings = {
726 722 'rhodecode_{}'.format(key): 'value'
727 723 for key in VcsSettingsModel.GENERAL_SETTINGS
728 724 }
729 725 settings.update(general_settings)
730 726
731 727 filtered_settings = model._filter_general_settings(general_settings)
732 728 assert sorted(filtered_settings) == sorted(general_settings)
733 729
734 730
735 731 class TestGetRepoUiSettings(object):
736 732 def test_global_uis_are_returned_when_no_repo_uis_found(
737 733 self, repo_stub):
738 734 model = VcsSettingsModel(repo=repo_stub.repo_name)
739 735 result = model.get_repo_ui_settings()
740 736 svn_sections = (
741 737 VcsSettingsModel.SVN_TAG_SECTION,
742 738 VcsSettingsModel.SVN_BRANCH_SECTION)
743 739 expected_result = [
744 740 s for s in model.global_settings.get_ui()
745 741 if s.section not in svn_sections]
746 742 assert sorted(result) == sorted(expected_result)
747 743
748 744 def test_repo_uis_are_overriding_global_uis(
749 745 self, repo_stub, settings_util):
750 746 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
751 747 settings_util.create_repo_rhodecode_ui(
752 748 repo_stub, section, 'repo', key=key, active=False)
753 749 model = VcsSettingsModel(repo=repo_stub.repo_name)
754 750 result = model.get_repo_ui_settings()
755 751 for setting in result:
756 752 locator = (setting.section, setting.key)
757 753 if locator in VcsSettingsModel.HOOKS_SETTINGS:
758 754 assert setting.value == 'repo'
759 755
760 756 assert setting.active is False
761 757
762 758 def test_global_svn_patterns_are_not_in_list(
763 759 self, repo_stub, settings_util):
764 760 svn_sections = (
765 761 VcsSettingsModel.SVN_TAG_SECTION,
766 762 VcsSettingsModel.SVN_BRANCH_SECTION)
767 763 for section in svn_sections:
768 764 settings_util.create_rhodecode_ui(
769 765 section, 'repo', key='deadbeef' + section, active=False)
770 766 Session().commit()
771 767
772 768 model = VcsSettingsModel(repo=repo_stub.repo_name)
773 769 result = model.get_repo_ui_settings()
774 770 for setting in result:
775 771 assert setting.section not in svn_sections
776 772
777 773 def test_repo_uis_filtered_by_section_are_returned(
778 774 self, repo_stub, settings_util):
779 775 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
780 776 settings_util.create_repo_rhodecode_ui(
781 777 repo_stub, section, 'repo', key=key, active=False)
782 778 model = VcsSettingsModel(repo=repo_stub.repo_name)
783 779 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
784 780 result = model.get_repo_ui_settings(section=section)
785 781 for setting in result:
786 782 assert setting.section == section
787 783
788 784 def test_repo_uis_filtered_by_key_are_returned(
789 785 self, repo_stub, settings_util):
790 786 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
791 787 settings_util.create_repo_rhodecode_ui(
792 788 repo_stub, section, 'repo', key=key, active=False)
793 789 model = VcsSettingsModel(repo=repo_stub.repo_name)
794 790 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
795 791 result = model.get_repo_ui_settings(key=key)
796 792 for setting in result:
797 793 assert setting.key == key
798 794
799 795 def test_raises_exception_when_repository_is_not_specified(self):
800 796 model = VcsSettingsModel()
801 797 with pytest.raises(Exception) as exc_info:
802 798 model.get_repo_ui_settings()
803 799 assert str(exc_info.value) == 'Repository is not specified'
804 800
805 801
806 802 class TestGetRepoGeneralSettings(object):
807 803 def test_global_settings_are_returned_when_no_repo_settings_found(
808 804 self, repo_stub):
809 805 model = VcsSettingsModel(repo=repo_stub.repo_name)
810 806 result = model.get_repo_general_settings()
811 807 expected_result = model.global_settings.get_all_settings()
812 808 assert sorted(result) == sorted(expected_result)
813 809
814 810 def test_repo_uis_are_overriding_global_uis(
815 811 self, repo_stub, settings_util):
816 812 for key in VcsSettingsModel.GENERAL_SETTINGS:
817 813 settings_util.create_repo_rhodecode_setting(
818 814 repo_stub, key, 'abcde', type_='unicode')
819 815 Session().commit()
820 816
821 817 model = VcsSettingsModel(repo=repo_stub.repo_name)
822 818 result = model.get_repo_ui_settings()
823 819 for key in result:
824 820 if key in VcsSettingsModel.GENERAL_SETTINGS:
825 821 assert result[key] == 'abcde'
826 822
827 823 def test_raises_exception_when_repository_is_not_specified(self):
828 824 model = VcsSettingsModel()
829 825 with pytest.raises(Exception) as exc_info:
830 826 model.get_repo_general_settings()
831 827 assert str(exc_info.value) == 'Repository is not specified'
832 828
833 829
834 830 class TestGetGlobalGeneralSettings(object):
835 831 def test_global_settings_are_returned(self, repo_stub):
836 832 model = VcsSettingsModel()
837 833 result = model.get_global_general_settings()
838 834 expected_result = model.global_settings.get_all_settings()
839 835 assert sorted(result) == sorted(expected_result)
840 836
841 837 def test_repo_uis_are_not_overriding_global_uis(
842 838 self, repo_stub, settings_util):
843 839 for key in VcsSettingsModel.GENERAL_SETTINGS:
844 840 settings_util.create_repo_rhodecode_setting(
845 841 repo_stub, key, 'abcde', type_='unicode')
846 842 Session().commit()
847 843
848 844 model = VcsSettingsModel(repo=repo_stub.repo_name)
849 845 result = model.get_global_general_settings()
850 846 expected_result = model.global_settings.get_all_settings()
851 847 assert sorted(result) == sorted(expected_result)
852 848
853 849
854 850 class TestGetGlobalUiSettings(object):
855 851 def test_global_uis_are_returned(self, repo_stub):
856 852 model = VcsSettingsModel()
857 853 result = model.get_global_ui_settings()
858 854 expected_result = model.global_settings.get_ui()
859 855 assert sorted(result) == sorted(expected_result)
860 856
861 857 def test_repo_uis_are_not_overriding_global_uis(
862 858 self, repo_stub, settings_util):
863 859 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
864 860 settings_util.create_repo_rhodecode_ui(
865 861 repo_stub, section, 'repo', key=key, active=False)
866 862 Session().commit()
867 863
868 864 model = VcsSettingsModel(repo=repo_stub.repo_name)
869 865 result = model.get_global_ui_settings()
870 866 expected_result = model.global_settings.get_ui()
871 867 assert sorted(result) == sorted(expected_result)
872 868
873 869 def test_ui_settings_filtered_by_section(
874 870 self, repo_stub, settings_util):
875 871 model = VcsSettingsModel(repo=repo_stub.repo_name)
876 872 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
877 873 result = model.get_global_ui_settings(section=section)
878 874 expected_result = model.global_settings.get_ui(section=section)
879 875 assert sorted(result) == sorted(expected_result)
880 876
881 877 def test_ui_settings_filtered_by_key(
882 878 self, repo_stub, settings_util):
883 879 model = VcsSettingsModel(repo=repo_stub.repo_name)
884 880 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
885 881 result = model.get_global_ui_settings(key=key)
886 882 expected_result = model.global_settings.get_ui(key=key)
887 883 assert sorted(result) == sorted(expected_result)
888 884
889 885
890 886 class TestGetGeneralSettings(object):
891 887 def test_global_settings_are_returned_when_inherited_is_true(
892 888 self, repo_stub, settings_util):
893 889 model = VcsSettingsModel(repo=repo_stub.repo_name)
894 890 model.inherit_global_settings = True
895 891 for key in VcsSettingsModel.GENERAL_SETTINGS:
896 892 settings_util.create_repo_rhodecode_setting(
897 893 repo_stub, key, 'abcde', type_='unicode')
898 894 Session().commit()
899 895
900 896 result = model.get_general_settings()
901 897 expected_result = model.get_global_general_settings()
902 898 assert sorted(result) == sorted(expected_result)
903 899
904 900 def test_repo_settings_are_returned_when_inherited_is_false(
905 901 self, repo_stub, settings_util):
906 902 model = VcsSettingsModel(repo=repo_stub.repo_name)
907 903 model.inherit_global_settings = False
908 904 for key in VcsSettingsModel.GENERAL_SETTINGS:
909 905 settings_util.create_repo_rhodecode_setting(
910 906 repo_stub, key, 'abcde', type_='unicode')
911 907 Session().commit()
912 908
913 909 result = model.get_general_settings()
914 910 expected_result = model.get_repo_general_settings()
915 911 assert sorted(result) == sorted(expected_result)
916 912
917 913 def test_global_settings_are_returned_when_no_repository_specified(self):
918 914 model = VcsSettingsModel()
919 915 result = model.get_general_settings()
920 916 expected_result = model.get_global_general_settings()
921 917 assert sorted(result) == sorted(expected_result)
922 918
923 919
924 920 class TestGetUiSettings(object):
925 921 def test_global_settings_are_returned_when_inherited_is_true(
926 922 self, repo_stub, settings_util):
927 923 model = VcsSettingsModel(repo=repo_stub.repo_name)
928 924 model.inherit_global_settings = True
929 925 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
930 926 settings_util.create_repo_rhodecode_ui(
931 927 repo_stub, section, 'repo', key=key, active=True)
932 928 Session().commit()
933 929
934 930 result = model.get_ui_settings()
935 931 expected_result = model.get_global_ui_settings()
936 932 assert sorted(result) == sorted(expected_result)
937 933
938 934 def test_repo_settings_are_returned_when_inherited_is_false(
939 935 self, repo_stub, settings_util):
940 936 model = VcsSettingsModel(repo=repo_stub.repo_name)
941 937 model.inherit_global_settings = False
942 938 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
943 939 settings_util.create_repo_rhodecode_ui(
944 940 repo_stub, section, 'repo', key=key, active=True)
945 941 Session().commit()
946 942
947 943 result = model.get_ui_settings()
948 944 expected_result = model.get_repo_ui_settings()
949 945 assert sorted(result) == sorted(expected_result)
950 946
951 947 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
952 948 model = VcsSettingsModel(repo=repo_stub.repo_name)
953 949 model.inherit_global_settings = False
954 950
955 951 args = ('section', 'key')
956 952 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
957 953 model.get_ui_settings(*args)
958 954 Session().commit()
959 955
960 956 settings_mock.assert_called_once_with(*args)
961 957
962 958 def test_global_settings_filtered_by_section_and_key(self):
963 959 model = VcsSettingsModel()
964 960 args = ('section', 'key')
965 961 with mock.patch.object(model, 'get_global_ui_settings') as (
966 962 settings_mock):
967 963 model.get_ui_settings(*args)
968 964 settings_mock.assert_called_once_with(*args)
969 965
970 966 def test_global_settings_are_returned_when_no_repository_specified(self):
971 967 model = VcsSettingsModel()
972 968 result = model.get_ui_settings()
973 969 expected_result = model.get_global_ui_settings()
974 970 assert sorted(result) == sorted(expected_result)
975 971
976 972
977 973 class TestGetSvnPatterns(object):
978 974 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
979 975 model = VcsSettingsModel(repo=repo_stub.repo_name)
980 976 args = ('section', )
981 977 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
982 978 model.get_svn_patterns(*args)
983 979
984 980 Session().commit()
985 981 settings_mock.assert_called_once_with(*args)
986 982
987 983 def test_global_settings_filtered_by_section_and_key(self):
988 984 model = VcsSettingsModel()
989 985 args = ('section', )
990 986 with mock.patch.object(model, 'get_global_ui_settings') as (
991 987 settings_mock):
992 988 model.get_svn_patterns(*args)
993 989 settings_mock.assert_called_once_with(*args)
994 990
995 991
996 992 class TestCreateOrUpdateRepoSettings(object):
997 993 FORM_DATA = {
998 994 'inherit_global_settings': False,
999 995 'hooks_changegroup_repo_size': False,
1000 996 'hooks_changegroup_push_logger': False,
1001 997 'hooks_outgoing_pull_logger': False,
1002 998 'extensions_largefiles': False,
1003 999 'extensions_evolve': False,
1004 'largefiles_usercache': '/example/largefiles-store',
1005 1000 'vcs_git_lfs_enabled': False,
1006 'vcs_git_lfs_store_location': '/',
1007 1001 'phases_publish': 'False',
1008 1002 'rhodecode_pr_merge_enabled': False,
1009 1003 'rhodecode_use_outdated_comments': False,
1010 1004 'new_svn_branch': '',
1011 1005 'new_svn_tag': ''
1012 1006 }
1013 1007
1014 1008 def test_get_raises_exception_when_repository_not_specified(self):
1015 1009 model = VcsSettingsModel()
1016 1010 with pytest.raises(Exception) as exc_info:
1017 1011 model.create_or_update_repo_settings(data=self.FORM_DATA)
1018 1012 Session().commit()
1019 1013
1020 1014 assert str(exc_info.value) == 'Repository is not specified'
1021 1015
1022 1016 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
1023 1017 repo = backend_svn.create_repo()
1024 1018 model = VcsSettingsModel(repo=repo)
1025 1019 with self._patch_model(model) as mocks:
1026 1020 model.create_or_update_repo_settings(
1027 1021 data=self.FORM_DATA, inherit_global_settings=False)
1028 1022 Session().commit()
1029 1023
1030 1024 mocks['create_repo_svn_settings'].assert_called_once_with(
1031 1025 self.FORM_DATA)
1032 1026 non_called_methods = (
1033 1027 'create_or_update_repo_hook_settings',
1034 1028 'create_or_update_repo_pr_settings',
1035 1029 'create_or_update_repo_hg_settings')
1036 1030 for method in non_called_methods:
1037 1031 assert mocks[method].call_count == 0
1038 1032
1039 1033 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
1040 1034 repo = backend_hg.create_repo()
1041 1035 model = VcsSettingsModel(repo=repo)
1042 1036 with self._patch_model(model) as mocks:
1043 1037 model.create_or_update_repo_settings(
1044 1038 data=self.FORM_DATA, inherit_global_settings=False)
1045 1039 Session().commit()
1046 1040
1047 1041 assert mocks['create_repo_svn_settings'].call_count == 0
1048 1042 called_methods = (
1049 1043 'create_or_update_repo_hook_settings',
1050 1044 'create_or_update_repo_pr_settings',
1051 1045 'create_or_update_repo_hg_settings')
1052 1046 for method in called_methods:
1053 1047 mocks[method].assert_called_once_with(self.FORM_DATA)
1054 1048
1055 1049 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
1056 1050 self, backend_git):
1057 1051 repo = backend_git.create_repo()
1058 1052 model = VcsSettingsModel(repo=repo)
1059 1053 with self._patch_model(model) as mocks:
1060 1054 model.create_or_update_repo_settings(
1061 1055 data=self.FORM_DATA, inherit_global_settings=False)
1062 1056
1063 1057 assert mocks['create_repo_svn_settings'].call_count == 0
1064 1058 called_methods = (
1065 1059 'create_or_update_repo_hook_settings',
1066 1060 'create_or_update_repo_pr_settings')
1067 1061 non_called_methods = (
1068 1062 'create_repo_svn_settings',
1069 1063 'create_or_update_repo_hg_settings'
1070 1064 )
1071 1065 for method in called_methods:
1072 1066 mocks[method].assert_called_once_with(self.FORM_DATA)
1073 1067 for method in non_called_methods:
1074 1068 assert mocks[method].call_count == 0
1075 1069
1076 1070 def test_no_methods_are_called_when_settings_are_inherited(
1077 1071 self, backend):
1078 1072 repo = backend.create_repo()
1079 1073 model = VcsSettingsModel(repo=repo)
1080 1074 with self._patch_model(model) as mocks:
1081 1075 model.create_or_update_repo_settings(
1082 1076 data=self.FORM_DATA, inherit_global_settings=True)
1083 1077 for method_name in mocks:
1084 1078 assert mocks[method_name].call_count == 0
1085 1079
1086 1080 def test_cache_is_marked_for_invalidation(self, repo_stub):
1087 1081 model = VcsSettingsModel(repo=repo_stub)
1088 1082 invalidation_patcher = mock.patch(
1089 1083 'rhodecode.model.scm.ScmModel.mark_for_invalidation')
1090 1084 with invalidation_patcher as invalidation_mock:
1091 1085 model.create_or_update_repo_settings(
1092 1086 data=self.FORM_DATA, inherit_global_settings=True)
1093 1087 Session().commit()
1094 1088
1095 1089 invalidation_mock.assert_called_once_with(
1096 1090 repo_stub.repo_name, delete=True)
1097 1091
1098 1092 def test_inherit_flag_is_saved(self, repo_stub):
1099 1093 model = VcsSettingsModel(repo=repo_stub)
1100 1094 model.inherit_global_settings = True
1101 1095 with self._patch_model(model):
1102 1096 model.create_or_update_repo_settings(
1103 1097 data=self.FORM_DATA, inherit_global_settings=False)
1104 1098 Session().commit()
1105 1099
1106 1100 assert model.inherit_global_settings is False
1107 1101
1108 1102 def _patch_model(self, model):
1109 1103 return mock.patch.multiple(
1110 1104 model,
1111 1105 create_repo_svn_settings=mock.DEFAULT,
1112 1106 create_or_update_repo_hook_settings=mock.DEFAULT,
1113 1107 create_or_update_repo_pr_settings=mock.DEFAULT,
1114 1108 create_or_update_repo_hg_settings=mock.DEFAULT)
General Comments 0
You need to be logged in to leave comments. Login now