##// END OF EJS Templates
removed duplicated logic of how we invalidate caches for repos
marcink -
r3693:6843cabe beta
parent child Browse files
Show More
@@ -1,606 +1,606 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repositories controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from formencode import htmlfill
30 30
31 31 from webob.exc import HTTPInternalServerError, HTTPForbidden
32 32 from pylons import request, session, tmpl_context as c, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35 from sqlalchemy.exc import IntegrityError
36 36
37 37 import rhodecode
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator, NotAnonymous,\
41 41 HasPermissionAny, HasReposGroupPermissionAny, HasRepoPermissionAnyDecorator
42 42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
43 from rhodecode.lib.utils import action_logger, repo_name_slug
44 44 from rhodecode.lib.helpers import get_token
45 45 from rhodecode.model.meta import Session
46 46 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup,\
47 47 RhodeCodeSetting, RepositoryField
48 48 from rhodecode.model.forms import RepoForm, RepoFieldForm, RepoPermsForm
49 49 from rhodecode.model.scm import ScmModel, GroupList
50 50 from rhodecode.model.repo import RepoModel
51 51 from rhodecode.lib.compat import json
52 52 from sqlalchemy.sql.expression import func
53 53 from rhodecode.lib.exceptions import AttachedForksError
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class ReposController(BaseRepoController):
59 59 """
60 60 REST Controller styled on the Atom Publishing Protocol"""
61 61 # To properly map this controller, ensure your config/routing.py
62 62 # file has a resource setup:
63 63 # map.resource('repo', 'repos')
64 64
65 65 @LoginRequired()
66 66 def __before__(self):
67 67 c.admin_user = session.get('admin_user')
68 68 c.admin_username = session.get('admin_username')
69 69 super(ReposController, self).__before__()
70 70
71 71 def __load_defaults(self):
72 72 acl_groups = GroupList(RepoGroup.query().all(),
73 73 perm_set=['group.write', 'group.admin'])
74 74 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
75 75 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
76 76
77 77 repo_model = RepoModel()
78 78 c.users_array = repo_model.get_users_js()
79 79 c.users_groups_array = repo_model.get_users_groups_js()
80 80 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
81 81 c.landing_revs_choices = choices
82 82
83 83 def __load_data(self, repo_name=None):
84 84 """
85 85 Load defaults settings for edit, and update
86 86
87 87 :param repo_name:
88 88 """
89 89 self.__load_defaults()
90 90
91 91 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
92 92 repo = db_repo.scm_instance
93 93
94 94 if c.repo_info is None:
95 95 h.not_mapped_error(repo_name)
96 96 return redirect(url('repos'))
97 97
98 98 ##override defaults for exact repo info here git/hg etc
99 99 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
100 100 c.landing_revs_choices = choices
101 101
102 102 c.default_user_id = User.get_by_username('default').user_id
103 103 c.in_public_journal = UserFollowing.query()\
104 104 .filter(UserFollowing.user_id == c.default_user_id)\
105 105 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
106 106
107 107 if c.repo_info.stats:
108 108 # this is on what revision we ended up so we add +1 for count
109 109 last_rev = c.repo_info.stats.stat_on_revision + 1
110 110 else:
111 111 last_rev = 0
112 112 c.stats_revision = last_rev
113 113
114 114 c.repo_last_rev = repo.count() if repo.revisions else 0
115 115
116 116 if last_rev == 0 or c.repo_last_rev == 0:
117 117 c.stats_percentage = 0
118 118 else:
119 119 c.stats_percentage = '%.2f' % ((float((last_rev)) /
120 120 c.repo_last_rev) * 100)
121 121
122 122 c.repo_fields = RepositoryField.query()\
123 123 .filter(RepositoryField.repository == db_repo).all()
124 124
125 125 defaults = RepoModel()._get_defaults(repo_name)
126 126
127 127 c.repos_list = [('', _('--REMOVE FORK--'))]
128 128 c.repos_list += [(x.repo_id, x.repo_name) for x in
129 129 Repository.query().order_by(Repository.repo_name).all()
130 130 if x.repo_id != c.repo_info.repo_id]
131 131
132 132 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
133 133 return defaults
134 134
135 135 @HasPermissionAllDecorator('hg.admin')
136 136 def index(self, format='html'):
137 137 """GET /repos: All items in the collection"""
138 138 # url('repos')
139 139
140 140 c.repos_list = Repository.query()\
141 141 .order_by(func.lower(Repository.repo_name))\
142 142 .all()
143 143
144 144 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
145 145 admin=True,
146 146 super_user_actions=True)
147 147 #json used to render the grid
148 148 c.data = json.dumps(repos_data)
149 149
150 150 return render('admin/repos/repos.html')
151 151
152 152 @NotAnonymous()
153 153 def create(self):
154 154 """
155 155 POST /repos: Create a new item"""
156 156 # url('repos')
157 157
158 158 self.__load_defaults()
159 159 form_result = {}
160 160 try:
161 161 form_result = RepoForm(repo_groups=c.repo_groups_choices,
162 162 landing_revs=c.landing_revs_choices)()\
163 163 .to_python(dict(request.POST))
164 164
165 165 new_repo = RepoModel().create(form_result,
166 166 self.rhodecode_user.user_id)
167 167 if form_result['clone_uri']:
168 168 h.flash(_('Created repository %s from %s') \
169 169 % (form_result['repo_name'], form_result['clone_uri']),
170 170 category='success')
171 171 else:
172 172 repo_url = h.link_to(form_result['repo_name'],
173 173 h.url('summary_home', repo_name=form_result['repo_name_full']))
174 174 h.flash(h.literal(_('Created repository %s') % repo_url),
175 175 category='success')
176 176
177 177 if request.POST.get('user_created'):
178 178 # created by regular non admin user
179 179 action_logger(self.rhodecode_user, 'user_created_repo',
180 180 form_result['repo_name_full'], self.ip_addr,
181 181 self.sa)
182 182 else:
183 183 action_logger(self.rhodecode_user, 'admin_created_repo',
184 184 form_result['repo_name_full'], self.ip_addr,
185 185 self.sa)
186 186 Session().commit()
187 187 except formencode.Invalid, errors:
188 188 return htmlfill.render(
189 189 render('admin/repos/repo_add.html'),
190 190 defaults=errors.value,
191 191 errors=errors.error_dict or {},
192 192 prefix_error=False,
193 193 encoding="UTF-8")
194 194
195 195 except Exception:
196 196 log.error(traceback.format_exc())
197 197 msg = _('Error creating repository %s') \
198 198 % form_result.get('repo_name')
199 199 h.flash(msg, category='error')
200 200 if c.rhodecode_user.is_admin:
201 201 return redirect(url('repos'))
202 202 return redirect(url('home'))
203 203 #redirect to our new repo !
204 204 return redirect(url('summary_home', repo_name=new_repo.repo_name))
205 205
206 206 @NotAnonymous()
207 207 def create_repository(self):
208 208 """GET /_admin/create_repository: Form to create a new item"""
209 209 new_repo = request.GET.get('repo', '')
210 210 parent_group = request.GET.get('parent_group')
211 211 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
212 212 #you're not super admin nor have global create permissions,
213 213 #but maybe you have at least write permission to a parent group ?
214 214 _gr = RepoGroup.get(parent_group)
215 215 gr_name = _gr.group_name if _gr else None
216 216 if not HasReposGroupPermissionAny('group.admin', 'group.write')(group_name=gr_name):
217 217 raise HTTPForbidden
218 218
219 219 acl_groups = GroupList(RepoGroup.query().all(),
220 220 perm_set=['group.write', 'group.admin'])
221 221 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
222 222 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
223 223 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
224 224
225 225 c.new_repo = repo_name_slug(new_repo)
226 226
227 227 ## apply the defaults from defaults page
228 228 defaults = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
229 229 if parent_group:
230 230 defaults.update({'repo_group': parent_group})
231 231
232 232 return htmlfill.render(
233 233 render('admin/repos/repo_add.html'),
234 234 defaults=defaults,
235 235 errors={},
236 236 prefix_error=False,
237 237 encoding="UTF-8"
238 238 )
239 239
240 240 @HasRepoPermissionAllDecorator('repository.admin')
241 241 def update(self, repo_name):
242 242 """
243 243 PUT /repos/repo_name: Update an existing item"""
244 244 # Forms posted to this method should contain a hidden field:
245 245 # <input type="hidden" name="_method" value="PUT" />
246 246 # Or using helpers:
247 247 # h.form(url('repo', repo_name=ID),
248 248 # method='put')
249 249 # url('repo', repo_name=ID)
250 250 self.__load_defaults()
251 251 repo_model = RepoModel()
252 252 changed_name = repo_name
253 253 #override the choices with extracted revisions !
254 254 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
255 255 c.landing_revs_choices = choices
256 256 repo = Repository.get_by_repo_name(repo_name)
257 257 _form = RepoForm(edit=True, old_data={'repo_name': repo_name,
258 258 'repo_group': repo.group.get_dict() \
259 259 if repo.group else {}},
260 260 repo_groups=c.repo_groups_choices,
261 261 landing_revs=c.landing_revs_choices)()
262 262 try:
263 263 form_result = _form.to_python(dict(request.POST))
264 264 repo = repo_model.update(repo_name, **form_result)
265 invalidate_cache('get_repo_cached_%s' % repo_name)
265 ScmModel().mark_for_invalidation(repo_name)
266 266 h.flash(_('Repository %s updated successfully') % repo_name,
267 267 category='success')
268 268 changed_name = repo.repo_name
269 269 action_logger(self.rhodecode_user, 'admin_updated_repo',
270 270 changed_name, self.ip_addr, self.sa)
271 271 Session().commit()
272 272 except formencode.Invalid, errors:
273 273 defaults = self.__load_data(repo_name)
274 274 defaults.update(errors.value)
275 275 return htmlfill.render(
276 276 render('admin/repos/repo_edit.html'),
277 277 defaults=defaults,
278 278 errors=errors.error_dict or {},
279 279 prefix_error=False,
280 280 encoding="UTF-8")
281 281
282 282 except Exception:
283 283 log.error(traceback.format_exc())
284 284 h.flash(_('Error occurred during update of repository %s') \
285 285 % repo_name, category='error')
286 286 return redirect(url('edit_repo', repo_name=changed_name))
287 287
288 288 @HasRepoPermissionAllDecorator('repository.admin')
289 289 def delete(self, repo_name):
290 290 """
291 291 DELETE /repos/repo_name: Delete an existing item"""
292 292 # Forms posted to this method should contain a hidden field:
293 293 # <input type="hidden" name="_method" value="DELETE" />
294 294 # Or using helpers:
295 295 # h.form(url('repo', repo_name=ID),
296 296 # method='delete')
297 297 # url('repo', repo_name=ID)
298 298
299 299 repo_model = RepoModel()
300 300 repo = repo_model.get_by_repo_name(repo_name)
301 301 if not repo:
302 302 h.not_mapped_error(repo_name)
303 303 return redirect(url('repos'))
304 304 try:
305 305 _forks = repo.forks.count()
306 306 handle_forks = None
307 307 if _forks and request.POST.get('forks'):
308 308 do = request.POST['forks']
309 309 if do == 'detach_forks':
310 310 handle_forks = 'detach'
311 311 h.flash(_('Detached %s forks') % _forks, category='success')
312 312 elif do == 'delete_forks':
313 313 handle_forks = 'delete'
314 314 h.flash(_('Deleted %s forks') % _forks, category='success')
315 315 repo_model.delete(repo, forks=handle_forks)
316 316 action_logger(self.rhodecode_user, 'admin_deleted_repo',
317 317 repo_name, self.ip_addr, self.sa)
318 invalidate_cache('get_repo_cached_%s' % repo_name)
318 ScmModel().mark_for_invalidation(repo_name)
319 319 h.flash(_('Deleted repository %s') % repo_name, category='success')
320 320 Session().commit()
321 321 except AttachedForksError:
322 322 h.flash(_('Cannot delete %s it still contains attached forks')
323 323 % repo_name, category='warning')
324 324
325 325 except Exception:
326 326 log.error(traceback.format_exc())
327 327 h.flash(_('An error occurred during deletion of %s') % repo_name,
328 328 category='error')
329 329
330 330 return redirect(url('repos'))
331 331
332 332 @HasRepoPermissionAllDecorator('repository.admin')
333 333 def set_repo_perm_member(self, repo_name):
334 334 form = RepoPermsForm()().to_python(request.POST)
335 335
336 336 perms_new = form['perms_new']
337 337 perms_updates = form['perms_updates']
338 338 cur_repo = repo_name
339 339
340 340 # update permissions
341 341 for member, perm, member_type in perms_updates:
342 342 if member_type == 'user':
343 343 # this updates existing one
344 344 RepoModel().grant_user_permission(
345 345 repo=cur_repo, user=member, perm=perm
346 346 )
347 347 else:
348 348 RepoModel().grant_users_group_permission(
349 349 repo=cur_repo, group_name=member, perm=perm
350 350 )
351 351 # set new permissions
352 352 for member, perm, member_type in perms_new:
353 353 if member_type == 'user':
354 354 RepoModel().grant_user_permission(
355 355 repo=cur_repo, user=member, perm=perm
356 356 )
357 357 else:
358 358 RepoModel().grant_users_group_permission(
359 359 repo=cur_repo, group_name=member, perm=perm
360 360 )
361 361 #TODO: implement this
362 362 #action_logger(self.rhodecode_user, 'admin_changed_repo_permissions',
363 363 # repo_name, self.ip_addr, self.sa)
364 364 Session().commit()
365 365 h.flash(_('Repository permissions updated'), category='success')
366 366 return redirect(url('edit_repo', repo_name=repo_name))
367 367
368 368 @HasRepoPermissionAllDecorator('repository.admin')
369 369 def delete_perm_user(self, repo_name):
370 370 """
371 371 DELETE an existing repository permission user
372 372
373 373 :param repo_name:
374 374 """
375 375 try:
376 376 RepoModel().revoke_user_permission(repo=repo_name,
377 377 user=request.POST['user_id'])
378 378 #TODO: implement this
379 379 #action_logger(self.rhodecode_user, 'admin_revoked_repo_permissions',
380 380 # repo_name, self.ip_addr, self.sa)
381 381 Session().commit()
382 382 except Exception:
383 383 log.error(traceback.format_exc())
384 384 h.flash(_('An error occurred during deletion of repository user'),
385 385 category='error')
386 386 raise HTTPInternalServerError()
387 387
388 388 @HasRepoPermissionAllDecorator('repository.admin')
389 389 def delete_perm_users_group(self, repo_name):
390 390 """
391 391 DELETE an existing repository permission user group
392 392
393 393 :param repo_name:
394 394 """
395 395
396 396 try:
397 397 RepoModel().revoke_users_group_permission(
398 398 repo=repo_name, group_name=request.POST['users_group_id']
399 399 )
400 400 Session().commit()
401 401 except Exception:
402 402 log.error(traceback.format_exc())
403 403 h.flash(_('An error occurred during deletion of repository'
404 404 ' user groups'),
405 405 category='error')
406 406 raise HTTPInternalServerError()
407 407
408 408 @HasRepoPermissionAllDecorator('repository.admin')
409 409 def repo_stats(self, repo_name):
410 410 """
411 411 DELETE an existing repository statistics
412 412
413 413 :param repo_name:
414 414 """
415 415
416 416 try:
417 417 RepoModel().delete_stats(repo_name)
418 418 Session().commit()
419 419 except Exception, e:
420 420 log.error(traceback.format_exc())
421 421 h.flash(_('An error occurred during deletion of repository stats'),
422 422 category='error')
423 423 return redirect(url('edit_repo', repo_name=repo_name))
424 424
425 425 @HasRepoPermissionAllDecorator('repository.admin')
426 426 def repo_cache(self, repo_name):
427 427 """
428 428 INVALIDATE existing repository cache
429 429
430 430 :param repo_name:
431 431 """
432 432
433 433 try:
434 434 ScmModel().mark_for_invalidation(repo_name)
435 435 Session().commit()
436 436 except Exception, e:
437 437 log.error(traceback.format_exc())
438 438 h.flash(_('An error occurred during cache invalidation'),
439 439 category='error')
440 440 return redirect(url('edit_repo', repo_name=repo_name))
441 441
442 442 @HasRepoPermissionAllDecorator('repository.admin')
443 443 def repo_locking(self, repo_name):
444 444 """
445 445 Unlock repository when it is locked !
446 446
447 447 :param repo_name:
448 448 """
449 449
450 450 try:
451 451 repo = Repository.get_by_repo_name(repo_name)
452 452 if request.POST.get('set_lock'):
453 453 Repository.lock(repo, c.rhodecode_user.user_id)
454 454 elif request.POST.get('set_unlock'):
455 455 Repository.unlock(repo)
456 456 except Exception, e:
457 457 log.error(traceback.format_exc())
458 458 h.flash(_('An error occurred during unlocking'),
459 459 category='error')
460 460 return redirect(url('edit_repo', repo_name=repo_name))
461 461
462 462 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
463 463 def toggle_locking(self, repo_name):
464 464 """
465 465 Toggle locking of repository by simple GET call to url
466 466
467 467 :param repo_name:
468 468 """
469 469
470 470 try:
471 471 repo = Repository.get_by_repo_name(repo_name)
472 472
473 473 if repo.enable_locking:
474 474 if repo.locked[0]:
475 475 Repository.unlock(repo)
476 476 action = _('Unlocked')
477 477 else:
478 478 Repository.lock(repo, c.rhodecode_user.user_id)
479 479 action = _('Locked')
480 480
481 481 h.flash(_('Repository has been %s') % action,
482 482 category='success')
483 483 except Exception, e:
484 484 log.error(traceback.format_exc())
485 485 h.flash(_('An error occurred during unlocking'),
486 486 category='error')
487 487 return redirect(url('summary_home', repo_name=repo_name))
488 488
489 489 @HasRepoPermissionAllDecorator('repository.admin')
490 490 def repo_public_journal(self, repo_name):
491 491 """
492 492 Set's this repository to be visible in public journal,
493 493 in other words assing default user to follow this repo
494 494
495 495 :param repo_name:
496 496 """
497 497
498 498 cur_token = request.POST.get('auth_token')
499 499 token = get_token()
500 500 if cur_token == token:
501 501 try:
502 502 repo_id = Repository.get_by_repo_name(repo_name).repo_id
503 503 user_id = User.get_by_username('default').user_id
504 504 self.scm_model.toggle_following_repo(repo_id, user_id)
505 505 h.flash(_('Updated repository visibility in public journal'),
506 506 category='success')
507 507 Session().commit()
508 508 except Exception:
509 509 h.flash(_('An error occurred during setting this'
510 510 ' repository in public journal'),
511 511 category='error')
512 512
513 513 else:
514 514 h.flash(_('Token mismatch'), category='error')
515 515 return redirect(url('edit_repo', repo_name=repo_name))
516 516
517 517 @HasRepoPermissionAllDecorator('repository.admin')
518 518 def repo_pull(self, repo_name):
519 519 """
520 520 Runs task to update given repository with remote changes,
521 521 ie. make pull on remote location
522 522
523 523 :param repo_name:
524 524 """
525 525 try:
526 526 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
527 527 h.flash(_('Pulled from remote location'), category='success')
528 528 except Exception, e:
529 529 h.flash(_('An error occurred during pull from remote location'),
530 530 category='error')
531 531
532 532 return redirect(url('edit_repo', repo_name=repo_name))
533 533
534 534 @HasRepoPermissionAllDecorator('repository.admin')
535 535 def repo_as_fork(self, repo_name):
536 536 """
537 537 Mark given repository as a fork of another
538 538
539 539 :param repo_name:
540 540 """
541 541 try:
542 542 fork_id = request.POST.get('id_fork_of')
543 543 repo = ScmModel().mark_as_fork(repo_name, fork_id,
544 544 self.rhodecode_user.username)
545 545 fork = repo.fork.repo_name if repo.fork else _('Nothing')
546 546 Session().commit()
547 547 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
548 548 category='success')
549 549 except Exception, e:
550 550 log.error(traceback.format_exc())
551 551 h.flash(_('An error occurred during this operation'),
552 552 category='error')
553 553
554 554 return redirect(url('edit_repo', repo_name=repo_name))
555 555
556 556 @HasPermissionAllDecorator('hg.admin')
557 557 def show(self, repo_name, format='html'):
558 558 """GET /repos/repo_name: Show a specific item"""
559 559 # url('repo', repo_name=ID)
560 560
561 561 @HasRepoPermissionAllDecorator('repository.admin')
562 562 def edit(self, repo_name, format='html'):
563 563 """GET /repos/repo_name/edit: Form to edit an existing item"""
564 564 # url('edit_repo', repo_name=ID)
565 565 defaults = self.__load_data(repo_name)
566 566
567 567 return htmlfill.render(
568 568 render('admin/repos/repo_edit.html'),
569 569 defaults=defaults,
570 570 encoding="UTF-8",
571 571 force_defaults=False
572 572 )
573 573
574 574 @HasPermissionAllDecorator('hg.admin')
575 575 def create_repo_field(self, repo_name):
576 576 try:
577 577 form_result = RepoFieldForm()().to_python(dict(request.POST))
578 578 new_field = RepositoryField()
579 579 new_field.repository = Repository.get_by_repo_name(repo_name)
580 580 new_field.field_key = form_result['new_field_key']
581 581 new_field.field_type = form_result['new_field_type'] # python type
582 582 new_field.field_value = form_result['new_field_value'] # set initial blank value
583 583 new_field.field_desc = form_result['new_field_desc']
584 584 new_field.field_label = form_result['new_field_label']
585 585 Session().add(new_field)
586 586 Session().commit()
587 587
588 588 except Exception, e:
589 589 log.error(traceback.format_exc())
590 590 msg = _('An error occurred during creation of field')
591 591 if isinstance(e, formencode.Invalid):
592 592 msg += ". " + e.msg
593 593 h.flash(msg, category='error')
594 594 return redirect(url('edit_repo', repo_name=repo_name))
595 595
596 596 @HasPermissionAllDecorator('hg.admin')
597 597 def delete_repo_field(self, repo_name, field_id):
598 598 field = RepositoryField.get_or_404(field_id)
599 599 try:
600 600 Session().delete(field)
601 601 Session().commit()
602 602 except Exception, e:
603 603 log.error(traceback.format_exc())
604 604 msg = _('An error occurred during removal of field')
605 605 h.flash(msg, category='error')
606 606 return redirect(url('edit_repo', repo_name=repo_name))
@@ -1,516 +1,515 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 settings controller for rhodecode admin
7 7
8 8 :created_on: Jul 14, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 import pkg_resources
30 30 import platform
31 31
32 32 from sqlalchemy import func
33 33 from formencode import htmlfill
34 34 from pylons import request, session, tmpl_context as c, url, config
35 35 from pylons.controllers.util import abort, redirect
36 36 from pylons.i18n.translation import _
37 37
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 HasPermissionAnyDecorator, NotAnonymous, HasPermissionAny,\
41 41 HasReposGroupPermissionAll, HasReposGroupPermissionAny, AuthUser
42 42 from rhodecode.lib.base import BaseController, render
43 43 from rhodecode.lib.celerylib import tasks, run_task
44 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
45 set_rhodecode_config, repo_name_slug, check_git_version
44 from rhodecode.lib.utils import repo2db_mapper, set_rhodecode_config, \
45 check_git_version
46 46 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
47 47 RhodeCodeSetting, PullRequest, PullRequestReviewers
48 48 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
49 49 ApplicationUiSettingsForm, ApplicationVisualisationForm
50 50 from rhodecode.model.scm import ScmModel, GroupList
51 51 from rhodecode.model.user import UserModel
52 52 from rhodecode.model.repo import RepoModel
53 53 from rhodecode.model.db import User
54 54 from rhodecode.model.notification import EmailNotificationModel
55 55 from rhodecode.model.meta import Session
56 56 from rhodecode.lib.utils2 import str2bool, safe_unicode
57 57 from rhodecode.lib.compat import json
58 from webob.exc import HTTPForbidden
59 58 log = logging.getLogger(__name__)
60 59
61 60
62 61 class SettingsController(BaseController):
63 62 """REST Controller styled on the Atom Publishing Protocol"""
64 63 # To properly map this controller, ensure your config/routing.py
65 64 # file has a resource setup:
66 65 # map.resource('setting', 'settings', controller='admin/settings',
67 66 # path_prefix='/admin', name_prefix='admin_')
68 67
69 68 @LoginRequired()
70 69 def __before__(self):
71 70 c.admin_user = session.get('admin_user')
72 71 c.admin_username = session.get('admin_username')
73 72 c.modules = sorted([(p.project_name, p.version)
74 73 for p in pkg_resources.working_set]
75 74 + [('git', check_git_version())],
76 75 key=lambda k: k[0].lower())
77 76 c.py_version = platform.python_version()
78 77 c.platform = platform.platform()
79 78 super(SettingsController, self).__before__()
80 79
81 80 @HasPermissionAllDecorator('hg.admin')
82 81 def index(self, format='html'):
83 82 """GET /admin/settings: All items in the collection"""
84 83 # url('admin_settings')
85 84
86 85 defaults = RhodeCodeSetting.get_app_settings()
87 86 defaults.update(self._get_hg_ui_settings())
88 87
89 88 return htmlfill.render(
90 89 render('admin/settings/settings.html'),
91 90 defaults=defaults,
92 91 encoding="UTF-8",
93 92 force_defaults=False
94 93 )
95 94
96 95 @HasPermissionAllDecorator('hg.admin')
97 96 def create(self):
98 97 """POST /admin/settings: Create a new item"""
99 98 # url('admin_settings')
100 99
101 100 @HasPermissionAllDecorator('hg.admin')
102 101 def new(self, format='html'):
103 102 """GET /admin/settings/new: Form to create a new item"""
104 103 # url('admin_new_setting')
105 104
106 105 @HasPermissionAllDecorator('hg.admin')
107 106 def update(self, setting_id):
108 107 """PUT /admin/settings/setting_id: Update an existing item"""
109 108 # Forms posted to this method should contain a hidden field:
110 109 # <input type="hidden" name="_method" value="PUT" />
111 110 # Or using helpers:
112 111 # h.form(url('admin_setting', setting_id=ID),
113 112 # method='put')
114 113 # url('admin_setting', setting_id=ID)
115 114
116 115 if setting_id == 'mapping':
117 116 rm_obsolete = request.POST.get('destroy', False)
118 117 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
119 118 initial = ScmModel().repo_scan()
120 119 log.debug('invalidating all repositories')
121 120 for repo_name in initial.keys():
122 invalidate_cache('get_repo_cached_%s' % repo_name)
121 ScmModel().mark_for_invalidation(repo_name)
123 122
124 123 added, removed = repo2db_mapper(initial, rm_obsolete)
125 124 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
126 125 h.flash(_('Repositories successfully '
127 126 'rescanned added: %s ; removed: %s') %
128 127 (_repr(added), _repr(removed)),
129 128 category='success')
130 129
131 130 if setting_id == 'whoosh':
132 131 repo_location = self._get_hg_ui_settings()['paths_root_path']
133 132 full_index = request.POST.get('full_index', False)
134 133 run_task(tasks.whoosh_index, repo_location, full_index)
135 134 h.flash(_('Whoosh reindex task scheduled'), category='success')
136 135
137 136 if setting_id == 'global':
138 137
139 138 application_form = ApplicationSettingsForm()()
140 139 try:
141 140 form_result = application_form.to_python(dict(request.POST))
142 141 except formencode.Invalid, errors:
143 142 return htmlfill.render(
144 143 render('admin/settings/settings.html'),
145 144 defaults=errors.value,
146 145 errors=errors.error_dict or {},
147 146 prefix_error=False,
148 147 encoding="UTF-8"
149 148 )
150 149
151 150 try:
152 151 sett1 = RhodeCodeSetting.get_by_name_or_create('title')
153 152 sett1.app_settings_value = form_result['rhodecode_title']
154 153 Session().add(sett1)
155 154
156 155 sett2 = RhodeCodeSetting.get_by_name_or_create('realm')
157 156 sett2.app_settings_value = form_result['rhodecode_realm']
158 157 Session().add(sett2)
159 158
160 159 sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code')
161 160 sett3.app_settings_value = form_result['rhodecode_ga_code']
162 161 Session().add(sett3)
163 162
164 163 Session().commit()
165 164 set_rhodecode_config(config)
166 165 h.flash(_('Updated application settings'), category='success')
167 166
168 167 except Exception:
169 168 log.error(traceback.format_exc())
170 169 h.flash(_('Error occurred during updating '
171 170 'application settings'),
172 171 category='error')
173 172
174 173 if setting_id == 'visual':
175 174
176 175 application_form = ApplicationVisualisationForm()()
177 176 try:
178 177 form_result = application_form.to_python(dict(request.POST))
179 178 except formencode.Invalid, errors:
180 179 return htmlfill.render(
181 180 render('admin/settings/settings.html'),
182 181 defaults=errors.value,
183 182 errors=errors.error_dict or {},
184 183 prefix_error=False,
185 184 encoding="UTF-8"
186 185 )
187 186
188 187 try:
189 188 sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon')
190 189 sett1.app_settings_value = \
191 190 form_result['rhodecode_show_public_icon']
192 191 Session().add(sett1)
193 192
194 193 sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon')
195 194 sett2.app_settings_value = \
196 195 form_result['rhodecode_show_private_icon']
197 196 Session().add(sett2)
198 197
199 198 sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags')
200 199 sett3.app_settings_value = \
201 200 form_result['rhodecode_stylify_metatags']
202 201 Session().add(sett3)
203 202
204 203 sett4 = RhodeCodeSetting.get_by_name_or_create('lightweight_dashboard')
205 204 sett4.app_settings_value = \
206 205 form_result['rhodecode_lightweight_dashboard']
207 206 Session().add(sett4)
208 207
209 208 sett4 = RhodeCodeSetting.get_by_name_or_create('repository_fields')
210 209 sett4.app_settings_value = \
211 210 form_result['rhodecode_repository_fields']
212 211 Session().add(sett4)
213 212
214 213 Session().commit()
215 214 set_rhodecode_config(config)
216 215 h.flash(_('Updated visualisation settings'),
217 216 category='success')
218 217
219 218 except Exception:
220 219 log.error(traceback.format_exc())
221 220 h.flash(_('Error occurred during updating '
222 221 'visualisation settings'),
223 222 category='error')
224 223
225 224 if setting_id == 'vcs':
226 225 application_form = ApplicationUiSettingsForm()()
227 226 try:
228 227 form_result = application_form.to_python(dict(request.POST))
229 228 except formencode.Invalid, errors:
230 229 return htmlfill.render(
231 230 render('admin/settings/settings.html'),
232 231 defaults=errors.value,
233 232 errors=errors.error_dict or {},
234 233 prefix_error=False,
235 234 encoding="UTF-8"
236 235 )
237 236
238 237 try:
239 238 sett = RhodeCodeUi.get_by_key('push_ssl')
240 239 sett.ui_value = form_result['web_push_ssl']
241 240 Session().add(sett)
242 241
243 242 sett = RhodeCodeUi.get_by_key('/')
244 243 sett.ui_value = form_result['paths_root_path']
245 244 Session().add(sett)
246 245
247 246 #HOOKS
248 247 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE)
249 248 sett.ui_active = form_result['hooks_changegroup_update']
250 249 Session().add(sett)
251 250
252 251 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_REPO_SIZE)
253 252 sett.ui_active = form_result['hooks_changegroup_repo_size']
254 253 Session().add(sett)
255 254
256 255 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PUSH)
257 256 sett.ui_active = form_result['hooks_changegroup_push_logger']
258 257 Session().add(sett)
259 258
260 259 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PULL)
261 260 sett.ui_active = form_result['hooks_outgoing_pull_logger']
262 261
263 262 Session().add(sett)
264 263
265 264 ## EXTENSIONS
266 265 sett = RhodeCodeUi.get_by_key('largefiles')
267 266 if not sett:
268 267 #make one if it's not there !
269 268 sett = RhodeCodeUi()
270 269 sett.ui_key = 'largefiles'
271 270 sett.ui_section = 'extensions'
272 271 sett.ui_active = form_result['extensions_largefiles']
273 272 Session().add(sett)
274 273
275 274 sett = RhodeCodeUi.get_by_key('hgsubversion')
276 275 if not sett:
277 276 #make one if it's not there !
278 277 sett = RhodeCodeUi()
279 278 sett.ui_key = 'hgsubversion'
280 279 sett.ui_section = 'extensions'
281 280
282 281 sett.ui_active = form_result['extensions_hgsubversion']
283 282 Session().add(sett)
284 283
285 284 # sett = RhodeCodeUi.get_by_key('hggit')
286 285 # if not sett:
287 286 # #make one if it's not there !
288 287 # sett = RhodeCodeUi()
289 288 # sett.ui_key = 'hggit'
290 289 # sett.ui_section = 'extensions'
291 290 #
292 291 # sett.ui_active = form_result['extensions_hggit']
293 292 # Session().add(sett)
294 293
295 294 Session().commit()
296 295
297 296 h.flash(_('Updated VCS settings'), category='success')
298 297
299 298 except Exception:
300 299 log.error(traceback.format_exc())
301 300 h.flash(_('Error occurred during updating '
302 301 'application settings'), category='error')
303 302
304 303 if setting_id == 'hooks':
305 304 ui_key = request.POST.get('new_hook_ui_key')
306 305 ui_value = request.POST.get('new_hook_ui_value')
307 306 try:
308 307
309 308 if ui_value and ui_key:
310 309 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
311 310 h.flash(_('Added new hook'),
312 311 category='success')
313 312
314 313 # check for edits
315 314 update = False
316 315 _d = request.POST.dict_of_lists()
317 316 for k, v in zip(_d.get('hook_ui_key', []),
318 317 _d.get('hook_ui_value_new', [])):
319 318 RhodeCodeUi.create_or_update_hook(k, v)
320 319 update = True
321 320
322 321 if update:
323 322 h.flash(_('Updated hooks'), category='success')
324 323 Session().commit()
325 324 except Exception:
326 325 log.error(traceback.format_exc())
327 326 h.flash(_('Error occurred during hook creation'),
328 327 category='error')
329 328
330 329 return redirect(url('admin_edit_setting', setting_id='hooks'))
331 330
332 331 if setting_id == 'email':
333 332 test_email = request.POST.get('test_email')
334 333 test_email_subj = 'RhodeCode TestEmail'
335 334 test_email_body = 'RhodeCode Email test'
336 335
337 336 test_email_html_body = EmailNotificationModel()\
338 337 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
339 338 body=test_email_body)
340 339
341 340 recipients = [test_email] if test_email else None
342 341
343 342 run_task(tasks.send_email, recipients, test_email_subj,
344 343 test_email_body, test_email_html_body)
345 344
346 345 h.flash(_('Email task created'), category='success')
347 346 return redirect(url('admin_settings'))
348 347
349 348 @HasPermissionAllDecorator('hg.admin')
350 349 def delete(self, setting_id):
351 350 """DELETE /admin/settings/setting_id: Delete an existing item"""
352 351 # Forms posted to this method should contain a hidden field:
353 352 # <input type="hidden" name="_method" value="DELETE" />
354 353 # Or using helpers:
355 354 # h.form(url('admin_setting', setting_id=ID),
356 355 # method='delete')
357 356 # url('admin_setting', setting_id=ID)
358 357 if setting_id == 'hooks':
359 358 hook_id = request.POST.get('hook_id')
360 359 RhodeCodeUi.delete(hook_id)
361 360 Session().commit()
362 361
363 362 @HasPermissionAllDecorator('hg.admin')
364 363 def show(self, setting_id, format='html'):
365 364 """
366 365 GET /admin/settings/setting_id: Show a specific item"""
367 366 # url('admin_setting', setting_id=ID)
368 367
369 368 @HasPermissionAllDecorator('hg.admin')
370 369 def edit(self, setting_id, format='html'):
371 370 """
372 371 GET /admin/settings/setting_id/edit: Form to
373 372 edit an existing item"""
374 373 # url('admin_edit_setting', setting_id=ID)
375 374
376 375 c.hooks = RhodeCodeUi.get_builtin_hooks()
377 376 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
378 377
379 378 return htmlfill.render(
380 379 render('admin/settings/hooks.html'),
381 380 defaults={},
382 381 encoding="UTF-8",
383 382 force_defaults=False
384 383 )
385 384
386 385 def _load_my_repos_data(self):
387 386 repos_list = Session().query(Repository)\
388 387 .filter(Repository.user_id ==
389 388 self.rhodecode_user.user_id)\
390 389 .order_by(func.lower(Repository.repo_name)).all()
391 390
392 391 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
393 392 admin=True)
394 393 #json used to render the grid
395 394 return json.dumps(repos_data)
396 395
397 396 @NotAnonymous()
398 397 def my_account(self):
399 398 """
400 399 GET /_admin/my_account Displays info about my account
401 400 """
402 401 # url('admin_settings_my_account')
403 402
404 403 c.user = User.get(self.rhodecode_user.user_id)
405 404 c.perm_user = AuthUser(user_id=self.rhodecode_user.user_id,
406 405 ip_addr=self.ip_addr)
407 406 c.ldap_dn = c.user.ldap_dn
408 407
409 408 if c.user.username == 'default':
410 409 h.flash(_("You can't edit this user since it's"
411 410 " crucial for entire application"), category='warning')
412 411 return redirect(url('users'))
413 412
414 413 #json used to render the grid
415 414 c.data = self._load_my_repos_data()
416 415
417 416 defaults = c.user.get_dict()
418 417
419 418 c.form = htmlfill.render(
420 419 render('admin/users/user_edit_my_account_form.html'),
421 420 defaults=defaults,
422 421 encoding="UTF-8",
423 422 force_defaults=False
424 423 )
425 424 return render('admin/users/user_edit_my_account.html')
426 425
427 426 @NotAnonymous()
428 427 def my_account_update(self):
429 428 """PUT /_admin/my_account_update: Update an existing item"""
430 429 # Forms posted to this method should contain a hidden field:
431 430 # <input type="hidden" name="_method" value="PUT" />
432 431 # Or using helpers:
433 432 # h.form(url('admin_settings_my_account_update'),
434 433 # method='put')
435 434 # url('admin_settings_my_account_update', id=ID)
436 435 uid = self.rhodecode_user.user_id
437 436 c.user = User.get(self.rhodecode_user.user_id)
438 437 c.perm_user = AuthUser(user_id=self.rhodecode_user.user_id,
439 438 ip_addr=self.ip_addr)
440 439 c.ldap_dn = c.user.ldap_dn
441 440 email = self.rhodecode_user.email
442 441 _form = UserForm(edit=True,
443 442 old_data={'user_id': uid, 'email': email})()
444 443 form_result = {}
445 444 try:
446 445 form_result = _form.to_python(dict(request.POST))
447 446 skip_attrs = ['admin', 'active'] # skip attr for my account
448 447 if c.ldap_dn:
449 448 #forbid updating username for ldap accounts
450 449 skip_attrs.append('username')
451 450 UserModel().update(uid, form_result, skip_attrs=skip_attrs)
452 451 h.flash(_('Your account was updated successfully'),
453 452 category='success')
454 453 Session().commit()
455 454 except formencode.Invalid, errors:
456 455 #json used to render the grid
457 456 c.data = self._load_my_repos_data()
458 457 c.form = htmlfill.render(
459 458 render('admin/users/user_edit_my_account_form.html'),
460 459 defaults=errors.value,
461 460 errors=errors.error_dict or {},
462 461 prefix_error=False,
463 462 encoding="UTF-8")
464 463 return render('admin/users/user_edit_my_account.html')
465 464 except Exception:
466 465 log.error(traceback.format_exc())
467 466 h.flash(_('Error occurred during update of user %s') \
468 467 % form_result.get('username'), category='error')
469 468
470 469 return redirect(url('my_account'))
471 470
472 471 @NotAnonymous()
473 472 def my_account_my_pullrequests(self):
474 473 c.show_closed = request.GET.get('pr_show_closed')
475 474
476 475 def _filter(pr):
477 476 s = sorted(pr, key=lambda o: o.created_on, reverse=True)
478 477 if not c.show_closed:
479 478 s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
480 479 return s
481 480
482 481 c.my_pull_requests = _filter(PullRequest.query()\
483 482 .filter(PullRequest.user_id ==
484 483 self.rhodecode_user.user_id)\
485 484 .all())
486 485
487 486 c.participate_in_pull_requests = _filter([
488 487 x.pull_request for x in PullRequestReviewers.query()\
489 488 .filter(PullRequestReviewers.user_id ==
490 489 self.rhodecode_user.user_id).all()])
491 490
492 491 return render('admin/users/user_edit_my_account_pullrequests.html')
493 492
494 493 def _get_hg_ui_settings(self):
495 494 ret = RhodeCodeUi.query().all()
496 495
497 496 if not ret:
498 497 raise Exception('Could not get application ui settings !')
499 498 settings = {}
500 499 for each in ret:
501 500 k = each.ui_key
502 501 v = each.ui_value
503 502 if k == '/':
504 503 k = 'root_path'
505 504
506 505 if k == 'push_ssl':
507 506 v = str2bool(v)
508 507
509 508 if k.find('.') != -1:
510 509 k = k.replace('.', '_')
511 510
512 511 if each.ui_section in ['hooks', 'extensions']:
513 512 v = each.ui_active
514 513
515 514 settings[each.ui_section + '_' + k] = v
516 515 return settings
@@ -1,345 +1,345 b''
1 1 """The base Controller API
2 2
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 import logging
6 6 import time
7 7 import traceback
8 8
9 9 from paste.auth.basic import AuthBasicAuthenticator
10 10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 11 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
12 12
13 13 from pylons import config, tmpl_context as c, request, session, url
14 14 from pylons.controllers import WSGIController
15 15 from pylons.controllers.util import redirect
16 16 from pylons.templating import render_mako as render
17 17
18 18 from rhodecode import __version__, BACKENDS
19 19
20 20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
21 21 safe_str, safe_int
22 22 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
23 23 HasPermissionAnyMiddleware, CookieStoreWrapper
24 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
24 from rhodecode.lib.utils import get_repo_slug
25 25 from rhodecode.model import meta
26 26
27 27 from rhodecode.model.db import Repository, RhodeCodeUi, User, RhodeCodeSetting
28 28 from rhodecode.model.notification import NotificationModel
29 29 from rhodecode.model.scm import ScmModel
30 30 from rhodecode.model.meta import Session
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 def _filter_proxy(ip):
36 36 """
37 37 HEADERS can have mutliple ips inside the left-most being the original
38 38 client, and each successive proxy that passed the request adding the IP
39 39 address where it received the request from.
40 40
41 41 :param ip:
42 42 """
43 43 if ',' in ip:
44 44 _ips = ip.split(',')
45 45 _first_ip = _ips[0].strip()
46 46 log.debug('Got multiple IPs %s, using %s' % (','.join(_ips), _first_ip))
47 47 return _first_ip
48 48 return ip
49 49
50 50
51 51 def _get_ip_addr(environ):
52 52 proxy_key = 'HTTP_X_REAL_IP'
53 53 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
54 54 def_key = 'REMOTE_ADDR'
55 55
56 56 ip = environ.get(proxy_key)
57 57 if ip:
58 58 return _filter_proxy(ip)
59 59
60 60 ip = environ.get(proxy_key2)
61 61 if ip:
62 62 return _filter_proxy(ip)
63 63
64 64 ip = environ.get(def_key, '0.0.0.0')
65 65 return _filter_proxy(ip)
66 66
67 67
68 68 def _get_access_path(environ):
69 69 path = environ.get('PATH_INFO')
70 70 org_req = environ.get('pylons.original_request')
71 71 if org_req:
72 72 path = org_req.environ.get('PATH_INFO')
73 73 return path
74 74
75 75
76 76 class BasicAuth(AuthBasicAuthenticator):
77 77
78 78 def __init__(self, realm, authfunc, auth_http_code=None):
79 79 self.realm = realm
80 80 self.authfunc = authfunc
81 81 self._rc_auth_http_code = auth_http_code
82 82
83 83 def build_authentication(self):
84 84 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
85 85 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
86 86 # return 403 if alternative http return code is specified in
87 87 # RhodeCode config
88 88 return HTTPForbidden(headers=head)
89 89 return HTTPUnauthorized(headers=head)
90 90
91 91 def authenticate(self, environ):
92 92 authorization = AUTHORIZATION(environ)
93 93 if not authorization:
94 94 return self.build_authentication()
95 95 (authmeth, auth) = authorization.split(' ', 1)
96 96 if 'basic' != authmeth.lower():
97 97 return self.build_authentication()
98 98 auth = auth.strip().decode('base64')
99 99 _parts = auth.split(':', 1)
100 100 if len(_parts) == 2:
101 101 username, password = _parts
102 102 if self.authfunc(environ, username, password):
103 103 return username
104 104 return self.build_authentication()
105 105
106 106 __call__ = authenticate
107 107
108 108
109 109 class BaseVCSController(object):
110 110
111 111 def __init__(self, application, config):
112 112 self.application = application
113 113 self.config = config
114 114 # base path of repo locations
115 115 self.basepath = self.config['base_path']
116 116 #authenticate this mercurial request using authfunc
117 117 self.authenticate = BasicAuth('', authfunc,
118 118 config.get('auth_ret_code'))
119 119 self.ip_addr = '0.0.0.0'
120 120
121 121 def _handle_request(self, environ, start_response):
122 122 raise NotImplementedError()
123 123
124 124 def _get_by_id(self, repo_name):
125 125 """
126 126 Get's a special pattern _<ID> from clone url and tries to replace it
127 127 with a repository_name for support of _<ID> non changable urls
128 128
129 129 :param repo_name:
130 130 """
131 131 try:
132 132 data = repo_name.split('/')
133 133 if len(data) >= 2:
134 134 by_id = data[1].split('_')
135 135 if len(by_id) == 2 and by_id[1].isdigit():
136 136 _repo_name = Repository.get(by_id[1]).repo_name
137 137 data[1] = _repo_name
138 138 except Exception:
139 139 log.debug('Failed to extract repo_name from id %s' % (
140 140 traceback.format_exc()
141 141 )
142 142 )
143 143
144 144 return '/'.join(data)
145 145
146 146 def _invalidate_cache(self, repo_name):
147 147 """
148 148 Set's cache for this repository for invalidation on next access
149 149
150 150 :param repo_name: full repo name, also a cache key
151 151 """
152 invalidate_cache('get_repo_cached_%s' % repo_name)
152 ScmModel().mark_for_invalidation(repo_name)
153 153
154 154 def _check_permission(self, action, user, repo_name, ip_addr=None):
155 155 """
156 156 Checks permissions using action (push/pull) user and repository
157 157 name
158 158
159 159 :param action: push or pull action
160 160 :param user: user instance
161 161 :param repo_name: repository name
162 162 """
163 163 #check IP
164 164 authuser = AuthUser(user_id=user.user_id, ip_addr=ip_addr)
165 165 if not authuser.ip_allowed:
166 166 return False
167 167 else:
168 168 log.info('Access for IP:%s allowed' % (ip_addr))
169 169 if action == 'push':
170 170 if not HasPermissionAnyMiddleware('repository.write',
171 171 'repository.admin')(user,
172 172 repo_name):
173 173 return False
174 174
175 175 else:
176 176 #any other action need at least read permission
177 177 if not HasPermissionAnyMiddleware('repository.read',
178 178 'repository.write',
179 179 'repository.admin')(user,
180 180 repo_name):
181 181 return False
182 182
183 183 return True
184 184
185 185 def _get_ip_addr(self, environ):
186 186 return _get_ip_addr(environ)
187 187
188 188 def _check_ssl(self, environ, start_response):
189 189 """
190 190 Checks the SSL check flag and returns False if SSL is not present
191 191 and required True otherwise
192 192 """
193 193 org_proto = environ['wsgi._org_proto']
194 194 #check if we have SSL required ! if not it's a bad request !
195 195 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
196 196 if require_ssl and org_proto == 'http':
197 197 log.debug('proto is %s and SSL is required BAD REQUEST !'
198 198 % org_proto)
199 199 return False
200 200 return True
201 201
202 202 def _check_locking_state(self, environ, action, repo, user_id):
203 203 """
204 204 Checks locking on this repository, if locking is enabled and lock is
205 205 present returns a tuple of make_lock, locked, locked_by.
206 206 make_lock can have 3 states None (do nothing) True, make lock
207 207 False release lock, This value is later propagated to hooks, which
208 208 do the locking. Think about this as signals passed to hooks what to do.
209 209
210 210 """
211 211 locked = False # defines that locked error should be thrown to user
212 212 make_lock = None
213 213 repo = Repository.get_by_repo_name(repo)
214 214 user = User.get(user_id)
215 215
216 216 # this is kind of hacky, but due to how mercurial handles client-server
217 217 # server see all operation on changeset; bookmarks, phases and
218 218 # obsolescence marker in different transaction, we don't want to check
219 219 # locking on those
220 220 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
221 221 locked_by = repo.locked
222 222 if repo and repo.enable_locking and not obsolete_call:
223 223 if action == 'push':
224 224 #check if it's already locked !, if it is compare users
225 225 user_id, _date = repo.locked
226 226 if user.user_id == user_id:
227 227 log.debug('Got push from user %s, now unlocking' % (user))
228 228 # unlock if we have push from user who locked
229 229 make_lock = False
230 230 else:
231 231 # we're not the same user who locked, ban with 423 !
232 232 locked = True
233 233 if action == 'pull':
234 234 if repo.locked[0] and repo.locked[1]:
235 235 locked = True
236 236 else:
237 237 log.debug('Setting lock on repo %s by %s' % (repo, user))
238 238 make_lock = True
239 239
240 240 else:
241 241 log.debug('Repository %s do not have locking enabled' % (repo))
242 242 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
243 243 % (make_lock, locked, locked_by))
244 244 return make_lock, locked, locked_by
245 245
246 246 def __call__(self, environ, start_response):
247 247 start = time.time()
248 248 try:
249 249 return self._handle_request(environ, start_response)
250 250 finally:
251 251 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
252 252 log.debug('Request time: %.3fs' % (time.time() - start))
253 253 meta.Session.remove()
254 254
255 255
256 256 class BaseController(WSGIController):
257 257
258 258 def __before__(self):
259 259 """
260 260 __before__ is called before controller methods and after __call__
261 261 """
262 262 c.rhodecode_version = __version__
263 263 c.rhodecode_instanceid = config.get('instance_id')
264 264 c.rhodecode_name = config.get('rhodecode_title')
265 265 c.use_gravatar = str2bool(config.get('use_gravatar'))
266 266 c.ga_code = config.get('rhodecode_ga_code')
267 267 # Visual options
268 268 c.visual = AttributeDict({})
269 269 rc_config = RhodeCodeSetting.get_app_settings()
270 270
271 271 c.visual.show_public_icon = str2bool(rc_config.get('rhodecode_show_public_icon'))
272 272 c.visual.show_private_icon = str2bool(rc_config.get('rhodecode_show_private_icon'))
273 273 c.visual.stylify_metatags = str2bool(rc_config.get('rhodecode_stylify_metatags'))
274 274 c.visual.lightweight_dashboard = str2bool(rc_config.get('rhodecode_lightweight_dashboard'))
275 275 c.visual.lightweight_dashboard_items = safe_int(config.get('dashboard_items', 100))
276 276 c.visual.repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
277 277 c.repo_name = get_repo_slug(request) # can be empty
278 278 c.backends = BACKENDS.keys()
279 279 c.unread_notifications = NotificationModel()\
280 280 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
281 281 self.cut_off_limit = int(config.get('cut_off_limit'))
282 282
283 283 self.sa = meta.Session
284 284 self.scm_model = ScmModel(self.sa)
285 285
286 286 def __call__(self, environ, start_response):
287 287 """Invoke the Controller"""
288 288 # WSGIController.__call__ dispatches to the Controller method
289 289 # the request is routed to. This routing information is
290 290 # available in environ['pylons.routes_dict']
291 291 try:
292 292 self.ip_addr = _get_ip_addr(environ)
293 293 # make sure that we update permissions each time we call controller
294 294 api_key = request.GET.get('api_key')
295 295 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
296 296 user_id = cookie_store.get('user_id', None)
297 297 username = get_container_username(environ, config)
298 298 auth_user = AuthUser(user_id, api_key, username, self.ip_addr)
299 299 request.user = auth_user
300 300 self.rhodecode_user = c.rhodecode_user = auth_user
301 301 if not self.rhodecode_user.is_authenticated and \
302 302 self.rhodecode_user.user_id is not None:
303 303 self.rhodecode_user.set_authenticated(
304 304 cookie_store.get('is_authenticated')
305 305 )
306 306 log.info('IP: %s User: %s accessed %s' % (
307 307 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
308 308 )
309 309 return WSGIController.__call__(self, environ, start_response)
310 310 finally:
311 311 meta.Session.remove()
312 312
313 313
314 314 class BaseRepoController(BaseController):
315 315 """
316 316 Base class for controllers responsible for loading all needed data for
317 317 repository loaded items are
318 318
319 319 c.rhodecode_repo: instance of scm repository
320 320 c.rhodecode_db_repo: instance of db
321 321 c.repository_followers: number of followers
322 322 c.repository_forks: number of forks
323 323 c.repository_following: weather the current user is following the current repo
324 324 """
325 325
326 326 def __before__(self):
327 327 super(BaseRepoController, self).__before__()
328 328 if c.repo_name:
329 329
330 330 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
331 331 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
332 332 # update last change according to VCS data
333 333 dbr.update_changeset_cache(dbr.get_changeset())
334 334 if c.rhodecode_repo is None:
335 335 log.error('%s this repository is present in database but it '
336 336 'cannot be created as an scm instance', c.repo_name)
337 337
338 338 redirect(url('home'))
339 339
340 340 # some globals counter for menu
341 341 c.repository_followers = self.scm_model.get_followers(dbr)
342 342 c.repository_forks = self.scm_model.get_forks(dbr)
343 343 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
344 344 c.repository_following = self.scm_model.is_following_repo(c.repo_name,
345 345 self.rhodecode_user.user_id)
@@ -1,792 +1,779 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import re
28 28 import logging
29 29 import datetime
30 30 import traceback
31 31 import paste
32 32 import beaker
33 33 import tarfile
34 34 import shutil
35 35 import decorator
36 36 import warnings
37 37 from os.path import abspath
38 38 from os.path import dirname as dn, join as jn
39 39
40 40 from paste.script.command import Command, BadCommand
41 41
42 42 from mercurial import ui, config
43 43
44 44 from webhelpers.text import collapse, remove_formatting, strip_tags
45 45
46 46 from rhodecode.lib.vcs import get_backend
47 47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
49 49 from rhodecode.lib.vcs.utils.helpers import get_scm
50 50 from rhodecode.lib.vcs.exceptions import VCSError
51 51
52 52 from rhodecode.lib.caching_query import FromCache
53 53
54 54 from rhodecode.model import meta
55 55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
56 56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
57 57 from rhodecode.model.meta import Session
58 58 from rhodecode.model.repos_group import ReposGroupModel
59 59 from rhodecode.lib.utils2 import safe_str, safe_unicode
60 60 from rhodecode.lib.vcs.utils.fakemod import create_module
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
65 65
66 66
67 67 def recursive_replace(str_, replace=' '):
68 68 """
69 69 Recursive replace of given sign to just one instance
70 70
71 71 :param str_: given string
72 72 :param replace: char to find and replace multiple instances
73 73
74 74 Examples::
75 75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
76 76 'Mighty-Mighty-Bo-sstones'
77 77 """
78 78
79 79 if str_.find(replace * 2) == -1:
80 80 return str_
81 81 else:
82 82 str_ = str_.replace(replace * 2, replace)
83 83 return recursive_replace(str_, replace)
84 84
85 85
86 86 def repo_name_slug(value):
87 87 """
88 88 Return slug of name of repository
89 89 This function is called on each creation/modification
90 90 of repository to prevent bad names in repo
91 91 """
92 92
93 93 slug = remove_formatting(value)
94 94 slug = strip_tags(slug)
95 95
96 96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
97 97 slug = slug.replace(c, '-')
98 98 slug = recursive_replace(slug, '-')
99 99 slug = collapse(slug, '-')
100 100 return slug
101 101
102 102
103 103 def get_repo_slug(request):
104 104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105 105 if _repo:
106 106 _repo = _repo.rstrip('/')
107 107 return _repo
108 108
109 109
110 110 def get_repos_group_slug(request):
111 111 _group = request.environ['pylons.routes_dict'].get('group_name')
112 112 if _group:
113 113 _group = _group.rstrip('/')
114 114 return _group
115 115
116 116
117 117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
118 118 """
119 119 Action logger for various actions made by users
120 120
121 121 :param user: user that made this action, can be a unique username string or
122 122 object containing user_id attribute
123 123 :param action: action to log, should be on of predefined unique actions for
124 124 easy translations
125 125 :param repo: string name of repository or object containing repo_id,
126 126 that action was made on
127 127 :param ipaddr: optional ip address from what the action was made
128 128 :param sa: optional sqlalchemy session
129 129
130 130 """
131 131
132 132 if not sa:
133 133 sa = meta.Session()
134 134
135 135 try:
136 136 if hasattr(user, 'user_id'):
137 137 user_obj = User.get(user.user_id)
138 138 elif isinstance(user, basestring):
139 139 user_obj = User.get_by_username(user)
140 140 else:
141 141 raise Exception('You have to provide a user object or a username')
142 142
143 143 if hasattr(repo, 'repo_id'):
144 144 repo_obj = Repository.get(repo.repo_id)
145 145 repo_name = repo_obj.repo_name
146 146 elif isinstance(repo, basestring):
147 147 repo_name = repo.lstrip('/')
148 148 repo_obj = Repository.get_by_repo_name(repo_name)
149 149 else:
150 150 repo_obj = None
151 151 repo_name = ''
152 152
153 153 user_log = UserLog()
154 154 user_log.user_id = user_obj.user_id
155 155 user_log.username = user_obj.username
156 156 user_log.action = safe_unicode(action)
157 157
158 158 user_log.repository = repo_obj
159 159 user_log.repository_name = repo_name
160 160
161 161 user_log.action_date = datetime.datetime.now()
162 162 user_log.user_ip = ipaddr
163 163 sa.add(user_log)
164 164
165 165 log.info('Logging action:%s on %s by user:%s ip:%s' %
166 166 (action, safe_unicode(repo), user_obj, ipaddr))
167 167 if commit:
168 168 sa.commit()
169 169 except Exception:
170 170 log.error(traceback.format_exc())
171 171 raise
172 172
173 173
174 174 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
175 175 """
176 176 Scans given path for repos and return (name,(type,path)) tuple
177 177
178 178 :param path: path to scan for repositories
179 179 :param recursive: recursive search and return names with subdirs in front
180 180 """
181 181
182 182 # remove ending slash for better results
183 183 path = path.rstrip(os.sep)
184 184 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
185 185
186 186 def _get_repos(p):
187 187 if not os.access(p, os.W_OK):
188 188 log.warn('ignoring repo path without write access: %s', p)
189 189 return
190 190 for dirpath in os.listdir(p):
191 191 if os.path.isfile(os.path.join(p, dirpath)):
192 192 continue
193 193 cur_path = os.path.join(p, dirpath)
194 194
195 195 # skip removed repos
196 196 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
197 197 continue
198 198
199 199 #skip .<somethin> dirs
200 200 if dirpath.startswith('.'):
201 201 continue
202 202
203 203 try:
204 204 scm_info = get_scm(cur_path)
205 205 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
206 206 except VCSError:
207 207 if not recursive:
208 208 continue
209 209 #check if this dir containts other repos for recursive scan
210 210 rec_path = os.path.join(p, dirpath)
211 211 if os.path.isdir(rec_path):
212 212 for inner_scm in _get_repos(rec_path):
213 213 yield inner_scm
214 214
215 215 return _get_repos(path)
216 216
217 217
218 218 def is_valid_repo(repo_name, base_path, scm=None):
219 219 """
220 220 Returns True if given path is a valid repository False otherwise.
221 221 If scm param is given also compare if given scm is the same as expected
222 222 from scm parameter
223 223
224 224 :param repo_name:
225 225 :param base_path:
226 226 :param scm:
227 227
228 228 :return True: if given path is a valid repository
229 229 """
230 230 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
231 231
232 232 try:
233 233 scm_ = get_scm(full_path)
234 234 if scm:
235 235 return scm_[0] == scm
236 236 return True
237 237 except VCSError:
238 238 return False
239 239
240 240
241 241 def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
242 242 """
243 243 Returns True if given path is a repository group False otherwise
244 244
245 245 :param repo_name:
246 246 :param base_path:
247 247 """
248 248 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
249 249
250 250 # check if it's not a repo
251 251 if is_valid_repo(repos_group_name, base_path):
252 252 return False
253 253
254 254 try:
255 255 # we need to check bare git repos at higher level
256 256 # since we might match branches/hooks/info/objects or possible
257 257 # other things inside bare git repo
258 258 get_scm(os.path.dirname(full_path))
259 259 return False
260 260 except VCSError:
261 261 pass
262 262
263 263 # check if it's a valid path
264 264 if skip_path_check or os.path.isdir(full_path):
265 265 return True
266 266
267 267 return False
268 268
269 269
270 270 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
271 271 while True:
272 272 ok = raw_input(prompt)
273 273 if ok in ('y', 'ye', 'yes'):
274 274 return True
275 275 if ok in ('n', 'no', 'nop', 'nope'):
276 276 return False
277 277 retries = retries - 1
278 278 if retries < 0:
279 279 raise IOError
280 280 print complaint
281 281
282 282 #propagated from mercurial documentation
283 283 ui_sections = ['alias', 'auth',
284 284 'decode/encode', 'defaults',
285 285 'diff', 'email',
286 286 'extensions', 'format',
287 287 'merge-patterns', 'merge-tools',
288 288 'hooks', 'http_proxy',
289 289 'smtp', 'patch',
290 290 'paths', 'profiling',
291 291 'server', 'trusted',
292 292 'ui', 'web', ]
293 293
294 294
295 295 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
296 296 """
297 297 A function that will read python rc files or database
298 298 and make an mercurial ui object from read options
299 299
300 300 :param path: path to mercurial config file
301 301 :param checkpaths: check the path
302 302 :param read_from: read from 'file' or 'db'
303 303 """
304 304
305 305 baseui = ui.ui()
306 306
307 307 # clean the baseui object
308 308 baseui._ocfg = config.config()
309 309 baseui._ucfg = config.config()
310 310 baseui._tcfg = config.config()
311 311
312 312 if read_from == 'file':
313 313 if not os.path.isfile(path):
314 314 log.debug('hgrc file is not present at %s, skipping...' % path)
315 315 return False
316 316 log.debug('reading hgrc from %s' % path)
317 317 cfg = config.config()
318 318 cfg.read(path)
319 319 for section in ui_sections:
320 320 for k, v in cfg.items(section):
321 321 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
322 322 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
323 323
324 324 elif read_from == 'db':
325 325 sa = meta.Session()
326 326 ret = sa.query(RhodeCodeUi)\
327 327 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
328 328 .all()
329 329
330 330 hg_ui = ret
331 331 for ui_ in hg_ui:
332 332 if ui_.ui_active:
333 333 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
334 334 ui_.ui_key, ui_.ui_value)
335 335 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
336 336 safe_str(ui_.ui_value))
337 337 if ui_.ui_key == 'push_ssl':
338 338 # force set push_ssl requirement to False, rhodecode
339 339 # handles that
340 340 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
341 341 False)
342 342 if clear_session:
343 343 meta.Session.remove()
344 344 return baseui
345 345
346 346
347 347 def set_rhodecode_config(config):
348 348 """
349 349 Updates pylons config with new settings from database
350 350
351 351 :param config:
352 352 """
353 353 hgsettings = RhodeCodeSetting.get_app_settings()
354 354
355 355 for k, v in hgsettings.items():
356 356 config[k] = v
357 357
358 358
359 def invalidate_cache(cache_key, *args):
360 """
361 Puts cache invalidation task into db for
362 further global cache invalidation
363 """
364
365 from rhodecode.model.scm import ScmModel
366
367 if cache_key.startswith('get_repo_cached_'):
368 name = cache_key.split('get_repo_cached_')[-1]
369 ScmModel().mark_for_invalidation(name)
370
371
372 359 def map_groups(path):
373 360 """
374 361 Given a full path to a repository, create all nested groups that this
375 362 repo is inside. This function creates parent-child relationships between
376 363 groups and creates default perms for all new groups.
377 364
378 365 :param paths: full path to repository
379 366 """
380 367 sa = meta.Session()
381 368 groups = path.split(Repository.url_sep())
382 369 parent = None
383 370 group = None
384 371
385 372 # last element is repo in nested groups structure
386 373 groups = groups[:-1]
387 374 rgm = ReposGroupModel(sa)
388 375 for lvl, group_name in enumerate(groups):
389 376 group_name = '/'.join(groups[:lvl] + [group_name])
390 377 group = RepoGroup.get_by_group_name(group_name)
391 378 desc = '%s group' % group_name
392 379
393 380 # skip folders that are now removed repos
394 381 if REMOVED_REPO_PAT.match(group_name):
395 382 break
396 383
397 384 if group is None:
398 385 log.debug('creating group level: %s group_name: %s' % (lvl,
399 386 group_name))
400 387 group = RepoGroup(group_name, parent)
401 388 group.group_description = desc
402 389 sa.add(group)
403 390 rgm._create_default_perms(group)
404 391 sa.flush()
405 392 parent = group
406 393 return group
407 394
408 395
409 396 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
410 397 install_git_hook=False):
411 398 """
412 399 maps all repos given in initial_repo_list, non existing repositories
413 400 are created, if remove_obsolete is True it also check for db entries
414 401 that are not in initial_repo_list and removes them.
415 402
416 403 :param initial_repo_list: list of repositories found by scanning methods
417 404 :param remove_obsolete: check for obsolete entries in database
418 405 :param install_git_hook: if this is True, also check and install githook
419 406 for a repo if missing
420 407 """
421 408 from rhodecode.model.repo import RepoModel
422 409 from rhodecode.model.scm import ScmModel
423 410 sa = meta.Session()
424 411 rm = RepoModel()
425 412 user = sa.query(User).filter(User.admin == True).first()
426 413 if user is None:
427 414 raise Exception('Missing administrative account!')
428 415 added = []
429 416
430 417 ##creation defaults
431 418 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
432 419 enable_statistics = defs.get('repo_enable_statistics')
433 420 enable_locking = defs.get('repo_enable_locking')
434 421 enable_downloads = defs.get('repo_enable_downloads')
435 422 private = defs.get('repo_private')
436 423
437 424 for name, repo in initial_repo_list.items():
438 425 group = map_groups(name)
439 426 db_repo = rm.get_by_repo_name(name)
440 427 # found repo that is on filesystem not in RhodeCode database
441 428 if not db_repo:
442 429 log.info('repository %s not found, creating now' % name)
443 430 added.append(name)
444 431 desc = (repo.description
445 432 if repo.description != 'unknown'
446 433 else '%s repository' % name)
447 434
448 435 new_repo = rm.create_repo(
449 436 repo_name=name,
450 437 repo_type=repo.alias,
451 438 description=desc,
452 439 repos_group=getattr(group, 'group_id', None),
453 440 owner=user,
454 441 just_db=True,
455 442 enable_locking=enable_locking,
456 443 enable_downloads=enable_downloads,
457 444 enable_statistics=enable_statistics,
458 445 private=private
459 446 )
460 447 # we added that repo just now, and make sure it has githook
461 448 # installed
462 449 if new_repo.repo_type == 'git':
463 450 ScmModel().install_git_hook(new_repo.scm_instance)
464 451 new_repo.update_changeset_cache()
465 452 elif install_git_hook:
466 453 if db_repo.repo_type == 'git':
467 454 ScmModel().install_git_hook(db_repo.scm_instance)
468 455 # during starting install all cache keys for all repositories in the
469 456 # system, this will register all repos and multiple instances
470 457 cache_key = CacheInvalidation._get_cache_key(name)
471 458 log.debug("Creating invalidation cache key for %s: %s", name, cache_key)
472 459 CacheInvalidation.invalidate(name)
473 460
474 461 sa.commit()
475 462 removed = []
476 463 if remove_obsolete:
477 464 # remove from database those repositories that are not in the filesystem
478 465 for repo in sa.query(Repository).all():
479 466 if repo.repo_name not in initial_repo_list.keys():
480 467 log.debug("Removing non-existing repository found in db `%s`" %
481 468 repo.repo_name)
482 469 try:
483 470 sa.delete(repo)
484 471 sa.commit()
485 472 removed.append(repo.repo_name)
486 473 except Exception:
487 474 #don't hold further removals on error
488 475 log.error(traceback.format_exc())
489 476 sa.rollback()
490 477 return added, removed
491 478
492 479
493 480 # set cache regions for beaker so celery can utilise it
494 481 def add_cache(settings):
495 482 cache_settings = {'regions': None}
496 483 for key in settings.keys():
497 484 for prefix in ['beaker.cache.', 'cache.']:
498 485 if key.startswith(prefix):
499 486 name = key.split(prefix)[1].strip()
500 487 cache_settings[name] = settings[key].strip()
501 488 if cache_settings['regions']:
502 489 for region in cache_settings['regions'].split(','):
503 490 region = region.strip()
504 491 region_settings = {}
505 492 for key, value in cache_settings.items():
506 493 if key.startswith(region):
507 494 region_settings[key.split('.')[1]] = value
508 495 region_settings['expire'] = int(region_settings.get('expire',
509 496 60))
510 497 region_settings.setdefault('lock_dir',
511 498 cache_settings.get('lock_dir'))
512 499 region_settings.setdefault('data_dir',
513 500 cache_settings.get('data_dir'))
514 501
515 502 if 'type' not in region_settings:
516 503 region_settings['type'] = cache_settings.get('type',
517 504 'memory')
518 505 beaker.cache.cache_regions[region] = region_settings
519 506
520 507
521 508 def load_rcextensions(root_path):
522 509 import rhodecode
523 510 from rhodecode.config import conf
524 511
525 512 path = os.path.join(root_path, 'rcextensions', '__init__.py')
526 513 if os.path.isfile(path):
527 514 rcext = create_module('rc', path)
528 515 EXT = rhodecode.EXTENSIONS = rcext
529 516 log.debug('Found rcextensions now loading %s...' % rcext)
530 517
531 518 # Additional mappings that are not present in the pygments lexers
532 519 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
533 520
534 521 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
535 522
536 523 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
537 524 log.debug('settings custom INDEX_EXTENSIONS')
538 525 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
539 526
540 527 #ADDITIONAL MAPPINGS
541 528 log.debug('adding extra into INDEX_EXTENSIONS')
542 529 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
543 530
544 531 # auto check if the module is not missing any data, set to default if is
545 532 # this will help autoupdate new feature of rcext module
546 533 from rhodecode.config import rcextensions
547 534 for k in dir(rcextensions):
548 535 if not k.startswith('_') and not hasattr(EXT, k):
549 536 setattr(EXT, k, getattr(rcextensions, k))
550 537
551 538
552 539 def get_custom_lexer(extension):
553 540 """
554 541 returns a custom lexer if it's defined in rcextensions module, or None
555 542 if there's no custom lexer defined
556 543 """
557 544 import rhodecode
558 545 from pygments import lexers
559 546 #check if we didn't define this extension as other lexer
560 547 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
561 548 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
562 549 return lexers.get_lexer_by_name(_lexer_name)
563 550
564 551
565 552 #==============================================================================
566 553 # TEST FUNCTIONS AND CREATORS
567 554 #==============================================================================
568 555 def create_test_index(repo_location, config, full_index):
569 556 """
570 557 Makes default test index
571 558
572 559 :param config: test config
573 560 :param full_index:
574 561 """
575 562
576 563 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
577 564 from rhodecode.lib.pidlock import DaemonLock, LockHeld
578 565
579 566 repo_location = repo_location
580 567
581 568 index_location = os.path.join(config['app_conf']['index_dir'])
582 569 if not os.path.exists(index_location):
583 570 os.makedirs(index_location)
584 571
585 572 try:
586 573 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
587 574 WhooshIndexingDaemon(index_location=index_location,
588 575 repo_location=repo_location)\
589 576 .run(full_index=full_index)
590 577 l.release()
591 578 except LockHeld:
592 579 pass
593 580
594 581
595 582 def create_test_env(repos_test_path, config):
596 583 """
597 584 Makes a fresh database and
598 585 install test repository into tmp dir
599 586 """
600 587 from rhodecode.lib.db_manage import DbManage
601 588 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
602 589
603 590 # PART ONE create db
604 591 dbconf = config['sqlalchemy.db1.url']
605 592 log.debug('making test db %s' % dbconf)
606 593
607 594 # create test dir if it doesn't exist
608 595 if not os.path.isdir(repos_test_path):
609 596 log.debug('Creating testdir %s' % repos_test_path)
610 597 os.makedirs(repos_test_path)
611 598
612 599 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
613 600 tests=True)
614 601 dbmanage.create_tables(override=True)
615 602 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
616 603 dbmanage.create_default_user()
617 604 dbmanage.admin_prompt()
618 605 dbmanage.create_permissions()
619 606 dbmanage.populate_default_permissions()
620 607 Session().commit()
621 608 # PART TWO make test repo
622 609 log.debug('making test vcs repositories')
623 610
624 611 idx_path = config['app_conf']['index_dir']
625 612 data_path = config['app_conf']['cache_dir']
626 613
627 614 #clean index and data
628 615 if idx_path and os.path.exists(idx_path):
629 616 log.debug('remove %s' % idx_path)
630 617 shutil.rmtree(idx_path)
631 618
632 619 if data_path and os.path.exists(data_path):
633 620 log.debug('remove %s' % data_path)
634 621 shutil.rmtree(data_path)
635 622
636 623 #CREATE DEFAULT TEST REPOS
637 624 cur_dir = dn(dn(abspath(__file__)))
638 625 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
639 626 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
640 627 tar.close()
641 628
642 629 cur_dir = dn(dn(abspath(__file__)))
643 630 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
644 631 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
645 632 tar.close()
646 633
647 634 #LOAD VCS test stuff
648 635 from rhodecode.tests.vcs import setup_package
649 636 setup_package()
650 637
651 638
652 639 #==============================================================================
653 640 # PASTER COMMANDS
654 641 #==============================================================================
655 642 class BasePasterCommand(Command):
656 643 """
657 644 Abstract Base Class for paster commands.
658 645
659 646 The celery commands are somewhat aggressive about loading
660 647 celery.conf, and since our module sets the `CELERY_LOADER`
661 648 environment variable to our loader, we have to bootstrap a bit and
662 649 make sure we've had a chance to load the pylons config off of the
663 650 command line, otherwise everything fails.
664 651 """
665 652 min_args = 1
666 653 min_args_error = "Please provide a paster config file as an argument."
667 654 takes_config_file = 1
668 655 requires_config_file = True
669 656
670 657 def notify_msg(self, msg, log=False):
671 658 """Make a notification to user, additionally if logger is passed
672 659 it logs this action using given logger
673 660
674 661 :param msg: message that will be printed to user
675 662 :param log: logging instance, to use to additionally log this message
676 663
677 664 """
678 665 if log and isinstance(log, logging):
679 666 log(msg)
680 667
681 668 def run(self, args):
682 669 """
683 670 Overrides Command.run
684 671
685 672 Checks for a config file argument and loads it.
686 673 """
687 674 if len(args) < self.min_args:
688 675 raise BadCommand(
689 676 self.min_args_error % {'min_args': self.min_args,
690 677 'actual_args': len(args)})
691 678
692 679 # Decrement because we're going to lob off the first argument.
693 680 # @@ This is hacky
694 681 self.min_args -= 1
695 682 self.bootstrap_config(args[0])
696 683 self.update_parser()
697 684 return super(BasePasterCommand, self).run(args[1:])
698 685
699 686 def update_parser(self):
700 687 """
701 688 Abstract method. Allows for the class's parser to be updated
702 689 before the superclass's `run` method is called. Necessary to
703 690 allow options/arguments to be passed through to the underlying
704 691 celery command.
705 692 """
706 693 raise NotImplementedError("Abstract Method.")
707 694
708 695 def bootstrap_config(self, conf):
709 696 """
710 697 Loads the pylons configuration.
711 698 """
712 699 from pylons import config as pylonsconfig
713 700
714 701 self.path_to_ini_file = os.path.realpath(conf)
715 702 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
716 703 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
717 704
718 705 def _init_session(self):
719 706 """
720 707 Inits SqlAlchemy Session
721 708 """
722 709 logging.config.fileConfig(self.path_to_ini_file)
723 710 from pylons import config
724 711 from rhodecode.model import init_model
725 712 from rhodecode.lib.utils2 import engine_from_config
726 713
727 714 #get to remove repos !!
728 715 add_cache(config)
729 716 engine = engine_from_config(config, 'sqlalchemy.db1.')
730 717 init_model(engine)
731 718
732 719
733 720 def check_git_version():
734 721 """
735 722 Checks what version of git is installed in system, and issues a warning
736 723 if it's too old for RhodeCode to properly work.
737 724 """
738 725 from rhodecode import BACKENDS
739 726 from rhodecode.lib.vcs.backends.git.repository import GitRepository
740 727 from distutils.version import StrictVersion
741 728
742 729 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
743 730 _safe=True)
744 731
745 732 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
746 733 if len(ver.split('.')) > 3:
747 734 #StrictVersion needs to be only 3 element type
748 735 ver = '.'.join(ver.split('.')[:3])
749 736 try:
750 737 _ver = StrictVersion(ver)
751 738 except Exception:
752 739 _ver = StrictVersion('0.0.0')
753 740 stderr = traceback.format_exc()
754 741
755 742 req_ver = '1.7.4'
756 743 to_old_git = False
757 744 if _ver < StrictVersion(req_ver):
758 745 to_old_git = True
759 746
760 747 if 'git' in BACKENDS:
761 748 log.debug('GIT version detected: %s' % stdout)
762 749 if stderr:
763 750 log.warning('Unable to detect git version, org error was: %r' % stderr)
764 751 elif to_old_git:
765 752 log.warning('RhodeCode detected git version %s, which is too old '
766 753 'for the system to function properly. Make sure '
767 754 'its version is at least %s' % (ver, req_ver))
768 755 return _ver
769 756
770 757
771 758 @decorator.decorator
772 759 def jsonify(func, *args, **kwargs):
773 760 """Action decorator that formats output for JSON
774 761
775 762 Given a function that will return content, this decorator will turn
776 763 the result into JSON, with a content-type of 'application/json' and
777 764 output it.
778 765
779 766 """
780 767 from pylons.decorators.util import get_pylons
781 768 from rhodecode.lib.ext_json import json
782 769 pylons = get_pylons(args)
783 770 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
784 771 data = func(*args, **kwargs)
785 772 if isinstance(data, (list, tuple)):
786 773 msg = "JSON responses with Array envelopes are susceptible to " \
787 774 "cross-site data leak attacks, see " \
788 775 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
789 776 warnings.warn(msg, Warning, 2)
790 777 log.warning(msg)
791 778 log.debug("Returning JSON wrapped action output")
792 779 return json.dumps(data, encoding='utf-8')
@@ -1,122 +1,122 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.tests.fixture import Fixture
3 3 from rhodecode.model.db import Repository
4 from rhodecode.lib.utils import invalidate_cache
5 4 from rhodecode.model.repo import RepoModel
6 5 from rhodecode.model.meta import Session
6 from rhodecode.model.scm import ScmModel
7 7
8 8 fixture = Fixture()
9 9
10 10
11 11 class TestSummaryController(TestController):
12 12
13 13 def test_index(self):
14 14 self.log_user()
15 15 ID = Repository.get_by_repo_name(HG_REPO).repo_id
16 16 response = self.app.get(url(controller='summary',
17 17 action='index',
18 18 repo_name=HG_REPO))
19 19
20 20 #repo type
21 21 response.mustcontain(
22 22 """<img style="margin-bottom:2px" class="icon" """
23 23 """title="Mercurial repository" alt="Mercurial repository" """
24 24 """src="/images/icons/hgicon.png"/>"""
25 25 )
26 26 response.mustcontain(
27 27 """<img style="margin-bottom:2px" class="icon" """
28 28 """title="Public repository" alt="Public """
29 29 """repository" src="/images/icons/lock_open.png"/>"""
30 30 )
31 31
32 32 #codes stats
33 33 self._enable_stats()
34 34
35 invalidate_cache('get_repo_cached_%s' % HG_REPO)
35 ScmModel().mark_for_invalidation(HG_REPO)
36 36 response = self.app.get(url(controller='summary', action='index',
37 37 repo_name=HG_REPO))
38 38 response.mustcontain(
39 39 """var data = [["py", {"count": 42, "desc": ["Python"]}], """
40 40 """["rst", {"count": 11, "desc": ["Rst"]}], """
41 41 """["sh", {"count": 2, "desc": ["Bash"]}], """
42 42 """["makefile", {"count": 1, "desc": ["Makefile", "Makefile"]}],"""
43 43 """ ["cfg", {"count": 1, "desc": ["Ini"]}], """
44 44 """["css", {"count": 1, "desc": ["Css"]}], """
45 45 """["bat", {"count": 1, "desc": ["Batch"]}]];"""
46 46 )
47 47
48 48 # clone url...
49 49 response.mustcontain('''id="clone_url" readonly="readonly" value="http://test_admin@localhost:80/%s"''' % HG_REPO)
50 50 response.mustcontain('''id="clone_url_id" readonly="readonly" value="http://test_admin@localhost:80/_%s"''' % ID)
51 51
52 52 def test_index_git(self):
53 53 self.log_user()
54 54 ID = Repository.get_by_repo_name(GIT_REPO).repo_id
55 55 response = self.app.get(url(controller='summary',
56 56 action='index',
57 57 repo_name=GIT_REPO))
58 58
59 59 #repo type
60 60 response.mustcontain(
61 61 """<img style="margin-bottom:2px" class="icon" """
62 62 """title="Git repository" alt="Git repository" """
63 63 """src="/images/icons/giticon.png"/>"""
64 64 )
65 65 response.mustcontain(
66 66 """<img style="margin-bottom:2px" class="icon" """
67 67 """title="Public repository" alt="Public """
68 68 """repository" src="/images/icons/lock_open.png"/>"""
69 69 )
70 70
71 71 # clone url...
72 72 response.mustcontain('''id="clone_url" readonly="readonly" value="http://test_admin@localhost:80/%s"''' % GIT_REPO)
73 73 response.mustcontain('''id="clone_url_id" readonly="readonly" value="http://test_admin@localhost:80/_%s"''' % ID)
74 74
75 75 def test_index_by_id_hg(self):
76 76 self.log_user()
77 77 ID = Repository.get_by_repo_name(HG_REPO).repo_id
78 78 response = self.app.get(url(controller='summary',
79 79 action='index',
80 80 repo_name='_%s' % ID))
81 81
82 82 #repo type
83 83 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
84 84 """title="Mercurial repository" alt="Mercurial """
85 85 """repository" src="/images/icons/hgicon.png"/>""")
86 86 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
87 87 """title="Public repository" alt="Public """
88 88 """repository" src="/images/icons/lock_open.png"/>""")
89 89
90 90 def test_index_by_repo_having_id_path_in_name_hg(self):
91 91 self.log_user()
92 92 fixture.create_repo(name='repo_1')
93 93 response = self.app.get(url(controller='summary',
94 94 action='index',
95 95 repo_name='repo_1'))
96 96
97 97 try:
98 98 response.mustcontain("repo_1")
99 99 finally:
100 100 RepoModel().delete(Repository.get_by_repo_name('repo_1'))
101 101 Session().commit()
102 102
103 103 def test_index_by_id_git(self):
104 104 self.log_user()
105 105 ID = Repository.get_by_repo_name(GIT_REPO).repo_id
106 106 response = self.app.get(url(controller='summary',
107 107 action='index',
108 108 repo_name='_%s' % ID))
109 109
110 110 #repo type
111 111 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
112 112 """title="Git repository" alt="Git """
113 113 """repository" src="/images/icons/giticon.png"/>""")
114 114 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
115 115 """title="Public repository" alt="Public """
116 116 """repository" src="/images/icons/lock_open.png"/>""")
117 117
118 118 def _enable_stats(self):
119 119 r = Repository.get_by_repo_name(HG_REPO)
120 120 r.enable_statistics = True
121 121 self.Session.add(r)
122 122 self.Session.commit()
General Comments 0
You need to be logged in to leave comments. Login now