##// END OF EJS Templates
svn: moving svn http support out of labs settings.
lisaq -
r748:0147d45d default
parent child Browse files
Show More
@@ -1,806 +1,793 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 settings controller for rhodecode admin
24 24 """
25 25
26 26 import collections
27 27 import logging
28 28 import urllib2
29 29
30 30 import datetime
31 31 import formencode
32 32 from formencode import htmlfill
33 33 import packaging.version
34 34 from pylons import request, tmpl_context as c, url, config
35 35 from pylons.controllers.util import redirect
36 36 from pylons.i18n.translation import _, lazy_ugettext
37 37 from webob.exc import HTTPBadRequest
38 38
39 39 import rhodecode
40 40 from rhodecode.admin.navigation import navigation_list
41 41 from rhodecode.lib import auth
42 42 from rhodecode.lib import helpers as h
43 43 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
44 44 from rhodecode.lib.base import BaseController, render
45 45 from rhodecode.lib.celerylib import tasks, run_task
46 46 from rhodecode.lib.utils import repo2db_mapper
47 47 from rhodecode.lib.utils2 import (
48 48 str2bool, safe_unicode, AttributeDict, safe_int)
49 49 from rhodecode.lib.compat import OrderedDict
50 50 from rhodecode.lib.ext_json import json
51 51 from rhodecode.lib.utils import jsonify
52 52
53 53 from rhodecode.model.db import RhodeCodeUi, Repository
54 54 from rhodecode.model.forms import ApplicationSettingsForm, \
55 55 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
56 56 LabsSettingsForm, IssueTrackerPatternsForm
57 57
58 58 from rhodecode.model.scm import ScmModel
59 59 from rhodecode.model.notification import EmailNotificationModel
60 60 from rhodecode.model.meta import Session
61 61 from rhodecode.model.settings import (
62 62 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
63 63 SettingsModel)
64 64
65 65 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
66 66
67 67
68 68 log = logging.getLogger(__name__)
69 69
70 70
71 71 class SettingsController(BaseController):
72 72 """REST Controller styled on the Atom Publishing Protocol"""
73 73 # To properly map this controller, ensure your config/routing.py
74 74 # file has a resource setup:
75 75 # map.resource('setting', 'settings', controller='admin/settings',
76 76 # path_prefix='/admin', name_prefix='admin_')
77 77
78 78 @LoginRequired()
79 79 def __before__(self):
80 80 super(SettingsController, self).__before__()
81 81 c.labs_active = str2bool(
82 82 rhodecode.CONFIG.get('labs_settings_active', 'true'))
83 83 c.navlist = navigation_list(request)
84 84
85 85 def _get_hg_ui_settings(self):
86 86 ret = RhodeCodeUi.query().all()
87 87
88 88 if not ret:
89 89 raise Exception('Could not get application ui settings !')
90 90 settings = {}
91 91 for each in ret:
92 92 k = each.ui_key
93 93 v = each.ui_value
94 94 if k == '/':
95 95 k = 'root_path'
96 96
97 97 if k in ['push_ssl', 'publish']:
98 98 v = str2bool(v)
99 99
100 100 if k.find('.') != -1:
101 101 k = k.replace('.', '_')
102 102
103 103 if each.ui_section in ['hooks', 'extensions']:
104 104 v = each.ui_active
105 105
106 106 settings[each.ui_section + '_' + k] = v
107 107 return settings
108 108
109 109 @HasPermissionAllDecorator('hg.admin')
110 110 @auth.CSRFRequired()
111 111 @jsonify
112 112 def delete_svn_pattern(self):
113 113 if not request.is_xhr:
114 114 raise HTTPBadRequest()
115 115
116 116 delete_pattern_id = request.POST.get('delete_svn_pattern')
117 117 model = VcsSettingsModel()
118 118 try:
119 119 model.delete_global_svn_pattern(delete_pattern_id)
120 120 except SettingNotFound:
121 121 raise HTTPBadRequest()
122 122
123 123 Session().commit()
124 124 return True
125 125
126 126 @HasPermissionAllDecorator('hg.admin')
127 127 @auth.CSRFRequired()
128 128 def settings_vcs_update(self):
129 129 """POST /admin/settings: All items in the collection"""
130 130 # url('admin_settings_vcs')
131 131 c.active = 'vcs'
132 132
133 133 model = VcsSettingsModel()
134 134 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
135 135 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
136 136
137 137 application_form = ApplicationUiSettingsForm()()
138 138 try:
139 139 form_result = application_form.to_python(dict(request.POST))
140 140 except formencode.Invalid as errors:
141 141 h.flash(
142 142 _("Some form inputs contain invalid data."),
143 143 category='error')
144 144 return htmlfill.render(
145 145 render('admin/settings/settings.html'),
146 146 defaults=errors.value,
147 147 errors=errors.error_dict or {},
148 148 prefix_error=False,
149 149 encoding="UTF-8",
150 150 force_defaults=False
151 151 )
152 152
153 153 try:
154 154 model.update_global_ssl_setting(form_result['web_push_ssl'])
155 155 if c.visual.allow_repo_location_change:
156 156 model.update_global_path_setting(
157 157 form_result['paths_root_path'])
158 158 model.update_global_hook_settings(form_result)
159 159 model.create_global_svn_settings(form_result)
160 160 model.create_or_update_global_hg_settings(form_result)
161 161 model.create_or_update_global_pr_settings(form_result)
162 162 except Exception:
163 163 log.exception("Exception while updating settings")
164 164 h.flash(_('Error occurred during updating '
165 165 'application settings'), category='error')
166 166 else:
167 167 Session().commit()
168 168 h.flash(_('Updated VCS settings'), category='success')
169 169 return redirect(url('admin_settings_vcs'))
170 170
171 171 return htmlfill.render(
172 172 render('admin/settings/settings.html'),
173 173 defaults=self._form_defaults(),
174 174 encoding="UTF-8",
175 175 force_defaults=False)
176 176
177 177 @HasPermissionAllDecorator('hg.admin')
178 178 def settings_vcs(self):
179 179 """GET /admin/settings: All items in the collection"""
180 180 # url('admin_settings_vcs')
181 181 c.active = 'vcs'
182 182 model = VcsSettingsModel()
183 183 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
184 184 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
185 185
186 186 return htmlfill.render(
187 187 render('admin/settings/settings.html'),
188 188 defaults=self._form_defaults(),
189 189 encoding="UTF-8",
190 190 force_defaults=False)
191 191
192 192 @HasPermissionAllDecorator('hg.admin')
193 193 @auth.CSRFRequired()
194 194 def settings_mapping_update(self):
195 195 """POST /admin/settings/mapping: All items in the collection"""
196 196 # url('admin_settings_mapping')
197 197 c.active = 'mapping'
198 198 rm_obsolete = request.POST.get('destroy', False)
199 199 invalidate_cache = request.POST.get('invalidate', False)
200 200 log.debug(
201 201 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
202 202
203 203 if invalidate_cache:
204 204 log.debug('invalidating all repositories cache')
205 205 for repo in Repository.get_all():
206 206 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
207 207
208 208 filesystem_repos = ScmModel().repo_scan()
209 209 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
210 210 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
211 211 h.flash(_('Repositories successfully '
212 212 'rescanned added: %s ; removed: %s') %
213 213 (_repr(added), _repr(removed)),
214 214 category='success')
215 215 return redirect(url('admin_settings_mapping'))
216 216
217 217 @HasPermissionAllDecorator('hg.admin')
218 218 def settings_mapping(self):
219 219 """GET /admin/settings/mapping: All items in the collection"""
220 220 # url('admin_settings_mapping')
221 221 c.active = 'mapping'
222 222
223 223 return htmlfill.render(
224 224 render('admin/settings/settings.html'),
225 225 defaults=self._form_defaults(),
226 226 encoding="UTF-8",
227 227 force_defaults=False)
228 228
229 229 @HasPermissionAllDecorator('hg.admin')
230 230 @auth.CSRFRequired()
231 231 def settings_global_update(self):
232 232 """POST /admin/settings/global: All items in the collection"""
233 233 # url('admin_settings_global')
234 234 c.active = 'global'
235 235 application_form = ApplicationSettingsForm()()
236 236 try:
237 237 form_result = application_form.to_python(dict(request.POST))
238 238 except formencode.Invalid as errors:
239 239 return htmlfill.render(
240 240 render('admin/settings/settings.html'),
241 241 defaults=errors.value,
242 242 errors=errors.error_dict or {},
243 243 prefix_error=False,
244 244 encoding="UTF-8",
245 245 force_defaults=False)
246 246
247 247 try:
248 248 settings = [
249 249 ('title', 'rhodecode_title'),
250 250 ('realm', 'rhodecode_realm'),
251 251 ('pre_code', 'rhodecode_pre_code'),
252 252 ('post_code', 'rhodecode_post_code'),
253 253 ('captcha_public_key', 'rhodecode_captcha_public_key'),
254 254 ('captcha_private_key', 'rhodecode_captcha_private_key'),
255 255 ]
256 256 for setting, form_key in settings:
257 257 sett = SettingsModel().create_or_update_setting(
258 258 setting, form_result[form_key])
259 259 Session().add(sett)
260 260
261 261 Session().commit()
262 262 SettingsModel().invalidate_settings_cache()
263 263 h.flash(_('Updated application settings'), category='success')
264 264 except Exception:
265 265 log.exception("Exception while updating application settings")
266 266 h.flash(
267 267 _('Error occurred during updating application settings'),
268 268 category='error')
269 269
270 270 return redirect(url('admin_settings_global'))
271 271
272 272 @HasPermissionAllDecorator('hg.admin')
273 273 def settings_global(self):
274 274 """GET /admin/settings/global: All items in the collection"""
275 275 # url('admin_settings_global')
276 276 c.active = 'global'
277 277
278 278 return htmlfill.render(
279 279 render('admin/settings/settings.html'),
280 280 defaults=self._form_defaults(),
281 281 encoding="UTF-8",
282 282 force_defaults=False)
283 283
284 284 @HasPermissionAllDecorator('hg.admin')
285 285 @auth.CSRFRequired()
286 286 def settings_visual_update(self):
287 287 """POST /admin/settings/visual: All items in the collection"""
288 288 # url('admin_settings_visual')
289 289 c.active = 'visual'
290 290 application_form = ApplicationVisualisationForm()()
291 291 try:
292 292 form_result = application_form.to_python(dict(request.POST))
293 293 except formencode.Invalid as errors:
294 294 return htmlfill.render(
295 295 render('admin/settings/settings.html'),
296 296 defaults=errors.value,
297 297 errors=errors.error_dict or {},
298 298 prefix_error=False,
299 299 encoding="UTF-8",
300 300 force_defaults=False
301 301 )
302 302
303 303 try:
304 304 settings = [
305 305 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
306 306 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
307 307 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
308 308 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
309 309 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
310 310 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
311 311 ('show_version', 'rhodecode_show_version', 'bool'),
312 312 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
313 313 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
314 314 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
315 315 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
316 316 ('support_url', 'rhodecode_support_url', 'unicode'),
317 317 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
318 318 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
319 319 ]
320 320 for setting, form_key, type_ in settings:
321 321 sett = SettingsModel().create_or_update_setting(
322 322 setting, form_result[form_key], type_)
323 323 Session().add(sett)
324 324
325 325 Session().commit()
326 326 SettingsModel().invalidate_settings_cache()
327 327 h.flash(_('Updated visualisation settings'), category='success')
328 328 except Exception:
329 329 log.exception("Exception updating visualization settings")
330 330 h.flash(_('Error occurred during updating '
331 331 'visualisation settings'),
332 332 category='error')
333 333
334 334 return redirect(url('admin_settings_visual'))
335 335
336 336 @HasPermissionAllDecorator('hg.admin')
337 337 def settings_visual(self):
338 338 """GET /admin/settings/visual: All items in the collection"""
339 339 # url('admin_settings_visual')
340 340 c.active = 'visual'
341 341
342 342 return htmlfill.render(
343 343 render('admin/settings/settings.html'),
344 344 defaults=self._form_defaults(),
345 345 encoding="UTF-8",
346 346 force_defaults=False)
347 347
348 348 @HasPermissionAllDecorator('hg.admin')
349 349 @auth.CSRFRequired()
350 350 def settings_issuetracker_test(self):
351 351 if request.is_xhr:
352 352 return h.urlify_commit_message(
353 353 request.POST.get('test_text', ''),
354 354 'repo_group/test_repo1')
355 355 else:
356 356 raise HTTPBadRequest()
357 357
358 358 @HasPermissionAllDecorator('hg.admin')
359 359 @auth.CSRFRequired()
360 360 def settings_issuetracker_delete(self):
361 361 uid = request.POST.get('uid')
362 362 IssueTrackerSettingsModel().delete_entries(uid)
363 363 h.flash(_('Removed issue tracker entry'), category='success')
364 364 return redirect(url('admin_settings_issuetracker'))
365 365
366 366 @HasPermissionAllDecorator('hg.admin')
367 367 def settings_issuetracker(self):
368 368 """GET /admin/settings/issue-tracker: All items in the collection"""
369 369 # url('admin_settings_issuetracker')
370 370 c.active = 'issuetracker'
371 371 defaults = SettingsModel().get_all_settings()
372 372
373 373 entry_key = 'rhodecode_issuetracker_pat_'
374 374
375 375 c.issuetracker_entries = {}
376 376 for k, v in defaults.items():
377 377 if k.startswith(entry_key):
378 378 uid = k[len(entry_key):]
379 379 c.issuetracker_entries[uid] = None
380 380
381 381 for uid in c.issuetracker_entries:
382 382 c.issuetracker_entries[uid] = AttributeDict({
383 383 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
384 384 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
385 385 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
386 386 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
387 387 })
388 388
389 389 return render('admin/settings/settings.html')
390 390
391 391 @HasPermissionAllDecorator('hg.admin')
392 392 @auth.CSRFRequired()
393 393 def settings_issuetracker_save(self):
394 394 settings_model = IssueTrackerSettingsModel()
395 395
396 396 form = IssueTrackerPatternsForm()().to_python(request.POST)
397 397 for uid in form['delete_patterns']:
398 398 settings_model.delete_entries(uid)
399 399
400 400 for pattern in form['patterns']:
401 401 for setting, value, type_ in pattern:
402 402 sett = settings_model.create_or_update_setting(
403 403 setting, value, type_)
404 404 Session().add(sett)
405 405
406 406 Session().commit()
407 407
408 408 SettingsModel().invalidate_settings_cache()
409 409 h.flash(_('Updated issue tracker entries'), category='success')
410 410 return redirect(url('admin_settings_issuetracker'))
411 411
412 412 @HasPermissionAllDecorator('hg.admin')
413 413 @auth.CSRFRequired()
414 414 def settings_email_update(self):
415 415 """POST /admin/settings/email: All items in the collection"""
416 416 # url('admin_settings_email')
417 417 c.active = 'email'
418 418
419 419 test_email = request.POST.get('test_email')
420 420
421 421 if not test_email:
422 422 h.flash(_('Please enter email address'), category='error')
423 423 return redirect(url('admin_settings_email'))
424 424
425 425 email_kwargs = {
426 426 'date': datetime.datetime.now(),
427 427 'user': c.rhodecode_user,
428 428 'rhodecode_version': c.rhodecode_version
429 429 }
430 430
431 431 (subject, headers, email_body,
432 432 email_body_plaintext) = EmailNotificationModel().render_email(
433 433 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
434 434
435 435 recipients = [test_email] if test_email else None
436 436
437 437 run_task(tasks.send_email, recipients, subject,
438 438 email_body_plaintext, email_body)
439 439
440 440 h.flash(_('Send email task created'), category='success')
441 441 return redirect(url('admin_settings_email'))
442 442
443 443 @HasPermissionAllDecorator('hg.admin')
444 444 def settings_email(self):
445 445 """GET /admin/settings/email: All items in the collection"""
446 446 # url('admin_settings_email')
447 447 c.active = 'email'
448 448 c.rhodecode_ini = rhodecode.CONFIG
449 449
450 450 return htmlfill.render(
451 451 render('admin/settings/settings.html'),
452 452 defaults=self._form_defaults(),
453 453 encoding="UTF-8",
454 454 force_defaults=False)
455 455
456 456 @HasPermissionAllDecorator('hg.admin')
457 457 @auth.CSRFRequired()
458 458 def settings_hooks_update(self):
459 459 """POST or DELETE /admin/settings/hooks: All items in the collection"""
460 460 # url('admin_settings_hooks')
461 461 c.active = 'hooks'
462 462 if c.visual.allow_custom_hooks_settings:
463 463 ui_key = request.POST.get('new_hook_ui_key')
464 464 ui_value = request.POST.get('new_hook_ui_value')
465 465
466 466 hook_id = request.POST.get('hook_id')
467 467 new_hook = False
468 468
469 469 model = SettingsModel()
470 470 try:
471 471 if ui_value and ui_key:
472 472 model.create_or_update_hook(ui_key, ui_value)
473 473 h.flash(_('Added new hook'), category='success')
474 474 new_hook = True
475 475 elif hook_id:
476 476 RhodeCodeUi.delete(hook_id)
477 477 Session().commit()
478 478
479 479 # check for edits
480 480 update = False
481 481 _d = request.POST.dict_of_lists()
482 482 for k, v in zip(_d.get('hook_ui_key', []),
483 483 _d.get('hook_ui_value_new', [])):
484 484 model.create_or_update_hook(k, v)
485 485 update = True
486 486
487 487 if update and not new_hook:
488 488 h.flash(_('Updated hooks'), category='success')
489 489 Session().commit()
490 490 except Exception:
491 491 log.exception("Exception during hook creation")
492 492 h.flash(_('Error occurred during hook creation'),
493 493 category='error')
494 494
495 495 return redirect(url('admin_settings_hooks'))
496 496
497 497 @HasPermissionAllDecorator('hg.admin')
498 498 def settings_hooks(self):
499 499 """GET /admin/settings/hooks: All items in the collection"""
500 500 # url('admin_settings_hooks')
501 501 c.active = 'hooks'
502 502
503 503 model = SettingsModel()
504 504 c.hooks = model.get_builtin_hooks()
505 505 c.custom_hooks = model.get_custom_hooks()
506 506
507 507 return htmlfill.render(
508 508 render('admin/settings/settings.html'),
509 509 defaults=self._form_defaults(),
510 510 encoding="UTF-8",
511 511 force_defaults=False)
512 512
513 513 @HasPermissionAllDecorator('hg.admin')
514 514 def settings_search(self):
515 515 """GET /admin/settings/search: All items in the collection"""
516 516 # url('admin_settings_search')
517 517 c.active = 'search'
518 518
519 519 from rhodecode.lib.index import searcher_from_config
520 520 searcher = searcher_from_config(config)
521 521 c.statistics = searcher.statistics()
522 522
523 523 return render('admin/settings/settings.html')
524 524
525 525 @HasPermissionAllDecorator('hg.admin')
526 526 def settings_system(self):
527 527 """GET /admin/settings/system: All items in the collection"""
528 528 # url('admin_settings_system')
529 529 snapshot = str2bool(request.GET.get('snapshot'))
530 530 c.active = 'system'
531 531
532 532 defaults = self._form_defaults()
533 533 c.rhodecode_ini = rhodecode.CONFIG
534 534 c.rhodecode_update_url = defaults.get('rhodecode_update_url')
535 535 server_info = ScmModel().get_server_info(request.environ)
536 536 for key, val in server_info.iteritems():
537 537 setattr(c, key, val)
538 538
539 539 if c.disk['percent'] > 90:
540 540 h.flash(h.literal(_(
541 541 'Critical: your disk space is very low <b>%s%%</b> used' %
542 542 c.disk['percent'])), 'error')
543 543 elif c.disk['percent'] > 70:
544 544 h.flash(h.literal(_(
545 545 'Warning: your disk space is running low <b>%s%%</b> used' %
546 546 c.disk['percent'])), 'warning')
547 547
548 548 try:
549 549 c.uptime_age = h._age(
550 550 h.time_to_datetime(c.boot_time), False, show_suffix=False)
551 551 except TypeError:
552 552 c.uptime_age = c.boot_time
553 553
554 554 try:
555 555 c.system_memory = '%s/%s, %s%% (%s%%) used%s' % (
556 556 h.format_byte_size_binary(c.memory['used']),
557 557 h.format_byte_size_binary(c.memory['total']),
558 558 c.memory['percent2'],
559 559 c.memory['percent'],
560 560 ' %s' % c.memory['error'] if 'error' in c.memory else '')
561 561 except TypeError:
562 562 c.system_memory = 'NOT AVAILABLE'
563 563
564 564 rhodecode_ini_safe = rhodecode.CONFIG.copy()
565 565 blacklist = [
566 566 'rhodecode_license_key',
567 567 'routes.map',
568 568 'pylons.h',
569 569 'pylons.app_globals',
570 570 'pylons.environ_config',
571 571 'sqlalchemy.db1.url',
572 572 ('app_conf', 'sqlalchemy.db1.url')
573 573 ]
574 574 for k in blacklist:
575 575 if isinstance(k, tuple):
576 576 section, key = k
577 577 if section in rhodecode_ini_safe:
578 578 rhodecode_ini_safe[section].pop(key, None)
579 579 else:
580 580 rhodecode_ini_safe.pop(k, None)
581 581
582 582 c.rhodecode_ini_safe = rhodecode_ini_safe
583 583
584 584 # TODO: marcink, figure out how to allow only selected users to do this
585 585 c.allowed_to_snapshot = False
586 586
587 587 if snapshot:
588 588 if c.allowed_to_snapshot:
589 589 return render('admin/settings/settings_system_snapshot.html')
590 590 else:
591 591 h.flash('You are not allowed to do this', category='warning')
592 592
593 593 return htmlfill.render(
594 594 render('admin/settings/settings.html'),
595 595 defaults=defaults,
596 596 encoding="UTF-8",
597 597 force_defaults=False)
598 598
599 599 @staticmethod
600 600 def get_update_data(update_url):
601 601 """Return the JSON update data."""
602 602 ver = rhodecode.__version__
603 603 log.debug('Checking for upgrade on `%s` server', update_url)
604 604 opener = urllib2.build_opener()
605 605 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
606 606 response = opener.open(update_url)
607 607 response_data = response.read()
608 608 data = json.loads(response_data)
609 609
610 610 return data
611 611
612 612 @HasPermissionAllDecorator('hg.admin')
613 613 def settings_system_update(self):
614 614 """GET /admin/settings/system/updates: All items in the collection"""
615 615 # url('admin_settings_system_update')
616 616 defaults = self._form_defaults()
617 617 update_url = defaults.get('rhodecode_update_url', '')
618 618
619 619 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
620 620 try:
621 621 data = self.get_update_data(update_url)
622 622 except urllib2.URLError as e:
623 623 log.exception("Exception contacting upgrade server")
624 624 return _err('Failed to contact upgrade server: %r' % e)
625 625 except ValueError as e:
626 626 log.exception("Bad data sent from update server")
627 627 return _err('Bad data sent from update server')
628 628
629 629 latest = data['versions'][0]
630 630
631 631 c.update_url = update_url
632 632 c.latest_data = latest
633 633 c.latest_ver = latest['version']
634 634 c.cur_ver = rhodecode.__version__
635 635 c.should_upgrade = False
636 636
637 637 if (packaging.version.Version(c.latest_ver) >
638 638 packaging.version.Version(c.cur_ver)):
639 639 c.should_upgrade = True
640 640 c.important_notices = latest['general']
641 641
642 642 return render('admin/settings/settings_system_update.html')
643 643
644 644 @HasPermissionAllDecorator('hg.admin')
645 645 def settings_supervisor(self):
646 646 c.rhodecode_ini = rhodecode.CONFIG
647 647 c.active = 'supervisor'
648 648
649 649 c.supervisor_procs = OrderedDict([
650 650 (SUPERVISOR_MASTER, {}),
651 651 ])
652 652
653 653 c.log_size = 10240
654 654 supervisor = SupervisorModel()
655 655
656 656 _connection = supervisor.get_connection(
657 657 c.rhodecode_ini.get('supervisor.uri'))
658 658 c.connection_error = None
659 659 try:
660 660 _connection.supervisor.getAllProcessInfo()
661 661 except Exception as e:
662 662 c.connection_error = str(e)
663 663 log.exception("Exception reading supervisor data")
664 664 return render('admin/settings/settings.html')
665 665
666 666 groupid = c.rhodecode_ini.get('supervisor.group_id')
667 667
668 668 # feed our group processes to the main
669 669 for proc in supervisor.get_group_processes(_connection, groupid):
670 670 c.supervisor_procs[proc['name']] = {}
671 671
672 672 for k in c.supervisor_procs.keys():
673 673 try:
674 674 # master process info
675 675 if k == SUPERVISOR_MASTER:
676 676 _data = supervisor.get_master_state(_connection)
677 677 _data['name'] = 'supervisor master'
678 678 _data['description'] = 'pid %s, id: %s, ver: %s' % (
679 679 _data['pid'], _data['id'], _data['ver'])
680 680 c.supervisor_procs[k] = _data
681 681 else:
682 682 procid = groupid + ":" + k
683 683 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
684 684 except Exception as e:
685 685 log.exception("Exception reading supervisor data")
686 686 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
687 687
688 688 return render('admin/settings/settings.html')
689 689
690 690 @HasPermissionAllDecorator('hg.admin')
691 691 def settings_supervisor_log(self, procid):
692 692 import rhodecode
693 693 c.rhodecode_ini = rhodecode.CONFIG
694 694 c.active = 'supervisor_tail'
695 695
696 696 supervisor = SupervisorModel()
697 697 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
698 698 groupid = c.rhodecode_ini.get('supervisor.group_id')
699 699 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
700 700
701 701 c.log_size = 10240
702 702 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
703 703 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
704 704
705 705 return render('admin/settings/settings.html')
706 706
707 707 @HasPermissionAllDecorator('hg.admin')
708 708 @auth.CSRFRequired()
709 709 def settings_labs_update(self):
710 710 """POST /admin/settings/labs: All items in the collection"""
711 711 # url('admin_settings/labs', method={'POST'})
712 712 c.active = 'labs'
713 713
714 714 application_form = LabsSettingsForm()()
715 715 try:
716 716 form_result = application_form.to_python(dict(request.POST))
717 717 except formencode.Invalid as errors:
718 718 h.flash(
719 719 _('Some form inputs contain invalid data.'),
720 720 category='error')
721 721 return htmlfill.render(
722 722 render('admin/settings/settings.html'),
723 723 defaults=errors.value,
724 724 errors=errors.error_dict or {},
725 725 prefix_error=False,
726 726 encoding='UTF-8',
727 727 force_defaults=False
728 728 )
729 729
730 730 try:
731 731 session = Session()
732 732 for setting in _LAB_SETTINGS:
733 733 setting_name = setting.key[len('rhodecode_'):]
734 734 sett = SettingsModel().create_or_update_setting(
735 735 setting_name, form_result[setting.key], setting.type)
736 736 session.add(sett)
737 737
738 738 except Exception:
739 739 log.exception('Exception while updating lab settings')
740 740 h.flash(_('Error occurred during updating labs settings'),
741 741 category='error')
742 742 else:
743 743 Session().commit()
744 744 SettingsModel().invalidate_settings_cache()
745 745 h.flash(_('Updated Labs settings'), category='success')
746 746 return redirect(url('admin_settings_labs'))
747 747
748 748 return htmlfill.render(
749 749 render('admin/settings/settings.html'),
750 750 defaults=self._form_defaults(),
751 751 encoding='UTF-8',
752 752 force_defaults=False)
753 753
754 754 @HasPermissionAllDecorator('hg.admin')
755 755 def settings_labs(self):
756 756 """GET /admin/settings/labs: All items in the collection"""
757 757 # url('admin_settings_labs')
758 758 if not c.labs_active:
759 759 redirect(url('admin_settings'))
760 760
761 761 c.active = 'labs'
762 762 c.lab_settings = _LAB_SETTINGS
763 763
764 764 return htmlfill.render(
765 765 render('admin/settings/settings.html'),
766 766 defaults=self._form_defaults(),
767 767 encoding='UTF-8',
768 768 force_defaults=False)
769 769
770 770 def _form_defaults(self):
771 771 defaults = SettingsModel().get_all_settings()
772 772 defaults.update(self._get_hg_ui_settings())
773 773 defaults.update({
774 774 'new_svn_branch': '',
775 775 'new_svn_tag': '',
776 776 })
777 777 return defaults
778 778
779 779
780 780 # :param key: name of the setting including the 'rhodecode_' prefix
781 781 # :param type: the RhodeCodeSetting type to use.
782 782 # :param group: the i18ned group in which we should dispaly this setting
783 783 # :param label: the i18ned label we should display for this setting
784 784 # :param help: the i18ned help we should dispaly for this setting
785 785 LabSetting = collections.namedtuple(
786 786 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
787 787
788 788
789 789 # This list has to be kept in sync with the form
790 790 # rhodecode.model.forms.LabsSettingsForm.
791 791 _LAB_SETTINGS = [
792 LabSetting(
793 key='rhodecode_proxy_subversion_http_requests',
794 type='bool',
795 group=lazy_ugettext('Subversion HTTP Support'),
796 label=lazy_ugettext('Proxy subversion HTTP requests'),
797 help='' # Do not translate the empty string!
798 ),
799 LabSetting(
800 key='rhodecode_subversion_http_server_url',
801 type='str',
802 group=lazy_ugettext('Subversion HTTP Server URL'),
803 label='', # Do not translate the empty string!
804 help=lazy_ugettext('e.g. http://localhost:8080/')
805 ),
792
806 793 ]
@@ -1,549 +1,547 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 this is forms validation classes
23 23 http://formencode.org/module-formencode.validators.html
24 24 for list off all availible validators
25 25
26 26 we can create our own validators
27 27
28 28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 29 pre_validators [] These validators will be applied before the schema
30 30 chained_validators [] These validators will be applied after the schema
31 31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 33 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.
34 34 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
35 35
36 36
37 37 <name> = formencode.validators.<name of validator>
38 38 <name> must equal form name
39 39 list=[1,2,3,4,5]
40 40 for SELECT use formencode.All(OneOf(list), Int())
41 41
42 42 """
43 43
44 44 import deform
45 45 import logging
46 46 import formencode
47 47
48 48 from pkg_resources import resource_filename
49 49 from formencode import All, Pipe
50 50
51 51 from pylons.i18n.translation import _
52 52
53 53 from rhodecode import BACKENDS
54 54 from rhodecode.lib import helpers
55 55 from rhodecode.model import validators as v
56 56
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 deform_templates = resource_filename('deform', 'templates')
61 61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 62 search_path = (rhodecode_templates, deform_templates)
63 63
64 64
65 65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 67 def __call__(self, template_name, **kw):
68 68 kw['h'] = helpers
69 69 return self.load(template_name)(**kw)
70 70
71 71
72 72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 73 deform.Form.set_default_renderer(form_renderer)
74 74
75 75
76 76 def LoginForm():
77 77 class _LoginForm(formencode.Schema):
78 78 allow_extra_fields = True
79 79 filter_extra_fields = True
80 80 username = v.UnicodeString(
81 81 strip=True,
82 82 min=1,
83 83 not_empty=True,
84 84 messages={
85 85 'empty': _(u'Please enter a login'),
86 86 'tooShort': _(u'Enter a value %(min)i characters long or more')
87 87 }
88 88 )
89 89
90 90 password = v.UnicodeString(
91 91 strip=False,
92 92 min=3,
93 93 not_empty=True,
94 94 messages={
95 95 'empty': _(u'Please enter a password'),
96 96 'tooShort': _(u'Enter %(min)i characters or more')}
97 97 )
98 98
99 99 remember = v.StringBoolean(if_missing=False)
100 100
101 101 chained_validators = [v.ValidAuth()]
102 102 return _LoginForm
103 103
104 104
105 105 def UserForm(edit=False, available_languages=[], old_data={}):
106 106 class _UserForm(formencode.Schema):
107 107 allow_extra_fields = True
108 108 filter_extra_fields = True
109 109 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
110 110 v.ValidUsername(edit, old_data))
111 111 if edit:
112 112 new_password = All(
113 113 v.ValidPassword(),
114 114 v.UnicodeString(strip=False, min=6, not_empty=False)
115 115 )
116 116 password_confirmation = All(
117 117 v.ValidPassword(),
118 118 v.UnicodeString(strip=False, min=6, not_empty=False),
119 119 )
120 120 admin = v.StringBoolean(if_missing=False)
121 121 else:
122 122 password = All(
123 123 v.ValidPassword(),
124 124 v.UnicodeString(strip=False, min=6, not_empty=True)
125 125 )
126 126 password_confirmation = All(
127 127 v.ValidPassword(),
128 128 v.UnicodeString(strip=False, min=6, not_empty=False)
129 129 )
130 130
131 131 password_change = v.StringBoolean(if_missing=False)
132 132 create_repo_group = v.StringBoolean(if_missing=False)
133 133
134 134 active = v.StringBoolean(if_missing=False)
135 135 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
136 136 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
137 137 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
138 138 extern_name = v.UnicodeString(strip=True)
139 139 extern_type = v.UnicodeString(strip=True)
140 140 language = v.OneOf(available_languages, hideList=False,
141 141 testValueList=True, if_missing=None)
142 142 chained_validators = [v.ValidPasswordsMatch()]
143 143 return _UserForm
144 144
145 145
146 146 def UserGroupForm(edit=False, old_data=None, available_members=None,
147 147 allow_disabled=False):
148 148 old_data = old_data or {}
149 149 available_members = available_members or []
150 150
151 151 class _UserGroupForm(formencode.Schema):
152 152 allow_extra_fields = True
153 153 filter_extra_fields = True
154 154
155 155 users_group_name = All(
156 156 v.UnicodeString(strip=True, min=1, not_empty=True),
157 157 v.ValidUserGroup(edit, old_data)
158 158 )
159 159 user_group_description = v.UnicodeString(strip=True, min=1,
160 160 not_empty=False)
161 161
162 162 users_group_active = v.StringBoolean(if_missing=False)
163 163
164 164 if edit:
165 165 users_group_members = v.OneOf(
166 166 available_members, hideList=False, testValueList=True,
167 167 if_missing=None, not_empty=False
168 168 )
169 169 # this is user group owner
170 170 user = All(
171 171 v.UnicodeString(not_empty=True),
172 172 v.ValidRepoUser(allow_disabled))
173 173 return _UserGroupForm
174 174
175 175
176 176 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
177 177 can_create_in_root=False, allow_disabled=False):
178 178 old_data = old_data or {}
179 179 available_groups = available_groups or []
180 180
181 181 class _RepoGroupForm(formencode.Schema):
182 182 allow_extra_fields = True
183 183 filter_extra_fields = False
184 184
185 185 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
186 186 v.SlugifyName(),)
187 187 group_description = v.UnicodeString(strip=True, min=1,
188 188 not_empty=False)
189 189 group_copy_permissions = v.StringBoolean(if_missing=False)
190 190
191 191 group_parent_id = v.OneOf(available_groups, hideList=False,
192 192 testValueList=True, not_empty=True)
193 193 enable_locking = v.StringBoolean(if_missing=False)
194 194 chained_validators = [
195 195 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
196 196
197 197 if edit:
198 198 # this is repo group owner
199 199 user = All(
200 200 v.UnicodeString(not_empty=True),
201 201 v.ValidRepoUser(allow_disabled))
202 202
203 203 return _RepoGroupForm
204 204
205 205
206 206 def RegisterForm(edit=False, old_data={}):
207 207 class _RegisterForm(formencode.Schema):
208 208 allow_extra_fields = True
209 209 filter_extra_fields = True
210 210 username = All(
211 211 v.ValidUsername(edit, old_data),
212 212 v.UnicodeString(strip=True, min=1, not_empty=True)
213 213 )
214 214 password = All(
215 215 v.ValidPassword(),
216 216 v.UnicodeString(strip=False, min=6, not_empty=True)
217 217 )
218 218 password_confirmation = All(
219 219 v.ValidPassword(),
220 220 v.UnicodeString(strip=False, min=6, not_empty=True)
221 221 )
222 222 active = v.StringBoolean(if_missing=False)
223 223 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
224 224 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
225 225 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
226 226
227 227 chained_validators = [v.ValidPasswordsMatch()]
228 228
229 229 return _RegisterForm
230 230
231 231
232 232 def PasswordResetForm():
233 233 class _PasswordResetForm(formencode.Schema):
234 234 allow_extra_fields = True
235 235 filter_extra_fields = True
236 236 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
237 237 return _PasswordResetForm
238 238
239 239
240 240 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
241 241 allow_disabled=False):
242 242 old_data = old_data or {}
243 243 repo_groups = repo_groups or []
244 244 landing_revs = landing_revs or []
245 245 supported_backends = BACKENDS.keys()
246 246
247 247 class _RepoForm(formencode.Schema):
248 248 allow_extra_fields = True
249 249 filter_extra_fields = False
250 250 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
251 251 v.SlugifyName())
252 252 repo_group = All(v.CanWriteGroup(old_data),
253 253 v.OneOf(repo_groups, hideList=True))
254 254 repo_type = v.OneOf(supported_backends, required=False,
255 255 if_missing=old_data.get('repo_type'))
256 256 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
257 257 repo_private = v.StringBoolean(if_missing=False)
258 258 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
259 259 repo_copy_permissions = v.StringBoolean(if_missing=False)
260 260 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
261 261
262 262 repo_enable_statistics = v.StringBoolean(if_missing=False)
263 263 repo_enable_downloads = v.StringBoolean(if_missing=False)
264 264 repo_enable_locking = v.StringBoolean(if_missing=False)
265 265
266 266 if edit:
267 267 # this is repo owner
268 268 user = All(
269 269 v.UnicodeString(not_empty=True),
270 270 v.ValidRepoUser(allow_disabled))
271 271 clone_uri_change = v.UnicodeString(
272 272 not_empty=False, if_missing=v.Missing)
273 273
274 274 chained_validators = [v.ValidCloneUri(),
275 275 v.ValidRepoName(edit, old_data)]
276 276 return _RepoForm
277 277
278 278
279 279 def RepoPermsForm():
280 280 class _RepoPermsForm(formencode.Schema):
281 281 allow_extra_fields = True
282 282 filter_extra_fields = False
283 283 chained_validators = [v.ValidPerms(type_='repo')]
284 284 return _RepoPermsForm
285 285
286 286
287 287 def RepoGroupPermsForm(valid_recursive_choices):
288 288 class _RepoGroupPermsForm(formencode.Schema):
289 289 allow_extra_fields = True
290 290 filter_extra_fields = False
291 291 recursive = v.OneOf(valid_recursive_choices)
292 292 chained_validators = [v.ValidPerms(type_='repo_group')]
293 293 return _RepoGroupPermsForm
294 294
295 295
296 296 def UserGroupPermsForm():
297 297 class _UserPermsForm(formencode.Schema):
298 298 allow_extra_fields = True
299 299 filter_extra_fields = False
300 300 chained_validators = [v.ValidPerms(type_='user_group')]
301 301 return _UserPermsForm
302 302
303 303
304 304 def RepoFieldForm():
305 305 class _RepoFieldForm(formencode.Schema):
306 306 filter_extra_fields = True
307 307 allow_extra_fields = True
308 308
309 309 new_field_key = All(v.FieldKey(),
310 310 v.UnicodeString(strip=True, min=3, not_empty=True))
311 311 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
312 312 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
313 313 if_missing='str')
314 314 new_field_label = v.UnicodeString(not_empty=False)
315 315 new_field_desc = v.UnicodeString(not_empty=False)
316 316
317 317 return _RepoFieldForm
318 318
319 319
320 320 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
321 321 repo_groups=[], landing_revs=[]):
322 322 class _RepoForkForm(formencode.Schema):
323 323 allow_extra_fields = True
324 324 filter_extra_fields = False
325 325 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
326 326 v.SlugifyName())
327 327 repo_group = All(v.CanWriteGroup(),
328 328 v.OneOf(repo_groups, hideList=True))
329 329 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
330 330 description = v.UnicodeString(strip=True, min=1, not_empty=True)
331 331 private = v.StringBoolean(if_missing=False)
332 332 copy_permissions = v.StringBoolean(if_missing=False)
333 333 fork_parent_id = v.UnicodeString()
334 334 chained_validators = [v.ValidForkName(edit, old_data)]
335 335 landing_rev = v.OneOf(landing_revs, hideList=True)
336 336
337 337 return _RepoForkForm
338 338
339 339
340 340 def ApplicationSettingsForm():
341 341 class _ApplicationSettingsForm(formencode.Schema):
342 342 allow_extra_fields = True
343 343 filter_extra_fields = False
344 344 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
345 345 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
346 346 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
347 347 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
348 348 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
349 349 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
350 350
351 351 return _ApplicationSettingsForm
352 352
353 353
354 354 def ApplicationVisualisationForm():
355 355 class _ApplicationVisualisationForm(formencode.Schema):
356 356 allow_extra_fields = True
357 357 filter_extra_fields = False
358 358 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
359 359 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
360 360 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
361 361
362 362 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
363 363 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
364 364 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
365 365 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
366 366 rhodecode_show_version = v.StringBoolean(if_missing=False)
367 367 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
368 368 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
369 369 rhodecode_gravatar_url = v.UnicodeString(min=3)
370 370 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
371 371 rhodecode_support_url = v.UnicodeString()
372 372 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
373 373 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
374 374
375 375 return _ApplicationVisualisationForm
376 376
377 377
378 378 class _BaseVcsSettingsForm(formencode.Schema):
379 379 allow_extra_fields = True
380 380 filter_extra_fields = False
381 381 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
382 382 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
383 383 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
384 384
385 385 extensions_largefiles = v.StringBoolean(if_missing=False)
386 386 phases_publish = v.StringBoolean(if_missing=False)
387 387
388 388 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
389 389 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
390 390 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
391 391
392 rhodecode_proxy_subversion_http_requests = v.StringBoolean(if_missing=False)
393 rhodecode_subversion_http_server_url = v.UnicodeString(
394 strip=True, if_missing=None)
392 395
393 396 def ApplicationUiSettingsForm():
394 397 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
395 398 web_push_ssl = v.StringBoolean(if_missing=False)
396 399 paths_root_path = All(
397 400 v.ValidPath(),
398 401 v.UnicodeString(strip=True, min=1, not_empty=True)
399 402 )
400 403 extensions_hgsubversion = v.StringBoolean(if_missing=False)
401 404 extensions_hggit = v.StringBoolean(if_missing=False)
402 405 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
403 406 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
404 407
405 408 return _ApplicationUiSettingsForm
406 409
407 410
408 411 def RepoVcsSettingsForm(repo_name):
409 412 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
410 413 inherit_global_settings = v.StringBoolean(if_missing=False)
411 414 new_svn_branch = v.ValidSvnPattern(
412 415 section='vcs_svn_branch', repo_name=repo_name)
413 416 new_svn_tag = v.ValidSvnPattern(
414 417 section='vcs_svn_tag', repo_name=repo_name)
415 418
416 419 return _RepoVcsSettingsForm
417 420
418 421
419 422 def LabsSettingsForm():
420 423 class _LabSettingsForm(formencode.Schema):
421 424 allow_extra_fields = True
422 425 filter_extra_fields = False
423 426
424 rhodecode_proxy_subversion_http_requests = v.StringBoolean(
425 if_missing=False)
426 rhodecode_subversion_http_server_url = v.UnicodeString(
427 strip=True, if_missing=None)
428
429 427 return _LabSettingsForm
430 428
431 429
432 430 def ApplicationPermissionsForm(register_choices, extern_activate_choices):
433 431 class _DefaultPermissionsForm(formencode.Schema):
434 432 allow_extra_fields = True
435 433 filter_extra_fields = True
436 434
437 435 anonymous = v.StringBoolean(if_missing=False)
438 436 default_register = v.OneOf(register_choices)
439 437 default_register_message = v.UnicodeString()
440 438 default_extern_activate = v.OneOf(extern_activate_choices)
441 439
442 440 return _DefaultPermissionsForm
443 441
444 442
445 443 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
446 444 user_group_perms_choices):
447 445 class _ObjectPermissionsForm(formencode.Schema):
448 446 allow_extra_fields = True
449 447 filter_extra_fields = True
450 448 overwrite_default_repo = v.StringBoolean(if_missing=False)
451 449 overwrite_default_group = v.StringBoolean(if_missing=False)
452 450 overwrite_default_user_group = v.StringBoolean(if_missing=False)
453 451 default_repo_perm = v.OneOf(repo_perms_choices)
454 452 default_group_perm = v.OneOf(group_perms_choices)
455 453 default_user_group_perm = v.OneOf(user_group_perms_choices)
456 454
457 455 return _ObjectPermissionsForm
458 456
459 457
460 458 def UserPermissionsForm(create_choices, create_on_write_choices,
461 459 repo_group_create_choices, user_group_create_choices,
462 460 fork_choices, inherit_default_permissions_choices):
463 461 class _DefaultPermissionsForm(formencode.Schema):
464 462 allow_extra_fields = True
465 463 filter_extra_fields = True
466 464
467 465 anonymous = v.StringBoolean(if_missing=False)
468 466
469 467 default_repo_create = v.OneOf(create_choices)
470 468 default_repo_create_on_write = v.OneOf(create_on_write_choices)
471 469 default_user_group_create = v.OneOf(user_group_create_choices)
472 470 default_repo_group_create = v.OneOf(repo_group_create_choices)
473 471 default_fork_create = v.OneOf(fork_choices)
474 472 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
475 473
476 474 return _DefaultPermissionsForm
477 475
478 476
479 477 def UserIndividualPermissionsForm():
480 478 class _DefaultPermissionsForm(formencode.Schema):
481 479 allow_extra_fields = True
482 480 filter_extra_fields = True
483 481
484 482 inherit_default_permissions = v.StringBoolean(if_missing=False)
485 483
486 484 return _DefaultPermissionsForm
487 485
488 486
489 487 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
490 488 class _DefaultsForm(formencode.Schema):
491 489 allow_extra_fields = True
492 490 filter_extra_fields = True
493 491 default_repo_type = v.OneOf(supported_backends)
494 492 default_repo_private = v.StringBoolean(if_missing=False)
495 493 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
496 494 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
497 495 default_repo_enable_locking = v.StringBoolean(if_missing=False)
498 496
499 497 return _DefaultsForm
500 498
501 499
502 500 def AuthSettingsForm():
503 501 class _AuthSettingsForm(formencode.Schema):
504 502 allow_extra_fields = True
505 503 filter_extra_fields = True
506 504 auth_plugins = All(v.ValidAuthPlugins(),
507 505 v.UniqueListFromString()(not_empty=True))
508 506
509 507 return _AuthSettingsForm
510 508
511 509
512 510 def UserExtraEmailForm():
513 511 class _UserExtraEmailForm(formencode.Schema):
514 512 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
515 513 return _UserExtraEmailForm
516 514
517 515
518 516 def UserExtraIpForm():
519 517 class _UserExtraIpForm(formencode.Schema):
520 518 ip = v.ValidIp()(not_empty=True)
521 519 return _UserExtraIpForm
522 520
523 521
524 522 def PullRequestForm(repo_id):
525 523 class _PullRequestForm(formencode.Schema):
526 524 allow_extra_fields = True
527 525 filter_extra_fields = True
528 526
529 527 user = v.UnicodeString(strip=True, required=True)
530 528 source_repo = v.UnicodeString(strip=True, required=True)
531 529 source_ref = v.UnicodeString(strip=True, required=True)
532 530 target_repo = v.UnicodeString(strip=True, required=True)
533 531 target_ref = v.UnicodeString(strip=True, required=True)
534 532 revisions = All(#v.NotReviewedRevisions(repo_id)(),
535 533 v.UniqueList()(not_empty=True))
536 534 review_members = v.UniqueList(convert=int)(not_empty=True)
537 535
538 536 pullrequest_title = v.UnicodeString(strip=True, required=True)
539 537 pullrequest_desc = v.UnicodeString(strip=True, required=False)
540 538
541 539 return _PullRequestForm
542 540
543 541
544 542 def IssueTrackerPatternsForm():
545 543 class _IssueTrackerPatternsForm(formencode.Schema):
546 544 allow_extra_fields = True
547 545 filter_extra_fields = False
548 546 chained_validators = [v.ValidPattern()]
549 547 return _IssueTrackerPatternsForm
@@ -1,698 +1,716 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import hashlib
22 22 import logging
23 23 from collections import namedtuple
24 24 from functools import wraps
25 25
26 26 from rhodecode.lib import caches
27 27 from rhodecode.lib.caching_query import FromCache
28 28 from rhodecode.lib.utils2 import (
29 29 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
30 30 from rhodecode.model import BaseModel
31 31 from rhodecode.model.db import (
32 32 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
33 33 from rhodecode.model.meta import Session
34 34
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 UiSetting = namedtuple(
40 40 'UiSetting', ['section', 'key', 'value', 'active'])
41 41
42 42 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
43 43
44 44
45 45 class SettingNotFound(Exception):
46 46 def __init__(self):
47 47 super(SettingNotFound, self).__init__('Setting is not found')
48 48
49 49
50 50 class SettingsModel(BaseModel):
51 51 BUILTIN_HOOKS = (
52 52 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
53 53 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PULL,
54 54 RhodeCodeUi.HOOK_PRE_PULL)
55 55 HOOKS_SECTION = 'hooks'
56 56
57 57 def __init__(self, sa=None, repo=None):
58 58 self.repo = repo
59 59 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
60 60 self.SettingsDbModel = (
61 61 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
62 62 super(SettingsModel, self).__init__(sa)
63 63
64 64 def get_ui_by_key(self, key):
65 65 q = self.UiDbModel.query()
66 66 q = q.filter(self.UiDbModel.ui_key == key)
67 67 q = self._filter_by_repo(RepoRhodeCodeUi, q)
68 68 return q.scalar()
69 69
70 70 def get_ui_by_section(self, section):
71 71 q = self.UiDbModel.query()
72 72 q = q.filter(self.UiDbModel.ui_section == section)
73 73 q = self._filter_by_repo(RepoRhodeCodeUi, q)
74 74 return q.all()
75 75
76 76 def get_ui_by_section_and_key(self, section, key):
77 77 q = self.UiDbModel.query()
78 78 q = q.filter(self.UiDbModel.ui_section == section)
79 79 q = q.filter(self.UiDbModel.ui_key == key)
80 80 q = self._filter_by_repo(RepoRhodeCodeUi, q)
81 81 return q.scalar()
82 82
83 83 def get_ui(self, section=None, key=None):
84 84 q = self.UiDbModel.query()
85 85 q = self._filter_by_repo(RepoRhodeCodeUi, q)
86 86
87 87 if section:
88 88 q = q.filter(self.UiDbModel.ui_section == section)
89 89 if key:
90 90 q = q.filter(self.UiDbModel.ui_key == key)
91 91
92 92 # TODO: mikhail: add caching
93 93 result = [
94 94 UiSetting(
95 95 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
96 96 value=safe_str(r.ui_value), active=r.ui_active
97 97 )
98 98 for r in q.all()
99 99 ]
100 100 return result
101 101
102 102 def get_builtin_hooks(self):
103 103 q = self.UiDbModel.query()
104 104 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
105 105 return self._get_hooks(q)
106 106
107 107 def get_custom_hooks(self):
108 108 q = self.UiDbModel.query()
109 109 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
110 110 return self._get_hooks(q)
111 111
112 112 def create_ui_section_value(self, section, val, key=None, active=True):
113 113 new_ui = self.UiDbModel()
114 114 new_ui.ui_section = section
115 115 new_ui.ui_value = val
116 116 new_ui.ui_active = active
117 117
118 118 if self.repo:
119 119 repo = self._get_repo(self.repo)
120 120 repository_id = repo.repo_id
121 121 new_ui.repository_id = repository_id
122 122
123 123 if not key:
124 124 # keys are unique so they need appended info
125 125 if self.repo:
126 126 key = hashlib.sha1(
127 127 '{}{}{}'.format(section, val, repository_id)).hexdigest()
128 128 else:
129 129 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
130 130
131 131 new_ui.ui_key = key
132 132
133 133 Session().add(new_ui)
134 134 return new_ui
135 135
136 136 def create_or_update_hook(self, key, value):
137 137 ui = (
138 138 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
139 139 self.UiDbModel())
140 140 ui.ui_section = self.HOOKS_SECTION
141 141 ui.ui_active = True
142 142 ui.ui_key = key
143 143 ui.ui_value = value
144 144
145 145 if self.repo:
146 146 repo = self._get_repo(self.repo)
147 147 repository_id = repo.repo_id
148 148 ui.repository_id = repository_id
149 149
150 150 Session().add(ui)
151 151 return ui
152 152
153 153 def delete_ui(self, id_):
154 154 ui = self.UiDbModel.get(id_)
155 155 if not ui:
156 156 raise SettingNotFound()
157 157 Session().delete(ui)
158 158
159 159 def get_setting_by_name(self, name):
160 160 q = self._get_settings_query()
161 161 q = q.filter(self.SettingsDbModel.app_settings_name == name)
162 162 return q.scalar()
163 163
164 164 def create_or_update_setting(
165 165 self, name, val=Optional(''), type_=Optional('unicode')):
166 166 """
167 167 Creates or updates RhodeCode setting. If updates is triggered it will
168 168 only update parameters that are explicityl set Optional instance will
169 169 be skipped
170 170
171 171 :param name:
172 172 :param val:
173 173 :param type_:
174 174 :return:
175 175 """
176 176
177 177 res = self.get_setting_by_name(name)
178 178 repo = self._get_repo(self.repo) if self.repo else None
179 179
180 180 if not res:
181 181 val = Optional.extract(val)
182 182 type_ = Optional.extract(type_)
183 183
184 184 args = (
185 185 (repo.repo_id, name, val, type_)
186 186 if repo else (name, val, type_))
187 187 res = self.SettingsDbModel(*args)
188 188
189 189 else:
190 190 if self.repo:
191 191 res.repository_id = repo.repo_id
192 192
193 193 res.app_settings_name = name
194 194 if not isinstance(type_, Optional):
195 195 # update if set
196 196 res.app_settings_type = type_
197 197 if not isinstance(val, Optional):
198 198 # update if set
199 199 res.app_settings_value = val
200 200
201 201 Session().add(res)
202 202 return res
203 203
204 204 def invalidate_settings_cache(self):
205 205 namespace = 'rhodecode_settings'
206 206 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
207 207 caches.clear_cache_manager(cache_manager)
208 208
209 209 def get_all_settings(self, cache=False):
210 210 def _compute():
211 211 q = self._get_settings_query()
212 212 if not q:
213 213 raise Exception('Could not get application settings !')
214 214
215 215 settings = {
216 216 'rhodecode_' + result.app_settings_name: result.app_settings_value
217 217 for result in q
218 218 }
219 219 return settings
220 220
221 221 if cache:
222 222 log.debug('Fetching app settings using cache')
223 223 repo = self._get_repo(self.repo) if self.repo else None
224 224 namespace = 'rhodecode_settings'
225 225 cache_manager = caches.get_cache_manager(
226 226 'sql_cache_short', namespace)
227 227 _cache_key = (
228 228 "get_repo_{}_settings".format(repo.repo_id)
229 229 if repo else "get_app_settings")
230 230
231 231 return cache_manager.get(_cache_key, createfunc=_compute)
232 232
233 233 else:
234 234 return _compute()
235 235
236 236 def get_auth_settings(self):
237 237 q = self._get_settings_query()
238 238 q = q.filter(
239 239 self.SettingsDbModel.app_settings_name.startswith('auth_'))
240 240 rows = q.all()
241 241 auth_settings = {
242 242 row.app_settings_name: row.app_settings_value for row in rows}
243 243 return auth_settings
244 244
245 245 def get_auth_plugins(self):
246 246 auth_plugins = self.get_setting_by_name("auth_plugins")
247 247 return auth_plugins.app_settings_value
248 248
249 249 def get_default_repo_settings(self, strip_prefix=False):
250 250 q = self._get_settings_query()
251 251 q = q.filter(
252 252 self.SettingsDbModel.app_settings_name.startswith('default_'))
253 253 rows = q.all()
254 254
255 255 result = {}
256 256 for row in rows:
257 257 key = row.app_settings_name
258 258 if strip_prefix:
259 259 key = remove_prefix(key, prefix='default_')
260 260 result.update({key: row.app_settings_value})
261 261 return result
262 262
263 263 def get_repo(self):
264 264 repo = self._get_repo(self.repo)
265 265 if not repo:
266 266 raise Exception(
267 267 'Repository {} cannot be found'.format(self.repo))
268 268 return repo
269 269
270 270 def _filter_by_repo(self, model, query):
271 271 if self.repo:
272 272 repo = self.get_repo()
273 273 query = query.filter(model.repository_id == repo.repo_id)
274 274 return query
275 275
276 276 def _get_hooks(self, query):
277 277 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
278 278 query = self._filter_by_repo(RepoRhodeCodeUi, query)
279 279 return query.all()
280 280
281 281 def _get_settings_query(self):
282 282 q = self.SettingsDbModel.query()
283 283 return self._filter_by_repo(RepoRhodeCodeSetting, q)
284 284
285 285 def list_enabled_social_plugins(self, settings):
286 286 enabled = []
287 287 for plug in SOCIAL_PLUGINS_LIST:
288 288 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
289 289 )):
290 290 enabled.append(plug)
291 291 return enabled
292 292
293 293
294 294 def assert_repo_settings(func):
295 295 @wraps(func)
296 296 def _wrapper(self, *args, **kwargs):
297 297 if not self.repo_settings:
298 298 raise Exception('Repository is not specified')
299 299 return func(self, *args, **kwargs)
300 300 return _wrapper
301 301
302 302
303 303 class IssueTrackerSettingsModel(object):
304 304 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
305 305 SETTINGS_PREFIX = 'issuetracker_'
306 306
307 307 def __init__(self, sa=None, repo=None):
308 308 self.global_settings = SettingsModel(sa=sa)
309 309 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
310 310
311 311 @property
312 312 def inherit_global_settings(self):
313 313 if not self.repo_settings:
314 314 return True
315 315 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
316 316 return setting.app_settings_value if setting else True
317 317
318 318 @inherit_global_settings.setter
319 319 def inherit_global_settings(self, value):
320 320 if self.repo_settings:
321 321 settings = self.repo_settings.create_or_update_setting(
322 322 self.INHERIT_SETTINGS, value, type_='bool')
323 323 Session().add(settings)
324 324
325 325 def _get_keyname(self, key, uid, prefix=''):
326 326 return '{0}{1}{2}_{3}'.format(
327 327 prefix, self.SETTINGS_PREFIX, key, uid)
328 328
329 329 def _make_dict_for_settings(self, qs):
330 330 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
331 331
332 332 issuetracker_entries = {}
333 333 # create keys
334 334 for k, v in qs.items():
335 335 if k.startswith(prefix_match):
336 336 uid = k[len(prefix_match):]
337 337 issuetracker_entries[uid] = None
338 338
339 339 # populate
340 340 for uid in issuetracker_entries:
341 341 issuetracker_entries[uid] = AttributeDict({
342 342 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
343 343 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
344 344 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
345 345 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
346 346 })
347 347 return issuetracker_entries
348 348
349 349 def get_global_settings(self, cache=False):
350 350 """
351 351 Returns list of global issue tracker settings
352 352 """
353 353 defaults = self.global_settings.get_all_settings(cache=cache)
354 354 settings = self._make_dict_for_settings(defaults)
355 355 return settings
356 356
357 357 def get_repo_settings(self, cache=False):
358 358 """
359 359 Returns list of issue tracker settings per repository
360 360 """
361 361 if not self.repo_settings:
362 362 raise Exception('Repository is not specified')
363 363 all_settings = self.repo_settings.get_all_settings(cache=cache)
364 364 settings = self._make_dict_for_settings(all_settings)
365 365 return settings
366 366
367 367 def get_settings(self, cache=False):
368 368 if self.inherit_global_settings:
369 369 return self.get_global_settings(cache=cache)
370 370 else:
371 371 return self.get_repo_settings(cache=cache)
372 372
373 373 def delete_entries(self, uid):
374 374 if self.repo_settings:
375 375 all_patterns = self.get_repo_settings()
376 376 settings_model = self.repo_settings
377 377 else:
378 378 all_patterns = self.get_global_settings()
379 379 settings_model = self.global_settings
380 380 entries = all_patterns.get(uid)
381 381
382 382 for del_key in entries:
383 383 setting_name = self._get_keyname(del_key, uid)
384 384 entry = settings_model.get_setting_by_name(setting_name)
385 385 if entry:
386 386 Session().delete(entry)
387 387
388 388 Session().commit()
389 389
390 390 def create_or_update_setting(
391 391 self, name, val=Optional(''), type_=Optional('unicode')):
392 392 if self.repo_settings:
393 393 setting = self.repo_settings.create_or_update_setting(
394 394 name, val, type_)
395 395 else:
396 396 setting = self.global_settings.create_or_update_setting(
397 397 name, val, type_)
398 398 return setting
399 399
400 400
401 401 class VcsSettingsModel(object):
402 402
403 403 INHERIT_SETTINGS = 'inherit_vcs_settings'
404 404 GENERAL_SETTINGS = (
405 405 'use_outdated_comments', 'pr_merge_enabled',
406 406 'hg_use_rebase_for_merging')
407 407 HOOKS_SETTINGS = (
408 408 ('hooks', 'changegroup.repo_size'),
409 409 ('hooks', 'changegroup.push_logger'),
410 410 ('hooks', 'outgoing.pull_logger'))
411 411 HG_SETTINGS = (
412 412 ('extensions', 'largefiles'), ('phases', 'publish'))
413 413 GLOBAL_HG_SETTINGS = HG_SETTINGS + (('extensions', 'hgsubversion'), )
414 GLOBAL_SVN_SETTINGS = (
415 'rhodecode_proxy_subversion_http_requests',
416 'rhodecode_subversion_http_server_url')
414 417 SVN_BRANCH_SECTION = 'vcs_svn_branch'
415 418 SVN_TAG_SECTION = 'vcs_svn_tag'
416 419 SSL_SETTING = ('web', 'push_ssl')
417 420 PATH_SETTING = ('paths', '/')
418 421
419 422 def __init__(self, sa=None, repo=None):
420 423 self.global_settings = SettingsModel(sa=sa)
421 424 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
422 425 self._ui_settings = self.HG_SETTINGS + self.HOOKS_SETTINGS
423 426 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
424 427
425 428 @property
426 429 @assert_repo_settings
427 430 def inherit_global_settings(self):
428 431 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
429 432 return setting.app_settings_value if setting else True
430 433
431 434 @inherit_global_settings.setter
432 435 @assert_repo_settings
433 436 def inherit_global_settings(self, value):
434 437 self.repo_settings.create_or_update_setting(
435 438 self.INHERIT_SETTINGS, value, type_='bool')
436 439
437 440 def get_global_svn_branch_patterns(self):
438 441 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
439 442
440 443 @assert_repo_settings
441 444 def get_repo_svn_branch_patterns(self):
442 445 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
443 446
444 447 def get_global_svn_tag_patterns(self):
445 448 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
446 449
447 450 @assert_repo_settings
448 451 def get_repo_svn_tag_patterns(self):
449 452 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
450 453
451 454 def get_global_settings(self):
452 455 return self._collect_all_settings(global_=True)
453 456
454 457 @assert_repo_settings
455 458 def get_repo_settings(self):
456 459 return self._collect_all_settings(global_=False)
457 460
458 461 @assert_repo_settings
459 462 def create_or_update_repo_settings(
460 463 self, data, inherit_global_settings=False):
461 464 from rhodecode.model.scm import ScmModel
462 465
463 466 self.inherit_global_settings = inherit_global_settings
464 467
465 468 repo = self.repo_settings.get_repo()
466 469 if not inherit_global_settings:
467 470 if repo.repo_type == 'svn':
468 471 self.create_repo_svn_settings(data)
469 472 else:
470 473 self.create_or_update_repo_hook_settings(data)
471 474 self.create_or_update_repo_pr_settings(data)
472 475
473 476 if repo.repo_type == 'hg':
474 477 self.create_or_update_repo_hg_settings(data)
475 478
476 479 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
477 480
478 481 @assert_repo_settings
479 482 def create_or_update_repo_hook_settings(self, data):
480 483 for section, key in self.HOOKS_SETTINGS:
481 484 data_key = self._get_form_ui_key(section, key)
482 485 if data_key not in data:
483 486 raise ValueError(
484 487 'The given data does not contain {} key'.format(data_key))
485 488
486 489 active = data.get(data_key)
487 490 repo_setting = self.repo_settings.get_ui_by_section_and_key(
488 491 section, key)
489 492 if not repo_setting:
490 493 global_setting = self.global_settings.\
491 494 get_ui_by_section_and_key(section, key)
492 495 self.repo_settings.create_ui_section_value(
493 496 section, global_setting.ui_value, key=key, active=active)
494 497 else:
495 498 repo_setting.ui_active = active
496 499 Session().add(repo_setting)
497 500
498 501 def update_global_hook_settings(self, data):
499 502 for section, key in self.HOOKS_SETTINGS:
500 503 data_key = self._get_form_ui_key(section, key)
501 504 if data_key not in data:
502 505 raise ValueError(
503 506 'The given data does not contain {} key'.format(data_key))
504 507 active = data.get(data_key)
505 508 repo_setting = self.global_settings.get_ui_by_section_and_key(
506 509 section, key)
507 510 repo_setting.ui_active = active
508 511 Session().add(repo_setting)
509 512
510 513 @assert_repo_settings
511 514 def create_or_update_repo_pr_settings(self, data):
512 515 return self._create_or_update_general_settings(
513 516 self.repo_settings, data)
514 517
515 518 def create_or_update_global_pr_settings(self, data):
516 519 return self._create_or_update_general_settings(
517 520 self.global_settings, data)
518 521
519 522 @assert_repo_settings
520 523 def create_repo_svn_settings(self, data):
521 524 return self._create_svn_settings(self.repo_settings, data)
522 525
523 526 def create_global_svn_settings(self, data):
524 527 return self._create_svn_settings(self.global_settings, data)
525 528
526 529 @assert_repo_settings
527 530 def create_or_update_repo_hg_settings(self, data):
528 531 largefiles, phases = self.HG_SETTINGS
529 532 largefiles_key, phases_key = self._get_hg_settings(
530 533 self.HG_SETTINGS, data)
531 534 self._create_or_update_ui(
532 535 self.repo_settings, *largefiles, value='',
533 536 active=data[largefiles_key])
534 537 self._create_or_update_ui(
535 538 self.repo_settings, *phases, value=safe_str(data[phases_key]))
536 539
537 540 def create_or_update_global_hg_settings(self, data):
538 541 largefiles, phases, subversion = self.GLOBAL_HG_SETTINGS
539 542 largefiles_key, phases_key, subversion_key = self._get_hg_settings(
540 543 self.GLOBAL_HG_SETTINGS, data)
541 544 self._create_or_update_ui(
542 545 self.global_settings, *largefiles, value='',
543 546 active=data[largefiles_key])
544 547 self._create_or_update_ui(
545 548 self.global_settings, *phases, value=safe_str(data[phases_key]))
546 549 self._create_or_update_ui(
547 550 self.global_settings, *subversion, active=data[subversion_key])
548 551
552 def create_or_update_global_svn_settings(self, data):
553 rhodecode_proxy_subversion_http_requests,
554 rhodecode_subversion_http_server_url = self.GLOBAL_SVN_SETTINGS
555 rhodecode_proxy_subversion_http_requests_key,
556 rhodecode_subversion_http_server_url_key = self._get_svn_settings(
557 self.GLOBAL_SVN_SETTINGS, data)
558 self._create_or_update_ui(
559 self.global_settings,
560 *rhodecode_proxy_subversion_http_requests,
561 value=safe_str(data[rhodecode_proxy_subversion_http_requests_key]))
562 self._create_or_update_ui(
563 self.global_settings,
564 *rhodecode_subversion_http_server_url,
565 active=data[rhodecode_subversion_http_server_url_key])
566
549 567 def update_global_ssl_setting(self, value):
550 568 self._create_or_update_ui(
551 569 self.global_settings, *self.SSL_SETTING, value=value)
552 570
553 571 def update_global_path_setting(self, value):
554 572 self._create_or_update_ui(
555 573 self.global_settings, *self.PATH_SETTING, value=value)
556 574
557 575 @assert_repo_settings
558 576 def delete_repo_svn_pattern(self, id_):
559 577 self.repo_settings.delete_ui(id_)
560 578
561 579 def delete_global_svn_pattern(self, id_):
562 580 self.global_settings.delete_ui(id_)
563 581
564 582 @assert_repo_settings
565 583 def get_repo_ui_settings(self, section=None, key=None):
566 584 global_uis = self.global_settings.get_ui(section, key)
567 585 repo_uis = self.repo_settings.get_ui(section, key)
568 586 filtered_repo_uis = self._filter_ui_settings(repo_uis)
569 587 filtered_repo_uis_keys = [
570 588 (s.section, s.key) for s in filtered_repo_uis]
571 589
572 590 def _is_global_ui_filtered(ui):
573 591 return (
574 592 (ui.section, ui.key) in filtered_repo_uis_keys
575 593 or ui.section in self._svn_sections)
576 594
577 595 filtered_global_uis = [
578 596 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
579 597
580 598 return filtered_global_uis + filtered_repo_uis
581 599
582 600 def get_global_ui_settings(self, section=None, key=None):
583 601 return self.global_settings.get_ui(section, key)
584 602
585 603 def get_ui_settings(self, section=None, key=None):
586 604 if not self.repo_settings or self.inherit_global_settings:
587 605 return self.get_global_ui_settings(section, key)
588 606 else:
589 607 return self.get_repo_ui_settings(section, key)
590 608
591 609 def get_svn_patterns(self, section=None):
592 610 if not self.repo_settings:
593 611 return self.get_global_ui_settings(section)
594 612 else:
595 613 return self.get_repo_ui_settings(section)
596 614
597 615 @assert_repo_settings
598 616 def get_repo_general_settings(self):
599 617 global_settings = self.global_settings.get_all_settings()
600 618 repo_settings = self.repo_settings.get_all_settings()
601 619 filtered_repo_settings = self._filter_general_settings(repo_settings)
602 620 global_settings.update(filtered_repo_settings)
603 621 return global_settings
604 622
605 623 def get_global_general_settings(self):
606 624 return self.global_settings.get_all_settings()
607 625
608 626 def get_general_settings(self):
609 627 if not self.repo_settings or self.inherit_global_settings:
610 628 return self.get_global_general_settings()
611 629 else:
612 630 return self.get_repo_general_settings()
613 631
614 632 def get_repos_location(self):
615 633 return self.global_settings.get_ui_by_key('/').ui_value
616 634
617 635 def _filter_ui_settings(self, settings):
618 636 filtered_settings = [
619 637 s for s in settings if self._should_keep_setting(s)]
620 638 return filtered_settings
621 639
622 640 def _should_keep_setting(self, setting):
623 641 keep = (
624 642 (setting.section, setting.key) in self._ui_settings or
625 643 setting.section in self._svn_sections)
626 644 return keep
627 645
628 646 def _filter_general_settings(self, settings):
629 647 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
630 648 return {
631 649 k: settings[k]
632 650 for k in settings if k in keys}
633 651
634 652 def _collect_all_settings(self, global_=False):
635 653 settings = self.global_settings if global_ else self.repo_settings
636 654 result = {}
637 655
638 656 for section, key in self._ui_settings:
639 657 ui = settings.get_ui_by_section_and_key(section, key)
640 658 result_key = self._get_form_ui_key(section, key)
641 659 if ui:
642 660 if section in ('hooks', 'extensions'):
643 661 result[result_key] = ui.ui_active
644 662 else:
645 663 result[result_key] = ui.ui_value
646 664
647 665 for name in self.GENERAL_SETTINGS:
648 666 setting = settings.get_setting_by_name(name)
649 667 if setting:
650 668 result_key = 'rhodecode_{}'.format(name)
651 669 result[result_key] = setting.app_settings_value
652 670
653 671 return result
654 672
655 673 def _get_form_ui_key(self, section, key):
656 674 return '{section}_{key}'.format(
657 675 section=section, key=key.replace('.', '_'))
658 676
659 677 def _create_or_update_ui(
660 678 self, settings, section, key, value=None, active=None):
661 679 ui = settings.get_ui_by_section_and_key(section, key)
662 680 if not ui:
663 681 active = True if active is None else active
664 682 settings.create_ui_section_value(
665 683 section, value, key=key, active=active)
666 684 else:
667 685 if active is not None:
668 686 ui.ui_active = active
669 687 if value is not None:
670 688 ui.ui_value = value
671 689 Session().add(ui)
672 690
673 691 def _create_svn_settings(self, settings, data):
674 692 svn_settings = {
675 693 'new_svn_branch': self.SVN_BRANCH_SECTION,
676 694 'new_svn_tag': self.SVN_TAG_SECTION
677 695 }
678 696 for key in svn_settings:
679 697 if data.get(key):
680 698 settings.create_ui_section_value(svn_settings[key], data[key])
681 699
682 700 def _create_or_update_general_settings(self, settings, data):
683 701 for name in self.GENERAL_SETTINGS:
684 702 data_key = 'rhodecode_{}'.format(name)
685 703 if data_key not in data:
686 704 raise ValueError(
687 705 'The given data does not contain {} key'.format(data_key))
688 706 setting = settings.create_or_update_setting(
689 707 name, data[data_key], 'bool')
690 708 Session().add(setting)
691 709
692 710 def _get_hg_settings(self, settings, data):
693 711 data_keys = [self._get_form_ui_key(*s) for s in settings]
694 712 for data_key in data_keys:
695 713 if data_key not in data:
696 714 raise ValueError(
697 715 'The given data does not contain {} key'.format(data_key))
698 716 return data_keys
@@ -1,244 +1,266 b''
1 1 ## snippet for displaying vcs settings
2 2 ## usage:
3 3 ## <%namespace name="vcss" file="/base/vcssettings.html"/>
4 4 ## ${vcss.vcs_settings_fields()}
5 5
6 6 <%def name="vcs_settings_fields(suffix='', svn_branch_patterns=None, svn_tag_patterns=None, repo_type=None, display_globals=False, allow_repo_location_change=False, **kwargs)">
7 7 % if display_globals:
8 8 <div class="panel panel-default">
9 9 <div class="panel-heading" id="general">
10 10 <h3 class="panel-title">${_('General')}</h3>
11 11 </div>
12 12 <div class="panel-body">
13 13 <div class="field">
14 14 <div class="checkbox">
15 15 ${h.checkbox('web_push_ssl' + suffix, 'True')}
16 16 <label for="web_push_ssl${suffix}">${_('Require SSL for vcs operations')}</label>
17 17 </div>
18 18 <div class="label">
19 19 <span class="help-block">${_('Activate to set RhodeCode to require SSL for pushing or pulling. If SSL certificate is missing it will return a HTTP Error 406: Not Acceptable.')}</span>
20 20 </div>
21 21 </div>
22 22 </div>
23 23 </div>
24 24 % endif
25 25
26 26 % if display_globals:
27 27 <div class="panel panel-default">
28 28 <div class="panel-heading">
29 29 <h3 class="panel-title">${_('Main Storage Location')}</h3>
30 30 </div>
31 31 <div class="panel-body">
32 32 <div class="field">
33 33 <div class="inputx locked_input">
34 34 %if allow_repo_location_change:
35 35 ${h.text('paths_root_path',size=59,readonly="readonly", class_="disabled")}
36 36 <span id="path_unlock" class="tooltip"
37 37 title="${h.tooltip(_('Click to unlock. You must restart RhodeCode in order to make this setting take effect.'))}">
38 38 <div class="btn btn-default lock_input_button"><i id="path_unlock_icon" class="icon-lock"></i></div>
39 39 </span>
40 40 %else:
41 41 ${_('Repository location change is disabled. You can enable this by changing the `allow_repo_location_change` inside .ini file.')}
42 42 ## form still requires this but we cannot internally change it anyway
43 43 ${h.hidden('paths_root_path',size=30,readonly="readonly", class_="disabled")}
44 44 %endif
45 45 </div>
46 46 </div>
47 47 <div class="label">
48 48 <span class="help-block">${_('Filesystem location where repositories should be stored. After changing this value a restart and rescan of the repository folder are required.')}</span>
49 49 </div>
50 50 </div>
51 51 </div>
52 52 % endif
53 53
54 54 % if display_globals or repo_type in ['git', 'hg']:
55 55 <div class="panel panel-default">
56 56 <div class="panel-heading" id="general">
57 57 <h3 class="panel-title">${_('Internal Hooks')}</h3>
58 58 </div>
59 59 <div class="panel-body">
60 60 <div class="field">
61 61 <div class="checkbox">
62 62 ${h.checkbox('hooks_changegroup_repo_size' + suffix, 'True', **kwargs)}
63 63 <label for="hooks_changegroup_repo_size${suffix}">${_('Show repository size after push')}</label>
64 64 </div>
65 65
66 66 <div class="label">
67 67 <span class="help-block">${_('Trigger a hook that calculates repository size after each push.')}</span>
68 68 </div>
69 69 <div class="checkbox">
70 70 ${h.checkbox('hooks_changegroup_push_logger' + suffix, 'True', **kwargs)}
71 71 <label for="hooks_changegroup_push_logger${suffix}">${_('Execute pre/post push hooks')}</label>
72 72 </div>
73 73 <div class="label">
74 74 <span class="help-block">${_('Execute Built in pre/post push hooks. This also executes rcextensions hooks.')}</span>
75 75 </div>
76 76 <div class="checkbox">
77 77 ${h.checkbox('hooks_outgoing_pull_logger' + suffix, 'True', **kwargs)}
78 78 <label for="hooks_outgoing_pull_logger${suffix}">${_('Execute pre/post pull hooks')}</label>
79 79 </div>
80 80 <div class="label">
81 81 <span class="help-block">${_('Execute Built in pre/post pull hooks. This also executes rcextensions hooks.')}</span>
82 82 </div>
83 83 </div>
84 84 </div>
85 85 </div>
86 86 % endif
87 87
88 88 % if display_globals or repo_type in ['hg']:
89 89 <div class="panel panel-default">
90 90 <div class="panel-heading">
91 91 <h3 class="panel-title">${_('Mercurial Settings')}</h3>
92 92 </div>
93 93 <div class="panel-body">
94 94 <div class="checkbox">
95 95 ${h.checkbox('extensions_largefiles' + suffix, 'True', **kwargs)}
96 96 <label for="extensions_largefiles${suffix}">${_('Enable largefiles extension')}</label>
97 97 </div>
98 98 <div class="label">
99 99 <span class="help-block">${_('Enable Largefiles extensions for all repositories.')}</span>
100 100 </div>
101 101 <div class="checkbox">
102 102 ${h.checkbox('phases_publish' + suffix, 'True', **kwargs)}
103 103 <label for="phases_publish${suffix}">${_('Set repositories as publishing') if display_globals else _('Set repository as publishing')}</label>
104 104 </div>
105 105 <div class="label">
106 106 <span class="help-block">${_('When this is enabled all commits in the repository are seen as public commits by clients.')}</span>
107 107 </div>
108 108 % if display_globals:
109 109 <div class="checkbox">
110 110 ${h.checkbox('extensions_hgsubversion' + suffix,'True')}
111 111 <label for="extensions_hgsubversion${suffix}">${_('Enable hgsubversion extension')}</label>
112 112 </div>
113 113 <div class="label">
114 114 <span class="help-block">${_('Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type.')}</span>
115 115 </div>
116 116 % endif
117 117 </div>
118 118 </div>
119 119 % endif
120 120
121 121 % if display_globals or repo_type in ['svn']:
122 122 <div class="panel panel-default">
123 123 <div class="panel-heading">
124 124 <h3 class="panel-title">${_('Subversion Settings')}</h3>
125 125 </div>
126 126 <div class="panel-body">
127 127 <div class="field">
128 128 <div class="content" >
129 <label>${_('Subversion HTTP Support')}</label><br/>
130 </div>
131 <div class="checkbox">
132 ${h.checkbox('rhodecode_proxy_subversion_http_requests' + suffix, 'True', **kwargs)}
133 <label for="rhodecode_proxy_subversion_http_requests${suffix}">${_('Proxy subversion HTTP requests')}</label>
134 </div>
135 </div>
136 <div class="field">
137 <div class="label">
138 <label for="rhodecode_subversion_http_server_url">${_('Subversion HTTP Server URL')}</label><br/>
139 </div>
140 <div class="input">
141 ${h.text('rhodecode_subversion_http_server_url',size=59)}
142 </div>
143 </div>
144 <div class="field">
145 <div class="label">
146 <span class="help-block">${_('e.g. http://localhost:8080/')}</span>
147 </div>
148 </div>
149 <div class="field">
150 <div class="content" >
129 151 <label>${_('Repository patterns')}</label><br/>
130 152 </div>
131 153 </div>
132 154 <div class="label">
133 155 <span class="help-block">${_('Patterns for identifying SVN branches and tags. For recursive search, use "*". Eg.: "/branches/*"')}</span>
134 156 </div>
135 157
136 158 <div class="field branch_patterns">
137 159 <div class="input" >
138 160 <label>${_('Branches')}:</label><br/>
139 161 </div>
140 162 % if svn_branch_patterns:
141 163 % for branch in svn_branch_patterns:
142 164 <div class="input adjacent" id="${'id%s' % branch.ui_id}">
143 165 ${h.hidden('branch_ui_key' + suffix, branch.ui_key)}
144 166 ${h.text('branch_value_%d' % branch.ui_id + suffix, branch.ui_value, size=59, readonly="readonly", class_='disabled')}
145 167 % if kwargs.get('disabled') != 'disabled':
146 168 <span class="btn btn-x" onclick="ajaxDeletePattern(${branch.ui_id},'${'id%s' % branch.ui_id}')">
147 169 ${_('Delete')}
148 170 </span>
149 171 % endif
150 172 </div>
151 173 % endfor
152 174 %endif
153 175 </div>
154 176 % if kwargs.get('disabled') != 'disabled':
155 177 <div class="field branch_patterns">
156 178 <div class="input" >
157 179 ${h.text('new_svn_branch',size=59,placeholder='New branch pattern')}
158 180 </div>
159 181 </div>
160 182 % endif
161 183 <div class="field tag_patterns">
162 184 <div class="input" >
163 185 <label>${_('Tags')}:</label><br/>
164 186 </div>
165 187 % if svn_tag_patterns:
166 188 % for tag in svn_tag_patterns:
167 189 <div class="input" id="${'id%s' % tag.ui_id + suffix}">
168 190 ${h.hidden('tag_ui_key' + suffix, tag.ui_key)}
169 191 ${h.text('tag_ui_value_new_%d' % tag.ui_id + suffix, tag.ui_value, size=59, readonly="readonly", class_='disabled tag_input')}
170 192 % if kwargs.get('disabled') != 'disabled':
171 193 <span class="btn btn-x" onclick="ajaxDeletePattern(${tag.ui_id},'${'id%s' % tag.ui_id}')">
172 194 ${_('Delete')}
173 195 </span>
174 196 %endif
175 197 </div>
176 198 % endfor
177 199 % endif
178 200 </div>
179 201 % if kwargs.get('disabled') != 'disabled':
180 202 <div class="field tag_patterns">
181 203 <div class="input" >
182 204 ${h.text('new_svn_tag' + suffix, size=59, placeholder='New tag pattern')}
183 205 </div>
184 206 </div>
185 207 %endif
186 208 </div>
187 209 </div>
188 210 % else:
189 211 ${h.hidden('new_svn_branch' + suffix, '')}
190 212 ${h.hidden('new_svn_tag' + suffix, '')}
191 213 % endif
192 214
193 215 % if display_globals or repo_type in ['hg', 'git']:
194 216 <div class="panel panel-default">
195 217 <div class="panel-heading">
196 218 <h3 class="panel-title">${_('Pull Request Settings')}</h3>
197 219 </div>
198 220 <div class="panel-body">
199 221 <div class="checkbox">
200 222 ${h.checkbox('rhodecode_pr_merge_enabled' + suffix, 'True', **kwargs)}
201 223 <label for="rhodecode_pr_merge_enabled${suffix}">${_('Enable server-side merge for pull requests')}</label>
202 224 </div>
203 225 <div class="label">
204 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>
205 227 </div>
206 228 <div class="checkbox">
207 229 ${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
208 230 <label for="rhodecode_use_outdated_comments${suffix}">${_('Invalidate and relocate inline comments during update')}</label>
209 231 </div>
210 232 <div class="label">
211 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>
212 234 </div>
213 235 </div>
214 236 </div>
215 237 % endif
216 238
217 239 ## This condition has to be adapted if we add more labs settings for
218 240 ## VCS types other than 'hg'
219 241 % if c.labs_active and (display_globals or repo_type in ['hg']):
220 242 <div class="panel panel-danger">
221 243 <div class="panel-heading">
222 244 <h3 class="panel-title">${_('Labs settings')}: ${_('These features are considered experimental and may not work as expected.')}</h3>
223 245 </div>
224 246 <div class="panel-body">
225 247 <div class="fields">
226 248
227 249 <div class="field">
228 250 <div class="label">
229 251 <label>${_('Mercurial server-side merge')}:</label>
230 252 </div>
231 253 <div class="checkboxes">
232 254 <div class="checkbox">
233 255 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
234 256 <label for="rhodecode_hg_use_rebase_for_merging${suffix}">${_('Use rebase instead of creating a merge commit when merging via web interface')}</label>
235 257 </div>
236 258 <!-- <p class="help-block">Help message here</p> -->
237 259 </div>
238 260 </div>
239 261
240 262 </div>
241 263 </div>
242 264 </div>
243 265 % endif
244 266 </%def>
General Comments 0
You need to be logged in to leave comments. Login now