##// END OF EJS Templates
Initial version of landing revisions ref #483...
marcink -
r2471:60bda63b beta
parent child Browse files
Show More
1 NO CONTENT: new file 100644, binary diff hidden
@@ -1,434 +1,437
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 paste.httpexceptions import HTTPInternalServerError
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 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 39 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
40 40 from rhodecode.lib.base import BaseController, render
41 41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 42 from rhodecode.lib.helpers import get_token
43 43 from rhodecode.model.meta import Session
44 44 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
45 45 from rhodecode.model.forms import RepoForm
46 46 from rhodecode.model.scm import ScmModel
47 47 from rhodecode.model.repo import RepoModel
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 class ReposController(BaseController):
53 53 """
54 54 REST Controller styled on the Atom Publishing Protocol"""
55 55 # To properly map this controller, ensure your config/routing.py
56 56 # file has a resource setup:
57 57 # map.resource('repo', 'repos')
58 58
59 59 @LoginRequired()
60 60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
61 61 def __before__(self):
62 62 c.admin_user = session.get('admin_user')
63 63 c.admin_username = session.get('admin_username')
64 64 super(ReposController, self).__before__()
65 65
66 66 def __load_defaults(self):
67 67 c.repo_groups = RepoGroup.groups_choices()
68 68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
69 69
70 70 repo_model = RepoModel()
71 71 c.users_array = repo_model.get_users_js()
72 72 c.users_groups_array = repo_model.get_users_groups_js()
73 c.landing_revs = ScmModel().get_repo_landing_revs()
73 74
74 75 def __load_data(self, repo_name=None):
75 76 """
76 77 Load defaults settings for edit, and update
77 78
78 79 :param repo_name:
79 80 """
80 81 self.__load_defaults()
81 82
82 83 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
83 84 repo = db_repo.scm_instance
84 85
85 86 if c.repo_info is None:
86 87 h.flash(_('%s repository is not mapped to db perhaps'
87 88 ' it was created or renamed from the filesystem'
88 89 ' please run the application again'
89 90 ' in order to rescan repositories') % repo_name,
90 91 category='error')
91 92
92 93 return redirect(url('repos'))
93 94
95 c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
94 96 c.default_user_id = User.get_by_username('default').user_id
95 97 c.in_public_journal = UserFollowing.query()\
96 98 .filter(UserFollowing.user_id == c.default_user_id)\
97 99 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
98 100
99 101 if c.repo_info.stats:
100 102 # this is on what revision we ended up so we add +1 for count
101 103 last_rev = c.repo_info.stats.stat_on_revision + 1
102 104 else:
103 105 last_rev = 0
104 106 c.stats_revision = last_rev
105 107
106 108 c.repo_last_rev = repo.count() if repo.revisions else 0
107 109
108 110 if last_rev == 0 or c.repo_last_rev == 0:
109 111 c.stats_percentage = 0
110 112 else:
111 113 c.stats_percentage = '%.2f' % ((float((last_rev)) /
112 114 c.repo_last_rev) * 100)
113 115
114 116 defaults = RepoModel()._get_defaults(repo_name)
115 117
116 118 c.repos_list = [('', _('--REMOVE FORK--'))]
117 119 c.repos_list += [(x.repo_id, x.repo_name) for x in
118 120 Repository.query().order_by(Repository.repo_name).all()]
121
119 122 return defaults
120 123
121 124 @HasPermissionAllDecorator('hg.admin')
122 125 def index(self, format='html'):
123 126 """GET /repos: All items in the collection"""
124 127 # url('repos')
125 128
126 129 c.repos_list = ScmModel().get_repos(Repository.query()
127 130 .order_by(Repository.repo_name)
128 131 .all(), sort_key='name_sort')
129 132 return render('admin/repos/repos.html')
130 133
131 134 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
132 135 def create(self):
133 136 """
134 137 POST /repos: Create a new item"""
135 138 # url('repos')
136 139
137 140 self.__load_defaults()
138 141 form_result = {}
139 142 try:
140 143 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
141 144 .to_python(dict(request.POST))
142 145 RepoModel().create(form_result, self.rhodecode_user)
143 146 if form_result['clone_uri']:
144 147 h.flash(_('created repository %s from %s') \
145 148 % (form_result['repo_name'], form_result['clone_uri']),
146 149 category='success')
147 150 else:
148 151 h.flash(_('created repository %s') % form_result['repo_name'],
149 152 category='success')
150 153
151 154 if request.POST.get('user_created'):
152 155 # created by regular non admin user
153 156 action_logger(self.rhodecode_user, 'user_created_repo',
154 157 form_result['repo_name_full'], self.ip_addr,
155 158 self.sa)
156 159 else:
157 160 action_logger(self.rhodecode_user, 'admin_created_repo',
158 161 form_result['repo_name_full'], self.ip_addr,
159 162 self.sa)
160 163 Session.commit()
161 164 except formencode.Invalid, errors:
162 165
163 166 c.new_repo = errors.value['repo_name']
164 167
165 168 if request.POST.get('user_created'):
166 169 r = render('admin/repos/repo_add_create_repository.html')
167 170 else:
168 171 r = render('admin/repos/repo_add.html')
169 172
170 173 return htmlfill.render(
171 174 r,
172 175 defaults=errors.value,
173 176 errors=errors.error_dict or {},
174 177 prefix_error=False,
175 178 encoding="UTF-8")
176 179
177 180 except Exception:
178 181 log.error(traceback.format_exc())
179 182 msg = _('error occurred during creation of repository %s') \
180 183 % form_result.get('repo_name')
181 184 h.flash(msg, category='error')
182 185 if request.POST.get('user_created'):
183 186 return redirect(url('home'))
184 187 return redirect(url('repos'))
185 188
186 189 @HasPermissionAllDecorator('hg.admin')
187 190 def new(self, format='html'):
188 191 """GET /repos/new: Form to create a new item"""
189 192 new_repo = request.GET.get('repo', '')
190 193 c.new_repo = repo_name_slug(new_repo)
191 194 self.__load_defaults()
192 195 return render('admin/repos/repo_add.html')
193 196
194 197 @HasPermissionAllDecorator('hg.admin')
195 198 def update(self, repo_name):
196 199 """
197 200 PUT /repos/repo_name: Update an existing item"""
198 201 # Forms posted to this method should contain a hidden field:
199 202 # <input type="hidden" name="_method" value="PUT" />
200 203 # Or using helpers:
201 204 # h.form(url('repo', repo_name=ID),
202 205 # method='put')
203 206 # url('repo', repo_name=ID)
204 207 self.__load_defaults()
205 208 repo_model = RepoModel()
206 209 changed_name = repo_name
207 210 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
208 211 repo_groups=c.repo_groups_choices)()
209 212 try:
210 213 form_result = _form.to_python(dict(request.POST))
211 214 repo = repo_model.update(repo_name, form_result)
212 215 invalidate_cache('get_repo_cached_%s' % repo_name)
213 216 h.flash(_('Repository %s updated successfully' % repo_name),
214 217 category='success')
215 218 changed_name = repo.repo_name
216 219 action_logger(self.rhodecode_user, 'admin_updated_repo',
217 220 changed_name, self.ip_addr, self.sa)
218 221 Session.commit()
219 222 except formencode.Invalid, errors:
220 223 defaults = self.__load_data(repo_name)
221 224 defaults.update(errors.value)
222 225 return htmlfill.render(
223 226 render('admin/repos/repo_edit.html'),
224 227 defaults=defaults,
225 228 errors=errors.error_dict or {},
226 229 prefix_error=False,
227 230 encoding="UTF-8")
228 231
229 232 except Exception:
230 233 log.error(traceback.format_exc())
231 234 h.flash(_('error occurred during update of repository %s') \
232 235 % repo_name, category='error')
233 236 return redirect(url('edit_repo', repo_name=changed_name))
234 237
235 238 @HasPermissionAllDecorator('hg.admin')
236 239 def delete(self, repo_name):
237 240 """
238 241 DELETE /repos/repo_name: Delete an existing item"""
239 242 # Forms posted to this method should contain a hidden field:
240 243 # <input type="hidden" name="_method" value="DELETE" />
241 244 # Or using helpers:
242 245 # h.form(url('repo', repo_name=ID),
243 246 # method='delete')
244 247 # url('repo', repo_name=ID)
245 248
246 249 repo_model = RepoModel()
247 250 repo = repo_model.get_by_repo_name(repo_name)
248 251 if not repo:
249 252 h.flash(_('%s repository is not mapped to db perhaps'
250 253 ' it was moved or renamed from the filesystem'
251 254 ' please run the application again'
252 255 ' in order to rescan repositories') % repo_name,
253 256 category='error')
254 257
255 258 return redirect(url('repos'))
256 259 try:
257 260 action_logger(self.rhodecode_user, 'admin_deleted_repo',
258 261 repo_name, self.ip_addr, self.sa)
259 262 repo_model.delete(repo)
260 263 invalidate_cache('get_repo_cached_%s' % repo_name)
261 264 h.flash(_('deleted repository %s') % repo_name, category='success')
262 265 Session.commit()
263 266 except IntegrityError, e:
264 267 if e.message.find('repositories_fork_id_fkey') != -1:
265 268 log.error(traceback.format_exc())
266 269 h.flash(_('Cannot delete %s it still contains attached '
267 270 'forks') % repo_name,
268 271 category='warning')
269 272 else:
270 273 log.error(traceback.format_exc())
271 274 h.flash(_('An error occurred during '
272 275 'deletion of %s') % repo_name,
273 276 category='error')
274 277
275 278 except Exception, e:
276 279 log.error(traceback.format_exc())
277 280 h.flash(_('An error occurred during deletion of %s') % repo_name,
278 281 category='error')
279 282
280 283 return redirect(url('repos'))
281 284
282 285 @HasRepoPermissionAllDecorator('repository.admin')
283 286 def delete_perm_user(self, repo_name):
284 287 """
285 288 DELETE an existing repository permission user
286 289
287 290 :param repo_name:
288 291 """
289 292 try:
290 293 RepoModel().revoke_user_permission(repo=repo_name,
291 294 user=request.POST['user_id'])
292 295 Session.commit()
293 296 except Exception:
294 297 log.error(traceback.format_exc())
295 298 h.flash(_('An error occurred during deletion of repository user'),
296 299 category='error')
297 300 raise HTTPInternalServerError()
298 301
299 302 @HasRepoPermissionAllDecorator('repository.admin')
300 303 def delete_perm_users_group(self, repo_name):
301 304 """
302 305 DELETE an existing repository permission users group
303 306
304 307 :param repo_name:
305 308 """
306 309
307 310 try:
308 311 RepoModel().revoke_users_group_permission(
309 312 repo=repo_name, group_name=request.POST['users_group_id']
310 313 )
311 314 Session.commit()
312 315 except Exception:
313 316 log.error(traceback.format_exc())
314 317 h.flash(_('An error occurred during deletion of repository'
315 318 ' users groups'),
316 319 category='error')
317 320 raise HTTPInternalServerError()
318 321
319 322 @HasPermissionAllDecorator('hg.admin')
320 323 def repo_stats(self, repo_name):
321 324 """
322 325 DELETE an existing repository statistics
323 326
324 327 :param repo_name:
325 328 """
326 329
327 330 try:
328 331 RepoModel().delete_stats(repo_name)
329 332 Session.commit()
330 333 except Exception, e:
331 334 h.flash(_('An error occurred during deletion of repository stats'),
332 335 category='error')
333 336 return redirect(url('edit_repo', repo_name=repo_name))
334 337
335 338 @HasPermissionAllDecorator('hg.admin')
336 339 def repo_cache(self, repo_name):
337 340 """
338 341 INVALIDATE existing repository cache
339 342
340 343 :param repo_name:
341 344 """
342 345
343 346 try:
344 347 ScmModel().mark_for_invalidation(repo_name)
345 348 Session.commit()
346 349 except Exception, e:
347 350 h.flash(_('An error occurred during cache invalidation'),
348 351 category='error')
349 352 return redirect(url('edit_repo', repo_name=repo_name))
350 353
351 354 @HasPermissionAllDecorator('hg.admin')
352 355 def repo_public_journal(self, repo_name):
353 356 """
354 357 Set's this repository to be visible in public journal,
355 358 in other words assing default user to follow this repo
356 359
357 360 :param repo_name:
358 361 """
359 362
360 363 cur_token = request.POST.get('auth_token')
361 364 token = get_token()
362 365 if cur_token == token:
363 366 try:
364 367 repo_id = Repository.get_by_repo_name(repo_name).repo_id
365 368 user_id = User.get_by_username('default').user_id
366 369 self.scm_model.toggle_following_repo(repo_id, user_id)
367 370 h.flash(_('Updated repository visibility in public journal'),
368 371 category='success')
369 372 Session.commit()
370 373 except:
371 374 h.flash(_('An error occurred during setting this'
372 375 ' repository in public journal'),
373 376 category='error')
374 377
375 378 else:
376 379 h.flash(_('Token mismatch'), category='error')
377 380 return redirect(url('edit_repo', repo_name=repo_name))
378 381
379 382 @HasPermissionAllDecorator('hg.admin')
380 383 def repo_pull(self, repo_name):
381 384 """
382 385 Runs task to update given repository with remote changes,
383 386 ie. make pull on remote location
384 387
385 388 :param repo_name:
386 389 """
387 390 try:
388 391 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
389 392 h.flash(_('Pulled from remote location'), category='success')
390 393 except Exception, e:
391 394 h.flash(_('An error occurred during pull from remote location'),
392 395 category='error')
393 396
394 397 return redirect(url('edit_repo', repo_name=repo_name))
395 398
396 399 @HasPermissionAllDecorator('hg.admin')
397 400 def repo_as_fork(self, repo_name):
398 401 """
399 402 Mark given repository as a fork of another
400 403
401 404 :param repo_name:
402 405 """
403 406 try:
404 407 fork_id = request.POST.get('id_fork_of')
405 408 repo = ScmModel().mark_as_fork(repo_name, fork_id,
406 409 self.rhodecode_user.username)
407 410 fork = repo.fork.repo_name if repo.fork else _('Nothing')
408 411 Session.commit()
409 412 h.flash(_('Marked repo %s as fork of %s' % (repo_name,fork)),
410 413 category='success')
411 414 except Exception, e:
412 415 raise
413 416 h.flash(_('An error occurred during this operation'),
414 417 category='error')
415 418
416 419 return redirect(url('edit_repo', repo_name=repo_name))
417 420
418 421 @HasPermissionAllDecorator('hg.admin')
419 422 def show(self, repo_name, format='html'):
420 423 """GET /repos/repo_name: Show a specific item"""
421 424 # url('repo', repo_name=ID)
422 425
423 426 @HasPermissionAllDecorator('hg.admin')
424 427 def edit(self, repo_name, format='html'):
425 428 """GET /repos/repo_name/edit: Form to edit an existing item"""
426 429 # url('edit_repo', repo_name=ID)
427 430 defaults = self.__load_data(repo_name)
428 431
429 432 return htmlfill.render(
430 433 render('admin/repos/repo_edit.html'),
431 434 defaults=defaults,
432 435 encoding="UTF-8",
433 436 force_defaults=False
434 437 )
@@ -1,426 +1,427
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
41 41 from rhodecode.lib.base import BaseController, render
42 42 from rhodecode.lib.celerylib import tasks, run_task
43 43 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
44 44 set_rhodecode_config, repo_name_slug
45 45 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
46 46 RhodeCodeSetting
47 47 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
48 48 ApplicationUiSettingsForm
49 49 from rhodecode.model.scm import ScmModel
50 50 from rhodecode.model.user import UserModel
51 51 from rhodecode.model.db import User
52 52 from rhodecode.model.notification import EmailNotificationModel
53 53 from rhodecode.model.meta import Session
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class SettingsController(BaseController):
59 59 """REST Controller styled on the Atom Publishing Protocol"""
60 60 # To properly map this controller, ensure your config/routing.py
61 61 # file has a resource setup:
62 62 # map.resource('setting', 'settings', controller='admin/settings',
63 63 # path_prefix='/admin', name_prefix='admin_')
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 c.modules = sorted([(p.project_name, p.version)
70 70 for p in pkg_resources.working_set],
71 71 key=lambda k: k[0].lower())
72 72 c.py_version = platform.python_version()
73 73 c.platform = platform.platform()
74 74 super(SettingsController, self).__before__()
75 75
76 76 @HasPermissionAllDecorator('hg.admin')
77 77 def index(self, format='html'):
78 78 """GET /admin/settings: All items in the collection"""
79 79 # url('admin_settings')
80 80
81 81 defaults = RhodeCodeSetting.get_app_settings()
82 82 defaults.update(self.get_hg_ui_settings())
83 83
84 84 return htmlfill.render(
85 85 render('admin/settings/settings.html'),
86 86 defaults=defaults,
87 87 encoding="UTF-8",
88 88 force_defaults=False
89 89 )
90 90
91 91 @HasPermissionAllDecorator('hg.admin')
92 92 def create(self):
93 93 """POST /admin/settings: Create a new item"""
94 94 # url('admin_settings')
95 95
96 96 @HasPermissionAllDecorator('hg.admin')
97 97 def new(self, format='html'):
98 98 """GET /admin/settings/new: Form to create a new item"""
99 99 # url('admin_new_setting')
100 100
101 101 @HasPermissionAllDecorator('hg.admin')
102 102 def update(self, setting_id):
103 103 """PUT /admin/settings/setting_id: Update an existing item"""
104 104 # Forms posted to this method should contain a hidden field:
105 105 # <input type="hidden" name="_method" value="PUT" />
106 106 # Or using helpers:
107 107 # h.form(url('admin_setting', setting_id=ID),
108 108 # method='put')
109 109 # url('admin_setting', setting_id=ID)
110 110 if setting_id == 'mapping':
111 111 rm_obsolete = request.POST.get('destroy', False)
112 112 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
113 113 initial = ScmModel().repo_scan()
114 114 log.debug('invalidating all repositories')
115 115 for repo_name in initial.keys():
116 116 invalidate_cache('get_repo_cached_%s' % repo_name)
117 117
118 118 added, removed = repo2db_mapper(initial, rm_obsolete)
119 119
120 120 h.flash(_('Repositories successfully'
121 121 ' rescanned added: %s,removed: %s') % (added, removed),
122 122 category='success')
123 123
124 124 if setting_id == 'whoosh':
125 125 repo_location = self.get_hg_ui_settings()['paths_root_path']
126 126 full_index = request.POST.get('full_index', False)
127 127 run_task(tasks.whoosh_index, repo_location, full_index)
128 128
129 129 h.flash(_('Whoosh reindex task scheduled'), category='success')
130 130 if setting_id == 'global':
131 131
132 132 application_form = ApplicationSettingsForm()()
133 133 try:
134 134 form_result = application_form.to_python(dict(request.POST))
135 135
136 136 try:
137 137 hgsettings1 = RhodeCodeSetting.get_by_name('title')
138 138 hgsettings1.app_settings_value = \
139 139 form_result['rhodecode_title']
140 140
141 141 hgsettings2 = RhodeCodeSetting.get_by_name('realm')
142 142 hgsettings2.app_settings_value = \
143 143 form_result['rhodecode_realm']
144 144
145 145 hgsettings3 = RhodeCodeSetting.get_by_name('ga_code')
146 146 hgsettings3.app_settings_value = \
147 147 form_result['rhodecode_ga_code']
148 148
149 149 self.sa.add(hgsettings1)
150 150 self.sa.add(hgsettings2)
151 151 self.sa.add(hgsettings3)
152 152 self.sa.commit()
153 153 set_rhodecode_config(config)
154 154 h.flash(_('Updated application settings'),
155 155 category='success')
156 156
157 157 except Exception:
158 158 log.error(traceback.format_exc())
159 159 h.flash(_('error occurred during updating '
160 160 'application settings'),
161 161 category='error')
162 162
163 163 self.sa.rollback()
164 164
165 165 except formencode.Invalid, errors:
166 166 return htmlfill.render(
167 167 render('admin/settings/settings.html'),
168 168 defaults=errors.value,
169 169 errors=errors.error_dict or {},
170 170 prefix_error=False,
171 171 encoding="UTF-8")
172 172
173 173 if setting_id == 'mercurial':
174 174 application_form = ApplicationUiSettingsForm()()
175 175 try:
176 176 form_result = application_form.to_python(dict(request.POST))
177 177 # fix namespaces for hooks
178 178 _f = lambda s: s.replace('.', '_')
179 179 try:
180 180
181 181 hgsettings1 = self.sa.query(RhodeCodeUi)\
182 182 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
183 183 hgsettings1.ui_value = form_result['web_push_ssl']
184 184
185 185 hgsettings2 = self.sa.query(RhodeCodeUi)\
186 186 .filter(RhodeCodeUi.ui_key == '/').one()
187 187 hgsettings2.ui_value = form_result['paths_root_path']
188 188
189 189 #HOOKS
190 190 hgsettings3 = self.sa.query(RhodeCodeUi)\
191 191 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_UPDATE)\
192 192 .one()
193 193 hgsettings3.ui_active = bool(form_result[_f('hooks_%s' %
194 194 RhodeCodeUi.HOOK_UPDATE)])
195 195
196 196 hgsettings4 = self.sa.query(RhodeCodeUi)\
197 197 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_REPO_SIZE)\
198 198 .one()
199 199 hgsettings4.ui_active = bool(form_result[_f('hooks_%s' %
200 200 RhodeCodeUi.HOOK_REPO_SIZE)])
201 201
202 202 hgsettings5 = self.sa.query(RhodeCodeUi)\
203 203 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_PUSH)\
204 204 .one()
205 205 hgsettings5.ui_active = bool(form_result[_f('hooks_%s' %
206 206 RhodeCodeUi.HOOK_PUSH)])
207 207
208 208 hgsettings6 = self.sa.query(RhodeCodeUi)\
209 209 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_PULL)\
210 210 .one()
211 211 hgsettings6.ui_active = bool(form_result[_f('hooks_%s' %
212 212 RhodeCodeUi.HOOK_PULL)])
213 213
214 214 self.sa.add(hgsettings1)
215 215 self.sa.add(hgsettings2)
216 216 self.sa.add(hgsettings3)
217 217 self.sa.add(hgsettings4)
218 218 self.sa.add(hgsettings5)
219 219 self.sa.add(hgsettings6)
220 220 self.sa.commit()
221 221
222 222 h.flash(_('Updated mercurial settings'),
223 223 category='success')
224 224
225 225 except:
226 226 log.error(traceback.format_exc())
227 227 h.flash(_('error occurred during updating '
228 228 'application settings'), category='error')
229 229
230 230 self.sa.rollback()
231 231
232 232 except formencode.Invalid, errors:
233 233 return htmlfill.render(
234 234 render('admin/settings/settings.html'),
235 235 defaults=errors.value,
236 236 errors=errors.error_dict or {},
237 237 prefix_error=False,
238 238 encoding="UTF-8")
239 239
240 240 if setting_id == 'hooks':
241 241 ui_key = request.POST.get('new_hook_ui_key')
242 242 ui_value = request.POST.get('new_hook_ui_value')
243 243 try:
244 244
245 245 if ui_value and ui_key:
246 246 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
247 247 h.flash(_('Added new hook'),
248 248 category='success')
249 249
250 250 # check for edits
251 251 update = False
252 252 _d = request.POST.dict_of_lists()
253 253 for k, v in zip(_d.get('hook_ui_key', []),
254 254 _d.get('hook_ui_value_new', [])):
255 255 RhodeCodeUi.create_or_update_hook(k, v)
256 256 update = True
257 257
258 258 if update:
259 259 h.flash(_('Updated hooks'), category='success')
260 260 self.sa.commit()
261 261 except:
262 262 log.error(traceback.format_exc())
263 263 h.flash(_('error occurred during hook creation'),
264 264 category='error')
265 265
266 266 return redirect(url('admin_edit_setting', setting_id='hooks'))
267 267
268 268 if setting_id == 'email':
269 269 test_email = request.POST.get('test_email')
270 270 test_email_subj = 'RhodeCode TestEmail'
271 271 test_email_body = 'RhodeCode Email test'
272 272
273 273 test_email_html_body = EmailNotificationModel()\
274 274 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
275 275 body=test_email_body)
276 276
277 277 recipients = [test_email] if [test_email] else None
278 278
279 279 run_task(tasks.send_email, recipients, test_email_subj,
280 280 test_email_body, test_email_html_body)
281 281
282 282 h.flash(_('Email task created'), category='success')
283 283 return redirect(url('admin_settings'))
284 284
285 285 @HasPermissionAllDecorator('hg.admin')
286 286 def delete(self, setting_id):
287 287 """DELETE /admin/settings/setting_id: Delete an existing item"""
288 288 # Forms posted to this method should contain a hidden field:
289 289 # <input type="hidden" name="_method" value="DELETE" />
290 290 # Or using helpers:
291 291 # h.form(url('admin_setting', setting_id=ID),
292 292 # method='delete')
293 293 # url('admin_setting', setting_id=ID)
294 294 if setting_id == 'hooks':
295 295 hook_id = request.POST.get('hook_id')
296 296 RhodeCodeUi.delete(hook_id)
297 297 self.sa.commit()
298 298
299 299 @HasPermissionAllDecorator('hg.admin')
300 300 def show(self, setting_id, format='html'):
301 301 """
302 302 GET /admin/settings/setting_id: Show a specific item"""
303 303 # url('admin_setting', setting_id=ID)
304 304
305 305 @HasPermissionAllDecorator('hg.admin')
306 306 def edit(self, setting_id, format='html'):
307 307 """
308 308 GET /admin/settings/setting_id/edit: Form to
309 309 edit an existing item"""
310 310 # url('admin_edit_setting', setting_id=ID)
311 311
312 312 c.hooks = RhodeCodeUi.get_builtin_hooks()
313 313 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
314 314
315 315 return htmlfill.render(
316 316 render('admin/settings/hooks.html'),
317 317 defaults={},
318 318 encoding="UTF-8",
319 319 force_defaults=False
320 320 )
321 321
322 322 @NotAnonymous()
323 323 def my_account(self):
324 324 """
325 325 GET /_admin/my_account Displays info about my account
326 326 """
327 327 # url('admin_settings_my_account')
328 328
329 329 c.user = User.get(self.rhodecode_user.user_id)
330 330 all_repos = self.sa.query(Repository)\
331 331 .filter(Repository.user_id == c.user.user_id)\
332 332 .order_by(func.lower(Repository.repo_name)).all()
333 333
334 334 c.user_repos = ScmModel().get_repos(all_repos)
335 335
336 336 if c.user.username == 'default':
337 337 h.flash(_("You can't edit this user since it's"
338 338 " crucial for entire application"), category='warning')
339 339 return redirect(url('users'))
340 340
341 341 defaults = c.user.get_dict()
342 342
343 343 c.form = htmlfill.render(
344 344 render('admin/users/user_edit_my_account_form.html'),
345 345 defaults=defaults,
346 346 encoding="UTF-8",
347 347 force_defaults=False
348 348 )
349 349 return render('admin/users/user_edit_my_account.html')
350 350
351 351 def my_account_update(self):
352 352 """PUT /_admin/my_account_update: Update an existing item"""
353 353 # Forms posted to this method should contain a hidden field:
354 354 # <input type="hidden" name="_method" value="PUT" />
355 355 # Or using helpers:
356 356 # h.form(url('admin_settings_my_account_update'),
357 357 # method='put')
358 358 # url('admin_settings_my_account_update', id=ID)
359 359 user_model = UserModel()
360 360 uid = self.rhodecode_user.user_id
361 361 _form = UserForm(edit=True,
362 362 old_data={'user_id': uid,
363 363 'email': self.rhodecode_user.email})()
364 364 form_result = {}
365 365 try:
366 366 form_result = _form.to_python(dict(request.POST))
367 367 user_model.update_my_account(uid, form_result)
368 368 h.flash(_('Your account was updated successfully'),
369 369 category='success')
370 370 Session.commit()
371 371 except formencode.Invalid, errors:
372 372 c.user = User.get(self.rhodecode_user.user_id)
373 373 all_repos = self.sa.query(Repository)\
374 374 .filter(Repository.user_id == c.user.user_id)\
375 375 .order_by(func.lower(Repository.repo_name))\
376 376 .all()
377 377 c.user_repos = ScmModel().get_repos(all_repos)
378 378
379 379 c.form = htmlfill.render(
380 380 render('admin/users/user_edit_my_account_form.html'),
381 381 defaults=errors.value,
382 382 errors=errors.error_dict or {},
383 383 prefix_error=False,
384 384 encoding="UTF-8")
385 385 return render('admin/users/user_edit_my_account.html')
386 386 except Exception:
387 387 log.error(traceback.format_exc())
388 388 h.flash(_('error occurred during update of user %s') \
389 389 % form_result.get('username'), category='error')
390 390
391 391 return redirect(url('my_account'))
392 392
393 393 @NotAnonymous()
394 394 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
395 395 def create_repository(self):
396 396 """GET /_admin/create_repository: Form to create a new item"""
397 397
398 398 c.repo_groups = RepoGroup.groups_choices()
399 399 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
400 c.landing_revs = ScmModel().get_repo_landing_revs()
400 401
401 402 new_repo = request.GET.get('repo', '')
402 403 c.new_repo = repo_name_slug(new_repo)
403 404
404 405 return render('admin/repos/repo_add_create_repository.html')
405 406
406 407 def get_hg_ui_settings(self):
407 408 ret = self.sa.query(RhodeCodeUi).all()
408 409
409 410 if not ret:
410 411 raise Exception('Could not get application ui settings !')
411 412 settings = {}
412 413 for each in ret:
413 414 k = each.ui_key
414 415 v = each.ui_value
415 416 if k == '/':
416 417 k = 'root_path'
417 418
418 419 if k.find('.') != -1:
419 420 k = k.replace('.', '_')
420 421
421 422 if each.ui_section == 'hooks':
422 423 v = each.ui_active
423 424
424 425 settings[each.ui_section + '_' + k] = v
425 426
426 427 return settings
@@ -1,671 +1,677
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 from os.path import abspath
36 36 from os.path import dirname as dn, join as jn
37 37
38 38 from paste.script.command import Command, BadCommand
39 39
40 40 from mercurial import ui, config
41 41
42 42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 43
44 44 from rhodecode.lib.vcs import get_backend
45 45 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.utils.helpers import get_scm
48 48 from rhodecode.lib.vcs.exceptions import VCSError
49 49
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 from rhodecode.model import meta
53 53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 54 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm,\
55 55 CacheInvalidation
56 56 from rhodecode.model.meta import Session
57 57 from rhodecode.model.repos_group import ReposGroupModel
58 58 from rhodecode.lib.utils2 import safe_str, safe_unicode
59 59 from rhodecode.lib.vcs.utils.fakemod import create_module
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
64 64
65 65
66 66 def recursive_replace(str_, replace=' '):
67 67 """
68 68 Recursive replace of given sign to just one instance
69 69
70 70 :param str_: given string
71 71 :param replace: char to find and replace multiple instances
72 72
73 73 Examples::
74 74 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
75 75 'Mighty-Mighty-Bo-sstones'
76 76 """
77 77
78 78 if str_.find(replace * 2) == -1:
79 79 return str_
80 80 else:
81 81 str_ = str_.replace(replace * 2, replace)
82 82 return recursive_replace(str_, replace)
83 83
84 84
85 85 def repo_name_slug(value):
86 86 """
87 87 Return slug of name of repository
88 88 This function is called on each creation/modification
89 89 of repository to prevent bad names in repo
90 90 """
91 91
92 92 slug = remove_formatting(value)
93 93 slug = strip_tags(slug)
94 94
95 95 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
96 96 slug = slug.replace(c, '-')
97 97 slug = recursive_replace(slug, '-')
98 98 slug = collapse(slug, '-')
99 99 return slug
100 100
101 101
102 102 def get_repo_slug(request):
103 103 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 104 if _repo:
105 105 _repo = _repo.rstrip('/')
106 106 return _repo
107 107
108 108
109 109 def get_repos_group_slug(request):
110 110 _group = request.environ['pylons.routes_dict'].get('group_name')
111 111 if _group:
112 112 _group = _group.rstrip('/')
113 113 return _group
114 114
115 115
116 116 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
117 117 """
118 118 Action logger for various actions made by users
119 119
120 120 :param user: user that made this action, can be a unique username string or
121 121 object containing user_id attribute
122 122 :param action: action to log, should be on of predefined unique actions for
123 123 easy translations
124 124 :param repo: string name of repository or object containing repo_id,
125 125 that action was made on
126 126 :param ipaddr: optional ip address from what the action was made
127 127 :param sa: optional sqlalchemy session
128 128
129 129 """
130 130
131 131 if not sa:
132 132 sa = meta.Session
133 133
134 134 try:
135 135 if hasattr(user, 'user_id'):
136 136 user_obj = user
137 137 elif isinstance(user, basestring):
138 138 user_obj = User.get_by_username(user)
139 139 else:
140 140 raise Exception('You have to provide user object or username')
141 141
142 142 if hasattr(repo, 'repo_id'):
143 143 repo_obj = Repository.get(repo.repo_id)
144 144 repo_name = repo_obj.repo_name
145 145 elif isinstance(repo, basestring):
146 146 repo_name = repo.lstrip('/')
147 147 repo_obj = Repository.get_by_repo_name(repo_name)
148 148 else:
149 149 repo_obj = None
150 150 repo_name = ''
151 151
152 152 user_log = UserLog()
153 153 user_log.user_id = user_obj.user_id
154 154 user_log.action = safe_unicode(action)
155 155
156 156 user_log.repository = repo_obj
157 157 user_log.repository_name = repo_name
158 158
159 159 user_log.action_date = datetime.datetime.now()
160 160 user_log.user_ip = ipaddr
161 161 sa.add(user_log)
162 162
163 163 log.info(
164 164 'Adding user %s, action %s on %s' % (user_obj, action,
165 165 safe_unicode(repo))
166 166 )
167 167 if commit:
168 168 sa.commit()
169 169 except:
170 170 log.error(traceback.format_exc())
171 171 raise
172 172
173 173
174 174 def get_repos(path, recursive=False):
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
185 185 def _get_repos(p):
186 186 if not os.access(p, os.W_OK):
187 187 return
188 188 for dirpath in os.listdir(p):
189 189 if os.path.isfile(os.path.join(p, dirpath)):
190 190 continue
191 191 cur_path = os.path.join(p, dirpath)
192 192 try:
193 193 scm_info = get_scm(cur_path)
194 194 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
195 195 except VCSError:
196 196 if not recursive:
197 197 continue
198 198 #check if this dir containts other repos for recursive scan
199 199 rec_path = os.path.join(p, dirpath)
200 200 if os.path.isdir(rec_path):
201 201 for inner_scm in _get_repos(rec_path):
202 202 yield inner_scm
203 203
204 204 return _get_repos(path)
205 205
206 206
207 207 def is_valid_repo(repo_name, base_path):
208 208 """
209 209 Returns True if given path is a valid repository False otherwise
210 210
211 211 :param repo_name:
212 212 :param base_path:
213 213
214 214 :return True: if given path is a valid repository
215 215 """
216 216 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
217 217
218 218 try:
219 219 get_scm(full_path)
220 220 return True
221 221 except VCSError:
222 222 return False
223 223
224 224
225 225 def is_valid_repos_group(repos_group_name, base_path):
226 226 """
227 227 Returns True if given path is a repos group False otherwise
228 228
229 229 :param repo_name:
230 230 :param base_path:
231 231 """
232 232 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
233 233
234 234 # check if it's not a repo
235 235 if is_valid_repo(repos_group_name, base_path):
236 236 return False
237 237
238 238 # check if it's a valid path
239 239 if os.path.isdir(full_path):
240 240 return True
241 241
242 242 return False
243 243
244 244
245 245 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
246 246 while True:
247 247 ok = raw_input(prompt)
248 248 if ok in ('y', 'ye', 'yes'):
249 249 return True
250 250 if ok in ('n', 'no', 'nop', 'nope'):
251 251 return False
252 252 retries = retries - 1
253 253 if retries < 0:
254 254 raise IOError
255 255 print complaint
256 256
257 257 #propagated from mercurial documentation
258 258 ui_sections = ['alias', 'auth',
259 259 'decode/encode', 'defaults',
260 260 'diff', 'email',
261 261 'extensions', 'format',
262 262 'merge-patterns', 'merge-tools',
263 263 'hooks', 'http_proxy',
264 264 'smtp', 'patch',
265 265 'paths', 'profiling',
266 266 'server', 'trusted',
267 267 'ui', 'web', ]
268 268
269 269
270 270 def make_ui(read_from='file', path=None, checkpaths=True):
271 271 """
272 272 A function that will read python rc files or database
273 273 and make an mercurial ui object from read options
274 274
275 275 :param path: path to mercurial config file
276 276 :param checkpaths: check the path
277 277 :param read_from: read from 'file' or 'db'
278 278 """
279 279
280 280 baseui = ui.ui()
281 281
282 282 # clean the baseui object
283 283 baseui._ocfg = config.config()
284 284 baseui._ucfg = config.config()
285 285 baseui._tcfg = config.config()
286 286
287 287 if read_from == 'file':
288 288 if not os.path.isfile(path):
289 289 log.debug('hgrc file is not present at %s skipping...' % path)
290 290 return False
291 291 log.debug('reading hgrc from %s' % path)
292 292 cfg = config.config()
293 293 cfg.read(path)
294 294 for section in ui_sections:
295 295 for k, v in cfg.items(section):
296 296 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
297 297 baseui.setconfig(section, k, v)
298 298
299 299 elif read_from == 'db':
300 300 sa = meta.Session
301 301 ret = sa.query(RhodeCodeUi)\
302 302 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
303 303 .all()
304 304
305 305 hg_ui = ret
306 306 for ui_ in hg_ui:
307 307 if ui_.ui_active:
308 308 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
309 309 ui_.ui_key, ui_.ui_value)
310 310 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
311 311
312 312 meta.Session.remove()
313 313 return baseui
314 314
315 315
316 316 def set_rhodecode_config(config):
317 317 """
318 318 Updates pylons config with new settings from database
319 319
320 320 :param config:
321 321 """
322 322 hgsettings = RhodeCodeSetting.get_app_settings()
323 323
324 324 for k, v in hgsettings.items():
325 325 config[k] = v
326 326
327 327
328 328 def invalidate_cache(cache_key, *args):
329 329 """
330 330 Puts cache invalidation task into db for
331 331 further global cache invalidation
332 332 """
333 333
334 334 from rhodecode.model.scm import ScmModel
335 335
336 336 if cache_key.startswith('get_repo_cached_'):
337 337 name = cache_key.split('get_repo_cached_')[-1]
338 338 ScmModel().mark_for_invalidation(name)
339 339
340 340
341 341 class EmptyChangeset(BaseChangeset):
342 342 """
343 343 An dummy empty changeset. It's possible to pass hash when creating
344 344 an EmptyChangeset
345 345 """
346 346
347 347 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
348 348 alias=None):
349 349 self._empty_cs = cs
350 350 self.revision = -1
351 351 self.message = ''
352 352 self.author = ''
353 353 self.date = ''
354 354 self.repository = repo
355 355 self.requested_revision = requested_revision
356 356 self.alias = alias
357 357
358 358 @LazyProperty
359 359 def raw_id(self):
360 360 """
361 361 Returns raw string identifying this changeset, useful for web
362 362 representation.
363 363 """
364 364
365 365 return self._empty_cs
366 366
367 367 @LazyProperty
368 368 def branch(self):
369 369 return get_backend(self.alias).DEFAULT_BRANCH_NAME
370 370
371 371 @LazyProperty
372 372 def short_id(self):
373 373 return self.raw_id[:12]
374 374
375 375 def get_file_changeset(self, path):
376 376 return self
377 377
378 378 def get_file_content(self, path):
379 379 return u''
380 380
381 381 def get_file_size(self, path):
382 382 return 0
383 383
384 384
385 385 def map_groups(path):
386 386 """
387 387 Given a full path to a repository, create all nested groups that this
388 388 repo is inside. This function creates parent-child relationships between
389 389 groups and creates default perms for all new groups.
390 390
391 391 :param paths: full path to repository
392 392 """
393 393 sa = meta.Session
394 394 groups = path.split(Repository.url_sep())
395 395 parent = None
396 396 group = None
397 397
398 398 # last element is repo in nested groups structure
399 399 groups = groups[:-1]
400 400 rgm = ReposGroupModel(sa)
401 401 for lvl, group_name in enumerate(groups):
402 402 group_name = '/'.join(groups[:lvl] + [group_name])
403 403 group = RepoGroup.get_by_group_name(group_name)
404 404 desc = '%s group' % group_name
405 405
406 406 # skip folders that are now removed repos
407 407 if REMOVED_REPO_PAT.match(group_name):
408 408 break
409 409
410 410 if group is None:
411 411 log.debug('creating group level: %s group_name: %s' % (lvl,
412 412 group_name))
413 413 group = RepoGroup(group_name, parent)
414 414 group.group_description = desc
415 415 sa.add(group)
416 416 rgm._create_default_perms(group)
417 417 sa.flush()
418 418 parent = group
419 419 return group
420 420
421 421
422 422 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
423 423 """
424 424 maps all repos given in initial_repo_list, non existing repositories
425 425 are created, if remove_obsolete is True it also check for db entries
426 426 that are not in initial_repo_list and removes them.
427 427
428 428 :param initial_repo_list: list of repositories found by scanning methods
429 429 :param remove_obsolete: check for obsolete entries in database
430 430 """
431 431 from rhodecode.model.repo import RepoModel
432 432 sa = meta.Session
433 433 rm = RepoModel()
434 434 user = sa.query(User).filter(User.admin == True).first()
435 435 if user is None:
436 436 raise Exception('Missing administrative account !')
437 437 added = []
438 438
439 439 for name, repo in initial_repo_list.items():
440 440 group = map_groups(name)
441 441 if not rm.get_by_repo_name(name, cache=False):
442 442 log.info('repository %s not found creating default' % name)
443 443 added.append(name)
444 444 form_data = {
445 445 'repo_name': name,
446 446 'repo_name_full': name,
447 447 'repo_type': repo.alias,
448 448 'description': repo.description \
449 449 if repo.description != 'unknown' else '%s repository' % name,
450 450 'private': False,
451 'group_id': getattr(group, 'group_id', None)
451 'group_id': getattr(group, 'group_id', None),
452 'landing_rev': repo.DEFAULT_BRANCH_NAME
452 453 }
453 454 rm.create(form_data, user, just_db=True)
454 455 sa.commit()
455 456 removed = []
456 457 if remove_obsolete:
457 458 # remove from database those repositories that are not in the filesystem
458 459 for repo in sa.query(Repository).all():
459 460 if repo.repo_name not in initial_repo_list.keys():
460 461 log.debug("Removing non existing repository found in db %s" %
461 462 repo.repo_name)
462 463 removed.append(repo.repo_name)
463 464 sa.delete(repo)
464 465 sa.commit()
465 466
466 467 # clear cache keys
467 468 log.debug("Clearing cache keys now...")
468 469 CacheInvalidation.clear_cache()
469 470 sa.commit()
470 471 return added, removed
471 472
472 473
473 474 # set cache regions for beaker so celery can utilise it
474 475 def add_cache(settings):
475 476 cache_settings = {'regions': None}
476 477 for key in settings.keys():
477 478 for prefix in ['beaker.cache.', 'cache.']:
478 479 if key.startswith(prefix):
479 480 name = key.split(prefix)[1].strip()
480 481 cache_settings[name] = settings[key].strip()
481 482 if cache_settings['regions']:
482 483 for region in cache_settings['regions'].split(','):
483 484 region = region.strip()
484 485 region_settings = {}
485 486 for key, value in cache_settings.items():
486 487 if key.startswith(region):
487 488 region_settings[key.split('.')[1]] = value
488 489 region_settings['expire'] = int(region_settings.get('expire',
489 490 60))
490 491 region_settings.setdefault('lock_dir',
491 492 cache_settings.get('lock_dir'))
492 493 region_settings.setdefault('data_dir',
493 494 cache_settings.get('data_dir'))
494 495
495 496 if 'type' not in region_settings:
496 497 region_settings['type'] = cache_settings.get('type',
497 498 'memory')
498 499 beaker.cache.cache_regions[region] = region_settings
499 500
500 501
501 502 def load_rcextensions(root_path):
502 503 import rhodecode
503 504 from rhodecode.config import conf
504 505
505 506 path = os.path.join(root_path, 'rcextensions', '__init__.py')
506 507 if os.path.isfile(path):
507 508 rcext = create_module('rc', path)
508 509 EXT = rhodecode.EXTENSIONS = rcext
509 510 log.debug('Found rcextensions now loading %s...' % rcext)
510 511
511 512 # Additional mappings that are not present in the pygments lexers
512 513 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
513 514
514 515 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
515 516
516 517 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
517 518 log.debug('settings custom INDEX_EXTENSIONS')
518 519 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
519 520
520 521 #ADDITIONAL MAPPINGS
521 522 log.debug('adding extra into INDEX_EXTENSIONS')
522 523 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
523 524
524 525
525 526 #==============================================================================
526 527 # TEST FUNCTIONS AND CREATORS
527 528 #==============================================================================
528 529 def create_test_index(repo_location, config, full_index):
529 530 """
530 531 Makes default test index
531 532
532 533 :param config: test config
533 534 :param full_index:
534 535 """
535 536
536 537 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
537 538 from rhodecode.lib.pidlock import DaemonLock, LockHeld
538 539
539 540 repo_location = repo_location
540 541
541 542 index_location = os.path.join(config['app_conf']['index_dir'])
542 543 if not os.path.exists(index_location):
543 544 os.makedirs(index_location)
544 545
545 546 try:
546 547 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
547 548 WhooshIndexingDaemon(index_location=index_location,
548 549 repo_location=repo_location)\
549 550 .run(full_index=full_index)
550 551 l.release()
551 552 except LockHeld:
552 553 pass
553 554
554 555
555 556 def create_test_env(repos_test_path, config):
556 557 """
557 558 Makes a fresh database and
558 559 install test repository into tmp dir
559 560 """
560 561 from rhodecode.lib.db_manage import DbManage
561 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
562 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
562 563
563 564 # PART ONE create db
564 565 dbconf = config['sqlalchemy.db1.url']
565 566 log.debug('making test db %s' % dbconf)
566 567
567 568 # create test dir if it doesn't exist
568 569 if not os.path.isdir(repos_test_path):
569 570 log.debug('Creating testdir %s' % repos_test_path)
570 571 os.makedirs(repos_test_path)
571 572
572 573 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
573 574 tests=True)
574 575 dbmanage.create_tables(override=True)
575 576 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
576 577 dbmanage.create_default_user()
577 578 dbmanage.admin_prompt()
578 579 dbmanage.create_permissions()
579 580 dbmanage.populate_default_permissions()
580 581 Session.commit()
581 582 # PART TWO make test repo
582 583 log.debug('making test vcs repositories')
583 584
584 585 idx_path = config['app_conf']['index_dir']
585 586 data_path = config['app_conf']['cache_dir']
586 587
587 588 #clean index and data
588 589 if idx_path and os.path.exists(idx_path):
589 590 log.debug('remove %s' % idx_path)
590 591 shutil.rmtree(idx_path)
591 592
592 593 if data_path and os.path.exists(data_path):
593 594 log.debug('remove %s' % data_path)
594 595 shutil.rmtree(data_path)
595 596
596 #CREATE DEFAULT HG REPOSITORY
597 #CREATE DEFAULT TEST REPOS
597 598 cur_dir = dn(dn(abspath(__file__)))
598 599 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
599 600 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
600 601 tar.close()
601 602
603 cur_dir = dn(dn(abspath(__file__)))
604 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
605 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
606 tar.close()
607
602 608 #LOAD VCS test stuff
603 609 from rhodecode.tests.vcs import setup_package
604 610 setup_package()
605 611
606 612
607 613 #==============================================================================
608 614 # PASTER COMMANDS
609 615 #==============================================================================
610 616 class BasePasterCommand(Command):
611 617 """
612 618 Abstract Base Class for paster commands.
613 619
614 620 The celery commands are somewhat aggressive about loading
615 621 celery.conf, and since our module sets the `CELERY_LOADER`
616 622 environment variable to our loader, we have to bootstrap a bit and
617 623 make sure we've had a chance to load the pylons config off of the
618 624 command line, otherwise everything fails.
619 625 """
620 626 min_args = 1
621 627 min_args_error = "Please provide a paster config file as an argument."
622 628 takes_config_file = 1
623 629 requires_config_file = True
624 630
625 631 def notify_msg(self, msg, log=False):
626 632 """Make a notification to user, additionally if logger is passed
627 633 it logs this action using given logger
628 634
629 635 :param msg: message that will be printed to user
630 636 :param log: logging instance, to use to additionally log this message
631 637
632 638 """
633 639 if log and isinstance(log, logging):
634 640 log(msg)
635 641
636 642 def run(self, args):
637 643 """
638 644 Overrides Command.run
639 645
640 646 Checks for a config file argument and loads it.
641 647 """
642 648 if len(args) < self.min_args:
643 649 raise BadCommand(
644 650 self.min_args_error % {'min_args': self.min_args,
645 651 'actual_args': len(args)})
646 652
647 653 # Decrement because we're going to lob off the first argument.
648 654 # @@ This is hacky
649 655 self.min_args -= 1
650 656 self.bootstrap_config(args[0])
651 657 self.update_parser()
652 658 return super(BasePasterCommand, self).run(args[1:])
653 659
654 660 def update_parser(self):
655 661 """
656 662 Abstract method. Allows for the class's parser to be updated
657 663 before the superclass's `run` method is called. Necessary to
658 664 allow options/arguments to be passed through to the underlying
659 665 celery command.
660 666 """
661 667 raise NotImplementedError("Abstract Method.")
662 668
663 669 def bootstrap_config(self, conf):
664 670 """
665 671 Loads the pylons configuration.
666 672 """
667 673 from pylons import config as pylonsconfig
668 674
669 675 self.path_to_ini_file = os.path.realpath(conf)
670 676 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
671 677 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,1348 +1,1349
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 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 logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 from collections import defaultdict
32 32
33 33 from sqlalchemy import *
34 34 from sqlalchemy.ext.hybrid import hybrid_property
35 35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 36 from beaker.cache import cache_region, region_invalidate
37 37
38 38 from rhodecode.lib.vcs import get_backend
39 39 from rhodecode.lib.vcs.utils.helpers import get_scm
40 40 from rhodecode.lib.vcs.exceptions import VCSError
41 41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
42 42
43 43 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
44 44 safe_unicode
45 45 from rhodecode.lib.compat import json
46 46 from rhodecode.lib.caching_query import FromCache
47 47 from rhodecode.model.meta import Base, Session
48 48
49 49
50 50 URL_SEP = '/'
51 51 log = logging.getLogger(__name__)
52 52
53 53 #==============================================================================
54 54 # BASE CLASSES
55 55 #==============================================================================
56 56
57 57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
58 58
59 59
60 60 class ModelSerializer(json.JSONEncoder):
61 61 """
62 62 Simple Serializer for JSON,
63 63
64 64 usage::
65 65
66 66 to make object customized for serialization implement a __json__
67 67 method that will return a dict for serialization into json
68 68
69 69 example::
70 70
71 71 class Task(object):
72 72
73 73 def __init__(self, name, value):
74 74 self.name = name
75 75 self.value = value
76 76
77 77 def __json__(self):
78 78 return dict(name=self.name,
79 79 value=self.value)
80 80
81 81 """
82 82
83 83 def default(self, obj):
84 84
85 85 if hasattr(obj, '__json__'):
86 86 return obj.__json__()
87 87 else:
88 88 return json.JSONEncoder.default(self, obj)
89 89
90 90
91 91 class BaseModel(object):
92 92 """
93 93 Base Model for all classess
94 94 """
95 95
96 96 @classmethod
97 97 def _get_keys(cls):
98 98 """return column names for this model """
99 99 return class_mapper(cls).c.keys()
100 100
101 101 def get_dict(self):
102 102 """
103 103 return dict with keys and values corresponding
104 104 to this model data """
105 105
106 106 d = {}
107 107 for k in self._get_keys():
108 108 d[k] = getattr(self, k)
109 109
110 110 # also use __json__() if present to get additional fields
111 111 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
112 112 d[k] = val
113 113 return d
114 114
115 115 def get_appstruct(self):
116 116 """return list with keys and values tupples corresponding
117 117 to this model data """
118 118
119 119 l = []
120 120 for k in self._get_keys():
121 121 l.append((k, getattr(self, k),))
122 122 return l
123 123
124 124 def populate_obj(self, populate_dict):
125 125 """populate model with data from given populate_dict"""
126 126
127 127 for k in self._get_keys():
128 128 if k in populate_dict:
129 129 setattr(self, k, populate_dict[k])
130 130
131 131 @classmethod
132 132 def query(cls):
133 133 return Session.query(cls)
134 134
135 135 @classmethod
136 136 def get(cls, id_):
137 137 if id_:
138 138 return cls.query().get(id_)
139 139
140 140 @classmethod
141 141 def getAll(cls):
142 142 return cls.query().all()
143 143
144 144 @classmethod
145 145 def delete(cls, id_):
146 146 obj = cls.query().get(id_)
147 147 Session.delete(obj)
148 148
149 149 def __repr__(self):
150 150 if hasattr(self, '__unicode__'):
151 151 # python repr needs to return str
152 152 return safe_str(self.__unicode__())
153 153 return '<DB:%s>' % (self.__class__.__name__)
154 154
155 155
156 156 class RhodeCodeSetting(Base, BaseModel):
157 157 __tablename__ = 'rhodecode_settings'
158 158 __table_args__ = (
159 159 UniqueConstraint('app_settings_name'),
160 160 {'extend_existing': True, 'mysql_engine': 'InnoDB',
161 161 'mysql_charset': 'utf8'}
162 162 )
163 163 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
164 164 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
165 165 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
166 166
167 167 def __init__(self, k='', v=''):
168 168 self.app_settings_name = k
169 169 self.app_settings_value = v
170 170
171 171 @validates('_app_settings_value')
172 172 def validate_settings_value(self, key, val):
173 173 assert type(val) == unicode
174 174 return val
175 175
176 176 @hybrid_property
177 177 def app_settings_value(self):
178 178 v = self._app_settings_value
179 179 if self.app_settings_name == 'ldap_active':
180 180 v = str2bool(v)
181 181 return v
182 182
183 183 @app_settings_value.setter
184 184 def app_settings_value(self, val):
185 185 """
186 186 Setter that will always make sure we use unicode in app_settings_value
187 187
188 188 :param val:
189 189 """
190 190 self._app_settings_value = safe_unicode(val)
191 191
192 192 def __unicode__(self):
193 193 return u"<%s('%s:%s')>" % (
194 194 self.__class__.__name__,
195 195 self.app_settings_name, self.app_settings_value
196 196 )
197 197
198 198 @classmethod
199 199 def get_by_name(cls, ldap_key):
200 200 return cls.query()\
201 201 .filter(cls.app_settings_name == ldap_key).scalar()
202 202
203 203 @classmethod
204 204 def get_app_settings(cls, cache=False):
205 205
206 206 ret = cls.query()
207 207
208 208 if cache:
209 209 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
210 210
211 211 if not ret:
212 212 raise Exception('Could not get application settings !')
213 213 settings = {}
214 214 for each in ret:
215 215 settings['rhodecode_' + each.app_settings_name] = \
216 216 each.app_settings_value
217 217
218 218 return settings
219 219
220 220 @classmethod
221 221 def get_ldap_settings(cls, cache=False):
222 222 ret = cls.query()\
223 223 .filter(cls.app_settings_name.startswith('ldap_')).all()
224 224 fd = {}
225 225 for row in ret:
226 226 fd.update({row.app_settings_name: row.app_settings_value})
227 227
228 228 return fd
229 229
230 230
231 231 class RhodeCodeUi(Base, BaseModel):
232 232 __tablename__ = 'rhodecode_ui'
233 233 __table_args__ = (
234 234 UniqueConstraint('ui_key'),
235 235 {'extend_existing': True, 'mysql_engine': 'InnoDB',
236 236 'mysql_charset': 'utf8'}
237 237 )
238 238
239 239 HOOK_UPDATE = 'changegroup.update'
240 240 HOOK_REPO_SIZE = 'changegroup.repo_size'
241 241 HOOK_PUSH = 'changegroup.push_logger'
242 242 HOOK_PULL = 'preoutgoing.pull_logger'
243 243
244 244 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
245 245 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 246 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 247 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
248 248 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
249 249
250 250 @classmethod
251 251 def get_by_key(cls, key):
252 252 return cls.query().filter(cls.ui_key == key)
253 253
254 254 @classmethod
255 255 def get_builtin_hooks(cls):
256 256 q = cls.query()
257 257 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
258 258 cls.HOOK_REPO_SIZE,
259 259 cls.HOOK_PUSH, cls.HOOK_PULL]))
260 260 return q.all()
261 261
262 262 @classmethod
263 263 def get_custom_hooks(cls):
264 264 q = cls.query()
265 265 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
266 266 cls.HOOK_REPO_SIZE,
267 267 cls.HOOK_PUSH, cls.HOOK_PULL]))
268 268 q = q.filter(cls.ui_section == 'hooks')
269 269 return q.all()
270 270
271 271 @classmethod
272 272 def get_repos_location(cls):
273 273 return cls.get_by_key('/').one().ui_value
274 274
275 275 @classmethod
276 276 def create_or_update_hook(cls, key, val):
277 277 new_ui = cls.get_by_key(key).scalar() or cls()
278 278 new_ui.ui_section = 'hooks'
279 279 new_ui.ui_active = True
280 280 new_ui.ui_key = key
281 281 new_ui.ui_value = val
282 282
283 283 Session.add(new_ui)
284 284
285 285
286 286 class User(Base, BaseModel):
287 287 __tablename__ = 'users'
288 288 __table_args__ = (
289 289 UniqueConstraint('username'), UniqueConstraint('email'),
290 290 {'extend_existing': True, 'mysql_engine': 'InnoDB',
291 291 'mysql_charset': 'utf8'}
292 292 )
293 293 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
294 294 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 295 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 296 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
297 297 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
298 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
298 name = Column("firstname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 299 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
300 300 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
301 301 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
302 302 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 303 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
304 304
305 305 user_log = relationship('UserLog', cascade='all')
306 306 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
307 307
308 308 repositories = relationship('Repository')
309 309 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
310 310 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
311 311 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
312 312
313 313 group_member = relationship('UsersGroupMember', cascade='all')
314 314
315 315 notifications = relationship('UserNotification', cascade='all')
316 316 # notifications assigned to this user
317 317 user_created_notifications = relationship('Notification', cascade='all')
318 318 # comments created by this user
319 319 user_comments = relationship('ChangesetComment', cascade='all')
320 320
321 321 @hybrid_property
322 322 def email(self):
323 323 return self._email
324 324
325 325 @email.setter
326 326 def email(self, val):
327 327 self._email = val.lower() if val else None
328 328
329 329 @property
330 330 def full_name(self):
331 331 return '%s %s' % (self.name, self.lastname)
332 332
333 333 @property
334 334 def full_name_or_username(self):
335 335 return ('%s %s' % (self.name, self.lastname)
336 336 if (self.name and self.lastname) else self.username)
337 337
338 338 @property
339 339 def full_contact(self):
340 340 return '%s %s <%s>' % (self.name, self.lastname, self.email)
341 341
342 342 @property
343 343 def short_contact(self):
344 344 return '%s %s' % (self.name, self.lastname)
345 345
346 346 @property
347 347 def is_admin(self):
348 348 return self.admin
349 349
350 350 def __unicode__(self):
351 351 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
352 352 self.user_id, self.username)
353 353
354 354 @classmethod
355 355 def get_by_username(cls, username, case_insensitive=False, cache=False):
356 356 if case_insensitive:
357 357 q = cls.query().filter(cls.username.ilike(username))
358 358 else:
359 359 q = cls.query().filter(cls.username == username)
360 360
361 361 if cache:
362 362 q = q.options(FromCache(
363 363 "sql_cache_short",
364 364 "get_user_%s" % _hash_key(username)
365 365 )
366 366 )
367 367 return q.scalar()
368 368
369 369 @classmethod
370 370 def get_by_api_key(cls, api_key, cache=False):
371 371 q = cls.query().filter(cls.api_key == api_key)
372 372
373 373 if cache:
374 374 q = q.options(FromCache("sql_cache_short",
375 375 "get_api_key_%s" % api_key))
376 376 return q.scalar()
377 377
378 378 @classmethod
379 379 def get_by_email(cls, email, case_insensitive=False, cache=False):
380 380 if case_insensitive:
381 381 q = cls.query().filter(cls.email.ilike(email))
382 382 else:
383 383 q = cls.query().filter(cls.email == email)
384 384
385 385 if cache:
386 386 q = q.options(FromCache("sql_cache_short",
387 387 "get_api_key_%s" % email))
388 388 return q.scalar()
389 389
390 390 def update_lastlogin(self):
391 391 """Update user lastlogin"""
392 392 self.last_login = datetime.datetime.now()
393 393 Session.add(self)
394 394 log.debug('updated user %s lastlogin' % self.username)
395 395
396 396 def __json__(self):
397 397 return dict(
398 398 user_id=self.user_id,
399 399 first_name=self.name,
400 400 last_name=self.lastname,
401 401 email=self.email,
402 402 full_name=self.full_name,
403 403 full_name_or_username=self.full_name_or_username,
404 404 short_contact=self.short_contact,
405 405 full_contact=self.full_contact
406 406 )
407 407
408 408
409 409 class UserLog(Base, BaseModel):
410 410 __tablename__ = 'user_logs'
411 411 __table_args__ = (
412 412 {'extend_existing': True, 'mysql_engine': 'InnoDB',
413 413 'mysql_charset': 'utf8'},
414 414 )
415 415 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
416 416 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
417 417 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
418 418 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
419 419 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
420 420 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
421 421 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
422 422
423 423 @property
424 424 def action_as_day(self):
425 425 return datetime.date(*self.action_date.timetuple()[:3])
426 426
427 427 user = relationship('User')
428 428 repository = relationship('Repository', cascade='')
429 429
430 430
431 431 class UsersGroup(Base, BaseModel):
432 432 __tablename__ = 'users_groups'
433 433 __table_args__ = (
434 434 {'extend_existing': True, 'mysql_engine': 'InnoDB',
435 435 'mysql_charset': 'utf8'},
436 436 )
437 437
438 438 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
439 439 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
440 440 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
441 441
442 442 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
443 443 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
444 444 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
445 445
446 446 def __unicode__(self):
447 447 return u'<userGroup(%s)>' % (self.users_group_name)
448 448
449 449 @classmethod
450 450 def get_by_group_name(cls, group_name, cache=False,
451 451 case_insensitive=False):
452 452 if case_insensitive:
453 453 q = cls.query().filter(cls.users_group_name.ilike(group_name))
454 454 else:
455 455 q = cls.query().filter(cls.users_group_name == group_name)
456 456 if cache:
457 457 q = q.options(FromCache(
458 458 "sql_cache_short",
459 459 "get_user_%s" % _hash_key(group_name)
460 460 )
461 461 )
462 462 return q.scalar()
463 463
464 464 @classmethod
465 465 def get(cls, users_group_id, cache=False):
466 466 users_group = cls.query()
467 467 if cache:
468 468 users_group = users_group.options(FromCache("sql_cache_short",
469 469 "get_users_group_%s" % users_group_id))
470 470 return users_group.get(users_group_id)
471 471
472 472
473 473 class UsersGroupMember(Base, BaseModel):
474 474 __tablename__ = 'users_groups_members'
475 475 __table_args__ = (
476 476 {'extend_existing': True, 'mysql_engine': 'InnoDB',
477 477 'mysql_charset': 'utf8'},
478 478 )
479 479
480 480 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
481 481 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
482 482 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
483 483
484 484 user = relationship('User', lazy='joined')
485 485 users_group = relationship('UsersGroup')
486 486
487 487 def __init__(self, gr_id='', u_id=''):
488 488 self.users_group_id = gr_id
489 489 self.user_id = u_id
490 490
491 491
492 492 class Repository(Base, BaseModel):
493 493 __tablename__ = 'repositories'
494 494 __table_args__ = (
495 495 UniqueConstraint('repo_name'),
496 496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
497 497 'mysql_charset': 'utf8'},
498 498 )
499 499
500 500 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
501 501 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
502 502 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
503 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
503 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
504 504 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
505 505 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
506 506 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
507 507 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
508 508 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
509 509 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
510 landing_rev = Column("landing_revision", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
510 511
511 512 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
512 513 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
513 514
514 515 user = relationship('User')
515 516 fork = relationship('Repository', remote_side=repo_id)
516 517 group = relationship('RepoGroup')
517 518 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
518 519 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
519 520 stats = relationship('Statistics', cascade='all', uselist=False)
520 521
521 522 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
522 523
523 524 logs = relationship('UserLog')
524 525
525 526 def __unicode__(self):
526 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
527 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
527 528 self.repo_name)
528 529
529 530 @classmethod
530 531 def url_sep(cls):
531 532 return URL_SEP
532 533
533 534 @classmethod
534 535 def get_by_repo_name(cls, repo_name):
535 536 q = Session.query(cls).filter(cls.repo_name == repo_name)
536 537 q = q.options(joinedload(Repository.fork))\
537 538 .options(joinedload(Repository.user))\
538 539 .options(joinedload(Repository.group))
539 540 return q.scalar()
540 541
541 542 @classmethod
542 543 def get_by_full_path(cls, repo_full_path):
543 544 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
544 545 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
545 546
546 547 @classmethod
547 548 def get_repo_forks(cls, repo_id):
548 549 return cls.query().filter(Repository.fork_id == repo_id)
549 550
550 551 @classmethod
551 552 def base_path(cls):
552 553 """
553 554 Returns base path when all repos are stored
554 555
555 556 :param cls:
556 557 """
557 558 q = Session.query(RhodeCodeUi)\
558 559 .filter(RhodeCodeUi.ui_key == cls.url_sep())
559 560 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
560 561 return q.one().ui_value
561 562
562 563 @property
563 564 def just_name(self):
564 565 return self.repo_name.split(Repository.url_sep())[-1]
565 566
566 567 @property
567 568 def groups_with_parents(self):
568 569 groups = []
569 570 if self.group is None:
570 571 return groups
571 572
572 573 cur_gr = self.group
573 574 groups.insert(0, cur_gr)
574 575 while 1:
575 576 gr = getattr(cur_gr, 'parent_group', None)
576 577 cur_gr = cur_gr.parent_group
577 578 if gr is None:
578 579 break
579 580 groups.insert(0, gr)
580 581
581 582 return groups
582 583
583 584 @property
584 585 def groups_and_repo(self):
585 586 return self.groups_with_parents, self.just_name
586 587
587 588 @LazyProperty
588 589 def repo_path(self):
589 590 """
590 591 Returns base full path for that repository means where it actually
591 592 exists on a filesystem
592 593 """
593 594 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
594 595 Repository.url_sep())
595 596 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
596 597 return q.one().ui_value
597 598
598 599 @property
599 600 def repo_full_path(self):
600 601 p = [self.repo_path]
601 602 # we need to split the name by / since this is how we store the
602 603 # names in the database, but that eventually needs to be converted
603 604 # into a valid system path
604 605 p += self.repo_name.split(Repository.url_sep())
605 606 return os.path.join(*p)
606 607
607 608 def get_new_name(self, repo_name):
608 609 """
609 610 returns new full repository name based on assigned group and new new
610 611
611 612 :param group_name:
612 613 """
613 614 path_prefix = self.group.full_path_splitted if self.group else []
614 615 return Repository.url_sep().join(path_prefix + [repo_name])
615 616
616 617 @property
617 618 def _ui(self):
618 619 """
619 620 Creates an db based ui object for this repository
620 621 """
621 622 from mercurial import ui
622 623 from mercurial import config
623 624 baseui = ui.ui()
624 625
625 626 #clean the baseui object
626 627 baseui._ocfg = config.config()
627 628 baseui._ucfg = config.config()
628 629 baseui._tcfg = config.config()
629 630
630 631 ret = RhodeCodeUi.query()\
631 632 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
632 633
633 634 hg_ui = ret
634 635 for ui_ in hg_ui:
635 636 if ui_.ui_active:
636 637 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
637 638 ui_.ui_key, ui_.ui_value)
638 639 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
639 640
640 641 return baseui
641 642
642 643 @classmethod
643 644 def is_valid(cls, repo_name):
644 645 """
645 646 returns True if given repo name is a valid filesystem repository
646 647
647 648 :param cls:
648 649 :param repo_name:
649 650 """
650 651 from rhodecode.lib.utils import is_valid_repo
651 652
652 653 return is_valid_repo(repo_name, cls.base_path())
653 654
654 655 #==========================================================================
655 656 # SCM PROPERTIES
656 657 #==========================================================================
657 658
658 659 def get_changeset(self, rev=None):
659 660 return get_changeset_safe(self.scm_instance, rev)
660 661
661 662 @property
662 663 def tip(self):
663 664 return self.get_changeset('tip')
664 665
665 666 @property
666 667 def author(self):
667 668 return self.tip.author
668 669
669 670 @property
670 671 def last_change(self):
671 672 return self.scm_instance.last_change
672 673
673 674 def comments(self, revisions=None):
674 675 """
675 676 Returns comments for this repository grouped by revisions
676 677
677 678 :param revisions: filter query by revisions only
678 679 """
679 680 cmts = ChangesetComment.query()\
680 681 .filter(ChangesetComment.repo == self)
681 682 if revisions:
682 683 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
683 684 grouped = defaultdict(list)
684 685 for cmt in cmts.all():
685 686 grouped[cmt.revision].append(cmt)
686 687 return grouped
687 688
688 689 #==========================================================================
689 690 # SCM CACHE INSTANCE
690 691 #==========================================================================
691 692
692 693 @property
693 694 def invalidate(self):
694 695 return CacheInvalidation.invalidate(self.repo_name)
695 696
696 697 def set_invalidate(self):
697 698 """
698 699 set a cache for invalidation for this instance
699 700 """
700 701 CacheInvalidation.set_invalidate(self.repo_name)
701 702
702 703 @LazyProperty
703 704 def scm_instance(self):
704 705 return self.__get_instance()
705 706
706 707 def scm_instance_cached(self, cache_map=None):
707 708 @cache_region('long_term')
708 709 def _c(repo_name):
709 710 return self.__get_instance()
710 711 rn = self.repo_name
711 712 log.debug('Getting cached instance of repo')
712 713
713 714 if cache_map:
714 715 # get using prefilled cache_map
715 716 invalidate_repo = cache_map[self.repo_name]
716 717 if invalidate_repo:
717 718 invalidate_repo = (None if invalidate_repo.cache_active
718 719 else invalidate_repo)
719 720 else:
720 721 # get from invalidate
721 722 invalidate_repo = self.invalidate
722 723
723 724 if invalidate_repo is not None:
724 725 region_invalidate(_c, None, rn)
725 726 # update our cache
726 727 CacheInvalidation.set_valid(invalidate_repo.cache_key)
727 728 return _c(rn)
728 729
729 730 def __get_instance(self):
730 731 repo_full_path = self.repo_full_path
731 732 try:
732 733 alias = get_scm(repo_full_path)[0]
733 734 log.debug('Creating instance of %s repository' % alias)
734 735 backend = get_backend(alias)
735 736 except VCSError:
736 737 log.error(traceback.format_exc())
737 738 log.error('Perhaps this repository is in db and not in '
738 739 'filesystem run rescan repositories with '
739 740 '"destroy old data " option from admin panel')
740 741 return
741 742
742 743 if alias == 'hg':
743 744
744 745 repo = backend(safe_str(repo_full_path), create=False,
745 746 baseui=self._ui)
746 747 # skip hidden web repository
747 748 if repo._get_hidden():
748 749 return
749 750 else:
750 751 repo = backend(repo_full_path, create=False)
751 752
752 753 return repo
753 754
754 755
755 756 class RepoGroup(Base, BaseModel):
756 757 __tablename__ = 'groups'
757 758 __table_args__ = (
758 759 UniqueConstraint('group_name', 'group_parent_id'),
759 760 CheckConstraint('group_id != group_parent_id'),
760 761 {'extend_existing': True, 'mysql_engine': 'InnoDB',
761 762 'mysql_charset': 'utf8'},
762 763 )
763 764 __mapper_args__ = {'order_by': 'group_name'}
764 765
765 766 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
766 767 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
767 768 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
768 769 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
769 770
770 771 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
771 772 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
772 773
773 774 parent_group = relationship('RepoGroup', remote_side=group_id)
774 775
775 776 def __init__(self, group_name='', parent_group=None):
776 777 self.group_name = group_name
777 778 self.parent_group = parent_group
778 779
779 780 def __unicode__(self):
780 781 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
781 782 self.group_name)
782 783
783 784 @classmethod
784 785 def groups_choices(cls):
785 786 from webhelpers.html import literal as _literal
786 787 repo_groups = [('', '')]
787 788 sep = ' &raquo; '
788 789 _name = lambda k: _literal(sep.join(k))
789 790
790 791 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
791 792 for x in cls.query().all()])
792 793
793 794 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
794 795 return repo_groups
795 796
796 797 @classmethod
797 798 def url_sep(cls):
798 799 return URL_SEP
799 800
800 801 @classmethod
801 802 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
802 803 if case_insensitive:
803 804 gr = cls.query()\
804 805 .filter(cls.group_name.ilike(group_name))
805 806 else:
806 807 gr = cls.query()\
807 808 .filter(cls.group_name == group_name)
808 809 if cache:
809 810 gr = gr.options(FromCache(
810 811 "sql_cache_short",
811 812 "get_group_%s" % _hash_key(group_name)
812 813 )
813 814 )
814 815 return gr.scalar()
815 816
816 817 @property
817 818 def parents(self):
818 819 parents_recursion_limit = 5
819 820 groups = []
820 821 if self.parent_group is None:
821 822 return groups
822 823 cur_gr = self.parent_group
823 824 groups.insert(0, cur_gr)
824 825 cnt = 0
825 826 while 1:
826 827 cnt += 1
827 828 gr = getattr(cur_gr, 'parent_group', None)
828 829 cur_gr = cur_gr.parent_group
829 830 if gr is None:
830 831 break
831 832 if cnt == parents_recursion_limit:
832 833 # this will prevent accidental infinit loops
833 834 log.error('group nested more than %s' %
834 835 parents_recursion_limit)
835 836 break
836 837
837 838 groups.insert(0, gr)
838 839 return groups
839 840
840 841 @property
841 842 def children(self):
842 843 return RepoGroup.query().filter(RepoGroup.parent_group == self)
843 844
844 845 @property
845 846 def name(self):
846 847 return self.group_name.split(RepoGroup.url_sep())[-1]
847 848
848 849 @property
849 850 def full_path(self):
850 851 return self.group_name
851 852
852 853 @property
853 854 def full_path_splitted(self):
854 855 return self.group_name.split(RepoGroup.url_sep())
855 856
856 857 @property
857 858 def repositories(self):
858 859 return Repository.query()\
859 860 .filter(Repository.group == self)\
860 861 .order_by(Repository.repo_name)
861 862
862 863 @property
863 864 def repositories_recursive_count(self):
864 865 cnt = self.repositories.count()
865 866
866 867 def children_count(group):
867 868 cnt = 0
868 869 for child in group.children:
869 870 cnt += child.repositories.count()
870 871 cnt += children_count(child)
871 872 return cnt
872 873
873 874 return cnt + children_count(self)
874 875
875 876 def get_new_name(self, group_name):
876 877 """
877 878 returns new full group name based on parent and new name
878 879
879 880 :param group_name:
880 881 """
881 882 path_prefix = (self.parent_group.full_path_splitted if
882 883 self.parent_group else [])
883 884 return RepoGroup.url_sep().join(path_prefix + [group_name])
884 885
885 886
886 887 class Permission(Base, BaseModel):
887 888 __tablename__ = 'permissions'
888 889 __table_args__ = (
889 890 {'extend_existing': True, 'mysql_engine': 'InnoDB',
890 891 'mysql_charset': 'utf8'},
891 892 )
892 893 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
893 894 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
894 895 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
895 896
896 897 def __unicode__(self):
897 898 return u"<%s('%s:%s')>" % (
898 899 self.__class__.__name__, self.permission_id, self.permission_name
899 900 )
900 901
901 902 @classmethod
902 903 def get_by_key(cls, key):
903 904 return cls.query().filter(cls.permission_name == key).scalar()
904 905
905 906 @classmethod
906 907 def get_default_perms(cls, default_user_id):
907 908 q = Session.query(UserRepoToPerm, Repository, cls)\
908 909 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
909 910 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
910 911 .filter(UserRepoToPerm.user_id == default_user_id)
911 912
912 913 return q.all()
913 914
914 915 @classmethod
915 916 def get_default_group_perms(cls, default_user_id):
916 917 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
917 918 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
918 919 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
919 920 .filter(UserRepoGroupToPerm.user_id == default_user_id)
920 921
921 922 return q.all()
922 923
923 924
924 925 class UserRepoToPerm(Base, BaseModel):
925 926 __tablename__ = 'repo_to_perm'
926 927 __table_args__ = (
927 928 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
928 929 {'extend_existing': True, 'mysql_engine': 'InnoDB',
929 930 'mysql_charset': 'utf8'}
930 931 )
931 932 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
932 933 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
933 934 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
934 935 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
935 936
936 937 user = relationship('User')
937 938 repository = relationship('Repository')
938 939 permission = relationship('Permission')
939 940
940 941 @classmethod
941 942 def create(cls, user, repository, permission):
942 943 n = cls()
943 944 n.user = user
944 945 n.repository = repository
945 946 n.permission = permission
946 947 Session.add(n)
947 948 return n
948 949
949 950 def __unicode__(self):
950 951 return u'<user:%s => %s >' % (self.user, self.repository)
951 952
952 953
953 954 class UserToPerm(Base, BaseModel):
954 955 __tablename__ = 'user_to_perm'
955 956 __table_args__ = (
956 957 UniqueConstraint('user_id', 'permission_id'),
957 958 {'extend_existing': True, 'mysql_engine': 'InnoDB',
958 959 'mysql_charset': 'utf8'}
959 960 )
960 961 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
961 962 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
962 963 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
963 964
964 965 user = relationship('User')
965 966 permission = relationship('Permission', lazy='joined')
966 967
967 968
968 969 class UsersGroupRepoToPerm(Base, BaseModel):
969 970 __tablename__ = 'users_group_repo_to_perm'
970 971 __table_args__ = (
971 972 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
972 973 {'extend_existing': True, 'mysql_engine': 'InnoDB',
973 974 'mysql_charset': 'utf8'}
974 975 )
975 976 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
976 977 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
977 978 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
978 979 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
979 980
980 981 users_group = relationship('UsersGroup')
981 982 permission = relationship('Permission')
982 983 repository = relationship('Repository')
983 984
984 985 @classmethod
985 986 def create(cls, users_group, repository, permission):
986 987 n = cls()
987 988 n.users_group = users_group
988 989 n.repository = repository
989 990 n.permission = permission
990 991 Session.add(n)
991 992 return n
992 993
993 994 def __unicode__(self):
994 995 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
995 996
996 997
997 998 class UsersGroupToPerm(Base, BaseModel):
998 999 __tablename__ = 'users_group_to_perm'
999 1000 __table_args__ = (
1000 1001 UniqueConstraint('users_group_id', 'permission_id',),
1001 1002 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1002 1003 'mysql_charset': 'utf8'}
1003 1004 )
1004 1005 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1005 1006 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1006 1007 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1007 1008
1008 1009 users_group = relationship('UsersGroup')
1009 1010 permission = relationship('Permission')
1010 1011
1011 1012
1012 1013 class UserRepoGroupToPerm(Base, BaseModel):
1013 1014 __tablename__ = 'user_repo_group_to_perm'
1014 1015 __table_args__ = (
1015 1016 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1016 1017 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1017 1018 'mysql_charset': 'utf8'}
1018 1019 )
1019 1020
1020 1021 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1021 1022 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1022 1023 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1023 1024 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1024 1025
1025 1026 user = relationship('User')
1026 1027 group = relationship('RepoGroup')
1027 1028 permission = relationship('Permission')
1028 1029
1029 1030
1030 1031 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1031 1032 __tablename__ = 'users_group_repo_group_to_perm'
1032 1033 __table_args__ = (
1033 1034 UniqueConstraint('users_group_id', 'group_id'),
1034 1035 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1035 1036 'mysql_charset': 'utf8'}
1036 1037 )
1037 1038
1038 1039 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1039 1040 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1040 1041 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1041 1042 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1042 1043
1043 1044 users_group = relationship('UsersGroup')
1044 1045 permission = relationship('Permission')
1045 1046 group = relationship('RepoGroup')
1046 1047
1047 1048
1048 1049 class Statistics(Base, BaseModel):
1049 1050 __tablename__ = 'statistics'
1050 1051 __table_args__ = (
1051 1052 UniqueConstraint('repository_id'),
1052 1053 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1053 1054 'mysql_charset': 'utf8'}
1054 1055 )
1055 1056 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1056 1057 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1057 1058 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1058 1059 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1059 1060 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1060 1061 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1061 1062
1062 1063 repository = relationship('Repository', single_parent=True)
1063 1064
1064 1065
1065 1066 class UserFollowing(Base, BaseModel):
1066 1067 __tablename__ = 'user_followings'
1067 1068 __table_args__ = (
1068 1069 UniqueConstraint('user_id', 'follows_repository_id'),
1069 1070 UniqueConstraint('user_id', 'follows_user_id'),
1070 1071 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1071 1072 'mysql_charset': 'utf8'}
1072 1073 )
1073 1074
1074 1075 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1075 1076 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1076 1077 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1077 1078 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1078 1079 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1079 1080
1080 1081 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1081 1082
1082 1083 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1083 1084 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1084 1085
1085 1086 @classmethod
1086 1087 def get_repo_followers(cls, repo_id):
1087 1088 return cls.query().filter(cls.follows_repo_id == repo_id)
1088 1089
1089 1090
1090 1091 class CacheInvalidation(Base, BaseModel):
1091 1092 __tablename__ = 'cache_invalidation'
1092 1093 __table_args__ = (
1093 1094 UniqueConstraint('cache_key'),
1094 1095 Index('key_idx', 'cache_key'),
1095 1096 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1096 1097 'mysql_charset': 'utf8'},
1097 1098 )
1098 1099 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1099 1100 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1100 1101 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1101 1102 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1102 1103
1103 1104 def __init__(self, cache_key, cache_args=''):
1104 1105 self.cache_key = cache_key
1105 1106 self.cache_args = cache_args
1106 1107 self.cache_active = False
1107 1108
1108 1109 def __unicode__(self):
1109 1110 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1110 1111 self.cache_id, self.cache_key)
1111 1112
1112 1113 @classmethod
1113 1114 def clear_cache(cls):
1114 1115 cls.query().delete()
1115 1116
1116 1117 @classmethod
1117 1118 def _get_key(cls, key):
1118 1119 """
1119 1120 Wrapper for generating a key, together with a prefix
1120 1121
1121 1122 :param key:
1122 1123 """
1123 1124 import rhodecode
1124 1125 prefix = ''
1125 1126 iid = rhodecode.CONFIG.get('instance_id')
1126 1127 if iid:
1127 1128 prefix = iid
1128 1129 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1129 1130
1130 1131 @classmethod
1131 1132 def get_by_key(cls, key):
1132 1133 return cls.query().filter(cls.cache_key == key).scalar()
1133 1134
1134 1135 @classmethod
1135 1136 def _get_or_create_key(cls, key, prefix, org_key):
1136 1137 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1137 1138 if not inv_obj:
1138 1139 try:
1139 1140 inv_obj = CacheInvalidation(key, org_key)
1140 1141 Session.add(inv_obj)
1141 1142 Session.commit()
1142 1143 except Exception:
1143 1144 log.error(traceback.format_exc())
1144 1145 Session.rollback()
1145 1146 return inv_obj
1146 1147
1147 1148 @classmethod
1148 1149 def invalidate(cls, key):
1149 1150 """
1150 1151 Returns Invalidation object if this given key should be invalidated
1151 1152 None otherwise. `cache_active = False` means that this cache
1152 1153 state is not valid and needs to be invalidated
1153 1154
1154 1155 :param key:
1155 1156 """
1156 1157
1157 1158 key, _prefix, _org_key = cls._get_key(key)
1158 1159 inv = cls._get_or_create_key(key, _prefix, _org_key)
1159 1160
1160 1161 if inv and inv.cache_active is False:
1161 1162 return inv
1162 1163
1163 1164 @classmethod
1164 1165 def set_invalidate(cls, key):
1165 1166 """
1166 1167 Mark this Cache key for invalidation
1167 1168
1168 1169 :param key:
1169 1170 """
1170 1171
1171 1172 key, _prefix, _org_key = cls._get_key(key)
1172 1173 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1173 1174 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1174 1175 _org_key))
1175 1176 try:
1176 1177 for inv_obj in inv_objs:
1177 1178 if inv_obj:
1178 1179 inv_obj.cache_active = False
1179 1180
1180 1181 Session.add(inv_obj)
1181 1182 Session.commit()
1182 1183 except Exception:
1183 1184 log.error(traceback.format_exc())
1184 1185 Session.rollback()
1185 1186
1186 1187 @classmethod
1187 1188 def set_valid(cls, key):
1188 1189 """
1189 1190 Mark this cache key as active and currently cached
1190 1191
1191 1192 :param key:
1192 1193 """
1193 1194 inv_obj = cls.get_by_key(key)
1194 1195 inv_obj.cache_active = True
1195 1196 Session.add(inv_obj)
1196 1197 Session.commit()
1197 1198
1198 1199 @classmethod
1199 1200 def get_cache_map(cls):
1200 1201
1201 1202 class cachemapdict(dict):
1202 1203
1203 1204 def __init__(self, *args, **kwargs):
1204 1205 fixkey = kwargs.get('fixkey')
1205 1206 if fixkey:
1206 1207 del kwargs['fixkey']
1207 1208 self.fixkey = fixkey
1208 1209 super(cachemapdict, self).__init__(*args, **kwargs)
1209 1210
1210 1211 def __getattr__(self, name):
1211 1212 key = name
1212 1213 if self.fixkey:
1213 1214 key, _prefix, _org_key = cls._get_key(key)
1214 1215 if key in self.__dict__:
1215 1216 return self.__dict__[key]
1216 1217 else:
1217 1218 return self[key]
1218 1219
1219 1220 def __getitem__(self, key):
1220 1221 if self.fixkey:
1221 1222 key, _prefix, _org_key = cls._get_key(key)
1222 1223 try:
1223 1224 return super(cachemapdict, self).__getitem__(key)
1224 1225 except KeyError:
1225 1226 return
1226 1227
1227 1228 cache_map = cachemapdict(fixkey=True)
1228 1229 for obj in cls.query().all():
1229 1230 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1230 1231 return cache_map
1231 1232
1232 1233
1233 1234 class ChangesetComment(Base, BaseModel):
1234 1235 __tablename__ = 'changeset_comments'
1235 1236 __table_args__ = (
1236 1237 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1237 1238 'mysql_charset': 'utf8'},
1238 1239 )
1239 1240 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1240 1241 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1241 1242 revision = Column('revision', String(40), nullable=False)
1242 1243 line_no = Column('line_no', Unicode(10), nullable=True)
1243 1244 f_path = Column('f_path', Unicode(1000), nullable=True)
1244 1245 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1245 1246 text = Column('text', Unicode(25000), nullable=False)
1246 1247 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1247 1248
1248 1249 author = relationship('User', lazy='joined')
1249 1250 repo = relationship('Repository')
1250 1251
1251 1252 @classmethod
1252 1253 def get_users(cls, revision):
1253 1254 """
1254 1255 Returns user associated with this changesetComment. ie those
1255 1256 who actually commented
1256 1257
1257 1258 :param cls:
1258 1259 :param revision:
1259 1260 """
1260 1261 return Session.query(User)\
1261 1262 .filter(cls.revision == revision)\
1262 1263 .join(ChangesetComment.author).all()
1263 1264
1264 1265
1265 1266 class Notification(Base, BaseModel):
1266 1267 __tablename__ = 'notifications'
1267 1268 __table_args__ = (
1268 1269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1269 1270 'mysql_charset': 'utf8'},
1270 1271 )
1271 1272
1272 1273 TYPE_CHANGESET_COMMENT = u'cs_comment'
1273 1274 TYPE_MESSAGE = u'message'
1274 1275 TYPE_MENTION = u'mention'
1275 1276 TYPE_REGISTRATION = u'registration'
1276 1277
1277 1278 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1278 1279 subject = Column('subject', Unicode(512), nullable=True)
1279 1280 body = Column('body', Unicode(50000), nullable=True)
1280 1281 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1281 1282 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1282 1283 type_ = Column('type', Unicode(256))
1283 1284
1284 1285 created_by_user = relationship('User')
1285 1286 notifications_to_users = relationship('UserNotification', lazy='joined',
1286 1287 cascade="all, delete, delete-orphan")
1287 1288
1288 1289 @property
1289 1290 def recipients(self):
1290 1291 return [x.user for x in UserNotification.query()\
1291 1292 .filter(UserNotification.notification == self)\
1292 1293 .order_by(UserNotification.user).all()]
1293 1294
1294 1295 @classmethod
1295 1296 def create(cls, created_by, subject, body, recipients, type_=None):
1296 1297 if type_ is None:
1297 1298 type_ = Notification.TYPE_MESSAGE
1298 1299
1299 1300 notification = cls()
1300 1301 notification.created_by_user = created_by
1301 1302 notification.subject = subject
1302 1303 notification.body = body
1303 1304 notification.type_ = type_
1304 1305 notification.created_on = datetime.datetime.now()
1305 1306
1306 1307 for u in recipients:
1307 1308 assoc = UserNotification()
1308 1309 assoc.notification = notification
1309 1310 u.notifications.append(assoc)
1310 1311 Session.add(notification)
1311 1312 return notification
1312 1313
1313 1314 @property
1314 1315 def description(self):
1315 1316 from rhodecode.model.notification import NotificationModel
1316 1317 return NotificationModel().make_description(self)
1317 1318
1318 1319
1319 1320 class UserNotification(Base, BaseModel):
1320 1321 __tablename__ = 'user_to_notification'
1321 1322 __table_args__ = (
1322 1323 UniqueConstraint('user_id', 'notification_id'),
1323 1324 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1324 1325 'mysql_charset': 'utf8'}
1325 1326 )
1326 1327 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1327 1328 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1328 1329 read = Column('read', Boolean, default=False)
1329 1330 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1330 1331
1331 1332 user = relationship('User', lazy="joined")
1332 1333 notification = relationship('Notification', lazy="joined",
1333 1334 order_by=lambda: Notification.created_on.desc(),)
1334 1335
1335 1336 def mark_as_read(self):
1336 1337 self.read = True
1337 1338 Session.add(self)
1338 1339
1339 1340
1340 1341 class DbMigrateVersion(Base, BaseModel):
1341 1342 __tablename__ = 'db_migrate_version'
1342 1343 __table_args__ = (
1343 1344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1344 1345 'mysql_charset': 'utf8'},
1345 1346 )
1346 1347 repository_id = Column('repository_id', String(250), primary_key=True)
1347 1348 repository_path = Column('repository_path', Text)
1348 1349 version = Column('version', Integer)
@@ -1,773 +1,774
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 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.
13 13 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
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import os
23 23 import re
24 24 import logging
25 25 import traceback
26 26
27 27 import formencode
28 28 from formencode import All
29 29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 30 Email, Bool, StringBoolean, Set
31 31
32 32 from pylons.i18n.translation import _
33 33 from webhelpers.pylonslib.secure_form import authentication_token
34 34
35 35 from rhodecode.config.routing import ADMIN_PREFIX
36 36 from rhodecode.lib.utils import repo_name_slug
37 37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 38 from rhodecode.lib.exceptions import LdapImportError
39 39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
40 40 from rhodecode import BACKENDS
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 #this is needed to translate the messages using _() in validators
46 46 class State_obj(object):
47 47 _ = staticmethod(_)
48 48
49 49
50 50 #==============================================================================
51 51 # VALIDATORS
52 52 #==============================================================================
53 53 class ValidAuthToken(formencode.validators.FancyValidator):
54 54 messages = {'invalid_token': _('Token mismatch')}
55 55
56 56 def validate_python(self, value, state):
57 57
58 58 if value != authentication_token():
59 59 raise formencode.Invalid(
60 60 self.message('invalid_token',
61 61 state, search_number=value),
62 62 value,
63 63 state
64 64 )
65 65
66 66
67 67 def ValidUsername(edit, old_data):
68 68 class _ValidUsername(formencode.validators.FancyValidator):
69 69
70 70 def validate_python(self, value, state):
71 71 if value in ['default', 'new_user']:
72 72 raise formencode.Invalid(_('Invalid username'), value, state)
73 73 #check if user is unique
74 74 old_un = None
75 75 if edit:
76 76 old_un = User.get(old_data.get('user_id')).username
77 77
78 78 if old_un != value or not edit:
79 79 if User.get_by_username(value, case_insensitive=True):
80 80 raise formencode.Invalid(_('This username already '
81 81 'exists') , value, state)
82 82
83 83 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
84 84 raise formencode.Invalid(
85 85 _('Username may only contain alphanumeric characters '
86 86 'underscores, periods or dashes and must begin with '
87 87 'alphanumeric character'),
88 88 value,
89 89 state
90 90 )
91 91
92 92 return _ValidUsername
93 93
94 94
95 95 def ValidUsersGroup(edit, old_data):
96 96
97 97 class _ValidUsersGroup(formencode.validators.FancyValidator):
98 98
99 99 def validate_python(self, value, state):
100 100 if value in ['default']:
101 101 raise formencode.Invalid(_('Invalid group name'), value, state)
102 102 #check if group is unique
103 103 old_ugname = None
104 104 if edit:
105 105 old_ugname = UsersGroup.get(
106 106 old_data.get('users_group_id')).users_group_name
107 107
108 108 if old_ugname != value or not edit:
109 109 if UsersGroup.get_by_group_name(value, cache=False,
110 110 case_insensitive=True):
111 111 raise formencode.Invalid(_('This users group '
112 112 'already exists'), value,
113 113 state)
114 114
115 115 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
116 116 raise formencode.Invalid(
117 117 _('RepoGroup name may only contain alphanumeric characters '
118 118 'underscores, periods or dashes and must begin with '
119 119 'alphanumeric character'),
120 120 value,
121 121 state
122 122 )
123 123
124 124 return _ValidUsersGroup
125 125
126 126
127 127 def ValidReposGroup(edit, old_data):
128 128 class _ValidReposGroup(formencode.validators.FancyValidator):
129 129
130 130 def validate_python(self, value, state):
131 131 # TODO WRITE VALIDATIONS
132 132 group_name = value.get('group_name')
133 133 group_parent_id = value.get('group_parent_id')
134 134
135 135 # slugify repo group just in case :)
136 136 slug = repo_name_slug(group_name)
137 137
138 138 # check for parent of self
139 139 parent_of_self = lambda: (
140 140 old_data['group_id'] == int(group_parent_id)
141 141 if group_parent_id else False
142 142 )
143 143 if edit and parent_of_self():
144 144 e_dict = {
145 145 'group_parent_id': _('Cannot assign this group as parent')
146 146 }
147 147 raise formencode.Invalid('', value, state,
148 148 error_dict=e_dict)
149 149
150 150 old_gname = None
151 151 if edit:
152 152 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
153 153
154 154 if old_gname != group_name or not edit:
155 155
156 156 # check group
157 157 gr = RepoGroup.query()\
158 158 .filter(RepoGroup.group_name == slug)\
159 159 .filter(RepoGroup.group_parent_id == group_parent_id)\
160 160 .scalar()
161 161
162 162 if gr:
163 163 e_dict = {
164 164 'group_name': _('This group already exists')
165 165 }
166 166 raise formencode.Invalid('', value, state,
167 167 error_dict=e_dict)
168 168
169 169 # check for same repo
170 170 repo = Repository.query()\
171 171 .filter(Repository.repo_name == slug)\
172 172 .scalar()
173 173
174 174 if repo:
175 175 e_dict = {
176 176 'group_name': _('Repository with this name already exists')
177 177 }
178 178 raise formencode.Invalid('', value, state,
179 179 error_dict=e_dict)
180 180
181 181 return _ValidReposGroup
182 182
183 183
184 184 class ValidPassword(formencode.validators.FancyValidator):
185 185
186 186 def to_python(self, value, state):
187 187
188 188 if not value:
189 189 return
190 190
191 191 if value.get('password'):
192 192 try:
193 193 value['password'] = get_crypt_password(value['password'])
194 194 except UnicodeEncodeError:
195 195 e_dict = {'password': _('Invalid characters in password')}
196 196 raise formencode.Invalid('', value, state, error_dict=e_dict)
197 197
198 198 if value.get('password_confirmation'):
199 199 try:
200 200 value['password_confirmation'] = \
201 201 get_crypt_password(value['password_confirmation'])
202 202 except UnicodeEncodeError:
203 203 e_dict = {
204 204 'password_confirmation': _('Invalid characters in password')
205 205 }
206 206 raise formencode.Invalid('', value, state, error_dict=e_dict)
207 207
208 208 if value.get('new_password'):
209 209 try:
210 210 value['new_password'] = \
211 211 get_crypt_password(value['new_password'])
212 212 except UnicodeEncodeError:
213 213 e_dict = {'new_password': _('Invalid characters in password')}
214 214 raise formencode.Invalid('', value, state, error_dict=e_dict)
215 215
216 216 return value
217 217
218 218
219 219 class ValidPasswordsMatch(formencode.validators.FancyValidator):
220 220
221 221 def validate_python(self, value, state):
222 222
223 223 pass_val = value.get('password') or value.get('new_password')
224 224 if pass_val != value['password_confirmation']:
225 225 e_dict = {'password_confirmation':
226 226 _('Passwords do not match')}
227 227 raise formencode.Invalid('', value, state, error_dict=e_dict)
228 228
229 229
230 230 class ValidAuth(formencode.validators.FancyValidator):
231 231 messages = {
232 232 'invalid_password':_('invalid password'),
233 233 'invalid_login':_('invalid user name'),
234 234 'disabled_account':_('Your account is disabled')
235 235 }
236 236
237 237 # error mapping
238 238 e_dict = {'username': messages['invalid_login'],
239 239 'password': messages['invalid_password']}
240 240 e_dict_disable = {'username': messages['disabled_account']}
241 241
242 242 def validate_python(self, value, state):
243 243 password = value['password']
244 244 username = value['username']
245 245 user = User.get_by_username(username)
246 246
247 247 if authenticate(username, password):
248 248 return value
249 249 else:
250 250 if user and user.active is False:
251 251 log.warning('user %s is disabled' % username)
252 252 raise formencode.Invalid(
253 253 self.message('disabled_account',
254 254 state=State_obj),
255 255 value, state,
256 256 error_dict=self.e_dict_disable
257 257 )
258 258 else:
259 259 log.warning('user %s failed to authenticate' % username)
260 260 raise formencode.Invalid(
261 261 self.message('invalid_password',
262 262 state=State_obj), value, state,
263 263 error_dict=self.e_dict
264 264 )
265 265
266 266
267 267 class ValidRepoUser(formencode.validators.FancyValidator):
268 268
269 269 def to_python(self, value, state):
270 270 try:
271 271 User.query().filter(User.active == True)\
272 272 .filter(User.username == value).one()
273 273 except Exception:
274 274 raise formencode.Invalid(_('This username is not valid'),
275 275 value, state)
276 276 return value
277 277
278 278
279 279 def ValidRepoName(edit, old_data):
280 280 class _ValidRepoName(formencode.validators.FancyValidator):
281 281 def to_python(self, value, state):
282 282
283 283 repo_name = value.get('repo_name')
284 284
285 285 slug = repo_name_slug(repo_name)
286 286 if slug in [ADMIN_PREFIX, '']:
287 287 e_dict = {'repo_name': _('This repository name is disallowed')}
288 288 raise formencode.Invalid('', value, state, error_dict=e_dict)
289 289
290 290 if value.get('repo_group'):
291 291 gr = RepoGroup.get(value.get('repo_group'))
292 292 group_path = gr.full_path
293 293 # value needs to be aware of group name in order to check
294 294 # db key This is an actual just the name to store in the
295 295 # database
296 296 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
297 297
298 298 else:
299 299 group_path = ''
300 300 repo_name_full = repo_name
301 301
302 302 value['repo_name_full'] = repo_name_full
303 303 rename = old_data.get('repo_name') != repo_name_full
304 304 create = not edit
305 305 if rename or create:
306 306
307 307 if group_path != '':
308 308 if Repository.get_by_repo_name(repo_name_full):
309 309 e_dict = {
310 310 'repo_name': _('This repository already exists in '
311 311 'a group "%s"') % gr.group_name
312 312 }
313 313 raise formencode.Invalid('', value, state,
314 314 error_dict=e_dict)
315 315 elif RepoGroup.get_by_group_name(repo_name_full):
316 316 e_dict = {
317 317 'repo_name': _('There is a group with this name '
318 318 'already "%s"') % repo_name_full
319 319 }
320 320 raise formencode.Invalid('', value, state,
321 321 error_dict=e_dict)
322 322
323 323 elif Repository.get_by_repo_name(repo_name_full):
324 324 e_dict = {'repo_name': _('This repository '
325 325 'already exists')}
326 326 raise formencode.Invalid('', value, state,
327 327 error_dict=e_dict)
328 328
329 329 return value
330 330
331 331 return _ValidRepoName
332 332
333 333
334 334 def ValidForkName(*args, **kwargs):
335 335 return ValidRepoName(*args, **kwargs)
336 336
337 337
338 338 def SlugifyName():
339 339 class _SlugifyName(formencode.validators.FancyValidator):
340 340
341 341 def to_python(self, value, state):
342 342 return repo_name_slug(value)
343 343
344 344 return _SlugifyName
345 345
346 346
347 347 def ValidCloneUri():
348 348 from rhodecode.lib.utils import make_ui
349 349
350 350 def url_handler(repo_type, url, proto, ui=None):
351 351 if repo_type == 'hg':
352 352 from mercurial.httprepo import httprepository, httpsrepository
353 353 if proto == 'https':
354 354 httpsrepository(make_ui('db'), url).capabilities
355 355 elif proto == 'http':
356 356 httprepository(make_ui('db'), url).capabilities
357 357 elif repo_type == 'git':
358 358 #TODO: write a git url validator
359 359 pass
360 360
361 361 class _ValidCloneUri(formencode.validators.FancyValidator):
362 362
363 363 def to_python(self, value, state):
364 364
365 365 repo_type = value.get('repo_type')
366 366 url = value.get('clone_uri')
367 367 e_dict = {'clone_uri': _('invalid clone url')}
368 368
369 369 if not url:
370 370 pass
371 371 elif url.startswith('https'):
372 372 try:
373 373 url_handler(repo_type, url, 'https', make_ui('db'))
374 374 except Exception:
375 375 log.error(traceback.format_exc())
376 376 raise formencode.Invalid('', value, state, error_dict=e_dict)
377 377 elif url.startswith('http'):
378 378 try:
379 379 url_handler(repo_type, url, 'http', make_ui('db'))
380 380 except Exception:
381 381 log.error(traceback.format_exc())
382 382 raise formencode.Invalid('', value, state, error_dict=e_dict)
383 383 else:
384 384 e_dict = {'clone_uri': _('Invalid clone url, provide a '
385 385 'valid clone http\s url')}
386 386 raise formencode.Invalid('', value, state, error_dict=e_dict)
387 387
388 388 return value
389 389
390 390 return _ValidCloneUri
391 391
392 392
393 393 def ValidForkType(old_data):
394 394 class _ValidForkType(formencode.validators.FancyValidator):
395 395
396 396 def to_python(self, value, state):
397 397 if old_data['repo_type'] != value:
398 398 raise formencode.Invalid(_('Fork have to be the same '
399 399 'type as original'), value, state)
400 400
401 401 return value
402 402 return _ValidForkType
403 403
404 404
405 405 def ValidPerms(type_='repo'):
406 406 if type_ == 'group':
407 407 EMPTY_PERM = 'group.none'
408 408 elif type_ == 'repo':
409 409 EMPTY_PERM = 'repository.none'
410 410
411 411 class _ValidPerms(formencode.validators.FancyValidator):
412 412 messages = {
413 413 'perm_new_member_name':
414 414 _('This username or users group name is not valid')
415 415 }
416 416
417 417 def to_python(self, value, state):
418 418 perms_update = []
419 419 perms_new = []
420 420 # build a list of permission to update and new permission to create
421 421 for k, v in value.items():
422 422 # means new added member to permissions
423 423 if k.startswith('perm_new_member'):
424 424 new_perm = value.get('perm_new_member', False)
425 425 new_member = value.get('perm_new_member_name', False)
426 426 new_type = value.get('perm_new_member_type')
427 427
428 428 if new_member and new_perm:
429 429 if (new_member, new_perm, new_type) not in perms_new:
430 430 perms_new.append((new_member, new_perm, new_type))
431 431 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
432 432 member = k[7:]
433 433 t = {'u': 'user',
434 434 'g': 'users_group'
435 435 }[k[0]]
436 436 if member == 'default':
437 437 if value.get('private'):
438 438 # set none for default when updating to private repo
439 439 v = EMPTY_PERM
440 440 perms_update.append((member, v, t))
441 441
442 442 value['perms_updates'] = perms_update
443 443 value['perms_new'] = perms_new
444 444
445 445 # update permissions
446 446 for k, v, t in perms_new:
447 447 try:
448 448 if t is 'user':
449 449 self.user_db = User.query()\
450 450 .filter(User.active == True)\
451 451 .filter(User.username == k).one()
452 452 if t is 'users_group':
453 453 self.user_db = UsersGroup.query()\
454 454 .filter(UsersGroup.users_group_active == True)\
455 455 .filter(UsersGroup.users_group_name == k).one()
456 456
457 457 except Exception:
458 458 msg = self.message('perm_new_member_name',
459 459 state=State_obj)
460 460 raise formencode.Invalid(
461 461 msg, value, state, error_dict={'perm_new_member_name': msg}
462 462 )
463 463 return value
464 464 return _ValidPerms
465 465
466 466
467 467 class ValidSettings(formencode.validators.FancyValidator):
468 468
469 469 def to_python(self, value, state):
470 470 # settings form can't edit user
471 471 if 'user' in value:
472 472 del['value']['user']
473 473 return value
474 474
475 475
476 476 class ValidPath(formencode.validators.FancyValidator):
477 477 def to_python(self, value, state):
478 478
479 479 if not os.path.isdir(value):
480 480 msg = _('This is not a valid path')
481 481 raise formencode.Invalid(msg, value, state,
482 482 error_dict={'paths_root_path': msg})
483 483 return value
484 484
485 485
486 486 def UniqSystemEmail(old_data):
487 487 class _UniqSystemEmail(formencode.validators.FancyValidator):
488 488 def to_python(self, value, state):
489 489 value = value.lower()
490 490 if (old_data.get('email') or '').lower() != value:
491 491 user = User.get_by_email(value, case_insensitive=True)
492 492 if user:
493 493 raise formencode.Invalid(
494 494 _("This e-mail address is already taken"), value, state
495 495 )
496 496 return value
497 497
498 498 return _UniqSystemEmail
499 499
500 500
501 501 class ValidSystemEmail(formencode.validators.FancyValidator):
502 502 def to_python(self, value, state):
503 503 value = value.lower()
504 504 user = User.get_by_email(value, case_insensitive=True)
505 505 if user is None:
506 506 raise formencode.Invalid(
507 507 _("This e-mail address doesn't exist."), value, state
508 508 )
509 509
510 510 return value
511 511
512 512
513 513 class LdapLibValidator(formencode.validators.FancyValidator):
514 514
515 515 def to_python(self, value, state):
516 516
517 517 try:
518 518 import ldap
519 519 except ImportError:
520 520 raise LdapImportError
521 521 return value
522 522
523 523
524 524 class AttrLoginValidator(formencode.validators.FancyValidator):
525 525
526 526 def to_python(self, value, state):
527 527
528 528 if not value or not isinstance(value, (str, unicode)):
529 529 raise formencode.Invalid(
530 530 _("The LDAP Login attribute of the CN must be specified - "
531 531 "this is the name of the attribute that is equivalent "
532 532 "to 'username'"), value, state
533 533 )
534 534
535 535 return value
536 536
537 537
538 538 #==============================================================================
539 539 # FORMS
540 540 #==============================================================================
541 541 class LoginForm(formencode.Schema):
542 542 allow_extra_fields = True
543 543 filter_extra_fields = True
544 544 username = UnicodeString(
545 545 strip=True,
546 546 min=1,
547 547 not_empty=True,
548 548 messages={
549 549 'empty': _('Please enter a login'),
550 550 'tooShort': _('Enter a value %(min)i characters long or more')}
551 551 )
552 552
553 553 password = UnicodeString(
554 554 strip=False,
555 555 min=3,
556 556 not_empty=True,
557 557 messages={
558 558 'empty': _('Please enter a password'),
559 559 'tooShort': _('Enter %(min)i characters or more')}
560 560 )
561 561
562 562 remember = StringBoolean(if_missing=False)
563 563
564 564 chained_validators = [ValidAuth]
565 565
566 566
567 567 def UserForm(edit=False, old_data={}):
568 568 class _UserForm(formencode.Schema):
569 569 allow_extra_fields = True
570 570 filter_extra_fields = True
571 571 username = All(UnicodeString(strip=True, min=1, not_empty=True),
572 572 ValidUsername(edit, old_data))
573 573 if edit:
574 574 new_password = All(UnicodeString(strip=False, min=6, not_empty=False))
575 575 password_confirmation = All(UnicodeString(strip=False, min=6,
576 576 not_empty=False))
577 577 admin = StringBoolean(if_missing=False)
578 578 else:
579 579 password = All(UnicodeString(strip=False, min=6, not_empty=True))
580 580 password_confirmation = All(UnicodeString(strip=False, min=6,
581 581 not_empty=False))
582 582
583 583 active = StringBoolean(if_missing=False)
584 584 name = UnicodeString(strip=True, min=1, not_empty=False)
585 585 lastname = UnicodeString(strip=True, min=1, not_empty=False)
586 586 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
587 587
588 588 chained_validators = [ValidPasswordsMatch, ValidPassword]
589 589
590 590 return _UserForm
591 591
592 592
593 593 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
594 594 class _UsersGroupForm(formencode.Schema):
595 595 allow_extra_fields = True
596 596 filter_extra_fields = True
597 597
598 598 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
599 599 ValidUsersGroup(edit, old_data))
600 600
601 601 users_group_active = StringBoolean(if_missing=False)
602 602
603 603 if edit:
604 604 users_group_members = OneOf(available_members, hideList=False,
605 605 testValueList=True,
606 606 if_missing=None, not_empty=False)
607 607
608 608 return _UsersGroupForm
609 609
610 610
611 611 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
612 612 class _ReposGroupForm(formencode.Schema):
613 613 allow_extra_fields = True
614 614 filter_extra_fields = False
615 615
616 616 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
617 617 SlugifyName())
618 618 group_description = UnicodeString(strip=True, min=1,
619 619 not_empty=True)
620 620 group_parent_id = OneOf(available_groups, hideList=False,
621 621 testValueList=True,
622 622 if_missing=None, not_empty=False)
623 623
624 624 chained_validators = [ValidReposGroup(edit, old_data), ValidPerms('group')]
625 625
626 626 return _ReposGroupForm
627 627
628 628
629 629 def RegisterForm(edit=False, old_data={}):
630 630 class _RegisterForm(formencode.Schema):
631 631 allow_extra_fields = True
632 632 filter_extra_fields = True
633 633 username = All(ValidUsername(edit, old_data),
634 634 UnicodeString(strip=True, min=1, not_empty=True))
635 635 password = All(UnicodeString(strip=False, min=6, not_empty=True))
636 636 password_confirmation = All(UnicodeString(strip=False, min=6, not_empty=True))
637 637 active = StringBoolean(if_missing=False)
638 638 name = UnicodeString(strip=True, min=1, not_empty=False)
639 639 lastname = UnicodeString(strip=True, min=1, not_empty=False)
640 640 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
641 641
642 642 chained_validators = [ValidPasswordsMatch, ValidPassword]
643 643
644 644 return _RegisterForm
645 645
646 646
647 647 def PasswordResetForm():
648 648 class _PasswordResetForm(formencode.Schema):
649 649 allow_extra_fields = True
650 650 filter_extra_fields = True
651 651 email = All(ValidSystemEmail(), Email(not_empty=True))
652 652 return _PasswordResetForm
653 653
654 654
655 655 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
656 656 repo_groups=[]):
657 657 class _RepoForm(formencode.Schema):
658 658 allow_extra_fields = True
659 659 filter_extra_fields = False
660 660 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
661 661 SlugifyName())
662 662 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False))
663 663 repo_group = OneOf(repo_groups, hideList=True)
664 664 repo_type = OneOf(supported_backends)
665 description = UnicodeString(strip=True, min=1, not_empty=True)
665 description = UnicodeString(strip=True, min=1, not_empty=False)
666 666 private = StringBoolean(if_missing=False)
667 667 enable_statistics = StringBoolean(if_missing=False)
668 668 enable_downloads = StringBoolean(if_missing=False)
669 landing_rev = UnicodeString(strip=True, min=1, not_empty=True)
669 670
670 671 if edit:
671 672 #this is repo owner
672 673 user = All(UnicodeString(not_empty=True), ValidRepoUser)
673 674
674 675 chained_validators = [ValidCloneUri()(),
675 676 ValidRepoName(edit, old_data),
676 677 ValidPerms()]
677 678 return _RepoForm
678 679
679 680
680 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
681 repo_groups=[]):
681 def RepoForkForm(edit=False, old_data={},
682 supported_backends=BACKENDS.keys(), repo_groups=[]):
682 683 class _RepoForkForm(formencode.Schema):
683 684 allow_extra_fields = True
684 685 filter_extra_fields = False
685 686 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
686 687 SlugifyName())
687 688 repo_group = OneOf(repo_groups, hideList=True)
688 689 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
689 690 description = UnicodeString(strip=True, min=1, not_empty=True)
690 691 private = StringBoolean(if_missing=False)
691 692 copy_permissions = StringBoolean(if_missing=False)
692 693 update_after_clone = StringBoolean(if_missing=False)
693 694 fork_parent_id = UnicodeString()
694 695 chained_validators = [ValidForkName(edit, old_data)]
695 696
696 697 return _RepoForkForm
697 698
698 699
699 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
700 repo_groups=[]):
700 def RepoSettingsForm(edit=False, old_data={},
701 supported_backends=BACKENDS.keys(), repo_groups=[]):
701 702 class _RepoForm(formencode.Schema):
702 703 allow_extra_fields = True
703 704 filter_extra_fields = False
704 705 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
705 706 SlugifyName())
706 707 description = UnicodeString(strip=True, min=1, not_empty=True)
707 708 repo_group = OneOf(repo_groups, hideList=True)
708 709 private = StringBoolean(if_missing=False)
709
710 landing_rev = UnicodeString(strip=True, min=1, not_empty=True)
710 711 chained_validators = [ValidRepoName(edit, old_data), ValidPerms(),
711 712 ValidSettings]
712 713 return _RepoForm
713 714
714 715
715 716 def ApplicationSettingsForm():
716 717 class _ApplicationSettingsForm(formencode.Schema):
717 718 allow_extra_fields = True
718 719 filter_extra_fields = False
719 720 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
720 721 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
721 722 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
722 723
723 724 return _ApplicationSettingsForm
724 725
725 726
726 727 def ApplicationUiSettingsForm():
727 728 class _ApplicationUiSettingsForm(formencode.Schema):
728 729 allow_extra_fields = True
729 730 filter_extra_fields = False
730 731 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
731 732 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
732 733 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
733 734 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
734 735 hooks_changegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
735 736 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
736 737
737 738 return _ApplicationUiSettingsForm
738 739
739 740
740 741 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
741 742 class _DefaultPermissionsForm(formencode.Schema):
742 743 allow_extra_fields = True
743 744 filter_extra_fields = True
744 745 overwrite_default = StringBoolean(if_missing=False)
745 746 anonymous = OneOf(['True', 'False'], if_missing=False)
746 747 default_perm = OneOf(perms_choices)
747 748 default_register = OneOf(register_choices)
748 749 default_create = OneOf(create_choices)
749 750
750 751 return _DefaultPermissionsForm
751 752
752 753
753 754 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
754 755 class _LdapSettingsForm(formencode.Schema):
755 756 allow_extra_fields = True
756 757 filter_extra_fields = True
757 758 #pre_validators = [LdapLibValidator]
758 759 ldap_active = StringBoolean(if_missing=False)
759 760 ldap_host = UnicodeString(strip=True,)
760 761 ldap_port = Number(strip=True,)
761 762 ldap_tls_kind = OneOf(tls_kind_choices)
762 763 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
763 764 ldap_dn_user = UnicodeString(strip=True,)
764 765 ldap_dn_pass = UnicodeString(strip=True,)
765 766 ldap_base_dn = UnicodeString(strip=True,)
766 767 ldap_filter = UnicodeString(strip=True,)
767 768 ldap_search_scope = OneOf(search_scope_choices)
768 769 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
769 770 ldap_attr_firstname = UnicodeString(strip=True,)
770 771 ldap_attr_lastname = UnicodeString(strip=True,)
771 772 ldap_attr_email = UnicodeString(strip=True,)
772 773
773 774 return _LdapSettingsForm
@@ -1,474 +1,505
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.scm
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Scm model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import time
27 27 import traceback
28 28 import logging
29 29 import cStringIO
30 30
31 31 from sqlalchemy import func
32 from pylons.i18n.translation import _
32 33
33 34 from rhodecode.lib.vcs import get_backend
34 35 from rhodecode.lib.vcs.exceptions import RepositoryError
35 36 from rhodecode.lib.vcs.utils.lazy import LazyProperty
36 37 from rhodecode.lib.vcs.nodes import FileNode
37 38
38 39 from rhodecode import BACKENDS
39 40 from rhodecode.lib import helpers as h
40 41 from rhodecode.lib.utils2 import safe_str, safe_unicode
41 42 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
42 43 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
43 44 action_logger, EmptyChangeset, REMOVED_REPO_PAT
44 45 from rhodecode.model import BaseModel
45 46 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
46 47 UserFollowing, UserLog, User, RepoGroup
47 48
48 49 log = logging.getLogger(__name__)
49 50
50 51
51 52 class UserTemp(object):
52 53 def __init__(self, user_id):
53 54 self.user_id = user_id
54 55
55 56 def __repr__(self):
56 57 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
57 58
58 59
59 60 class RepoTemp(object):
60 61 def __init__(self, repo_id):
61 62 self.repo_id = repo_id
62 63
63 64 def __repr__(self):
64 65 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
65 66
66 67
67 68 class CachedRepoList(object):
68 69
69 70 def __init__(self, db_repo_list, repos_path, order_by=None):
70 71 self.db_repo_list = db_repo_list
71 72 self.repos_path = repos_path
72 73 self.order_by = order_by
73 74 self.reversed = (order_by or '').startswith('-')
74 75
75 76 def __len__(self):
76 77 return len(self.db_repo_list)
77 78
78 79 def __repr__(self):
79 80 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
80 81
81 82 def __iter__(self):
82 83 # pre-propagated cache_map to save executing select statements
83 84 # for each repo
84 85 cache_map = CacheInvalidation.get_cache_map()
85 86
86 87 for dbr in self.db_repo_list:
87 88 scmr = dbr.scm_instance_cached(cache_map)
88 89 # check permission at this level
89 90 if not HasRepoPermissionAny(
90 91 'repository.read', 'repository.write', 'repository.admin'
91 92 )(dbr.repo_name, 'get repo check'):
92 93 continue
93 94
94 95 if scmr is None:
95 96 log.error(
96 97 '%s this repository is present in database but it '
97 98 'cannot be created as an scm instance' % dbr.repo_name
98 99 )
99 100 continue
100 101
101 102 last_change = scmr.last_change
102 103 tip = h.get_changeset_safe(scmr, 'tip')
103 104
104 105 tmp_d = {}
105 106 tmp_d['name'] = dbr.repo_name
106 107 tmp_d['name_sort'] = tmp_d['name'].lower()
107 108 tmp_d['description'] = dbr.description
108 109 tmp_d['description_sort'] = tmp_d['description']
109 110 tmp_d['last_change'] = last_change
110 111 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
111 112 tmp_d['tip'] = tip.raw_id
112 113 tmp_d['tip_sort'] = tip.revision
113 114 tmp_d['rev'] = tip.revision
114 115 tmp_d['contact'] = dbr.user.full_contact
115 116 tmp_d['contact_sort'] = tmp_d['contact']
116 117 tmp_d['owner_sort'] = tmp_d['contact']
117 118 tmp_d['repo_archives'] = list(scmr._get_archives())
118 119 tmp_d['last_msg'] = tip.message
119 120 tmp_d['author'] = tip.author
120 121 tmp_d['dbrepo'] = dbr.get_dict()
121 122 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
122 123 yield tmp_d
123 124
124 125
125 126 class GroupList(object):
126 127
127 128 def __init__(self, db_repo_group_list):
128 129 self.db_repo_group_list = db_repo_group_list
129 130
130 131 def __len__(self):
131 132 return len(self.db_repo_group_list)
132 133
133 134 def __repr__(self):
134 135 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
135 136
136 137 def __iter__(self):
137 138 for dbgr in self.db_repo_group_list:
138 139 # check permission at this level
139 140 if not HasReposGroupPermissionAny(
140 141 'group.read', 'group.write', 'group.admin'
141 142 )(dbgr.group_name, 'get group repo check'):
142 143 continue
143 144
144 145 yield dbgr
145 146
146 147
147 148 class ScmModel(BaseModel):
148 149 """
149 150 Generic Scm Model
150 151 """
151 152
152 153 def __get_repo(self, instance):
153 154 cls = Repository
154 155 if isinstance(instance, cls):
155 156 return instance
156 157 elif isinstance(instance, int) or str(instance).isdigit():
157 158 return cls.get(instance)
158 159 elif isinstance(instance, basestring):
159 160 return cls.get_by_repo_name(instance)
160 161 elif instance:
161 162 raise Exception('given object must be int, basestr or Instance'
162 163 ' of %s got %s' % (type(cls), type(instance)))
163 164
164 165 @LazyProperty
165 166 def repos_path(self):
166 167 """
167 168 Get's the repositories root path from database
168 169 """
169 170
170 171 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
171 172
172 173 return q.ui_value
173 174
174 175 def repo_scan(self, repos_path=None):
175 176 """
176 177 Listing of repositories in given path. This path should not be a
177 178 repository itself. Return a dictionary of repository objects
178 179
179 180 :param repos_path: path to directory containing repositories
180 181 """
181 182
182 183 if repos_path is None:
183 184 repos_path = self.repos_path
184 185
185 186 log.info('scanning for repositories in %s' % repos_path)
186 187
187 188 baseui = make_ui('db')
188 189 repos = {}
189 190
190 191 for name, path in get_filesystem_repos(repos_path, recursive=True):
191 192 # skip removed repos
192 193 if REMOVED_REPO_PAT.match(name):
193 194 continue
194 195
195 196 # name need to be decomposed and put back together using the /
196 197 # since this is internal storage separator for rhodecode
197 198 name = Repository.url_sep().join(name.split(os.sep))
198 199
199 200 try:
200 201 if name in repos:
201 202 raise RepositoryError('Duplicate repository name %s '
202 203 'found in %s' % (name, path))
203 204 else:
204 205
205 206 klass = get_backend(path[0])
206 207
207 208 if path[0] == 'hg' and path[0] in BACKENDS.keys():
208 209 repos[name] = klass(safe_str(path[1]), baseui=baseui)
209 210
210 211 if path[0] == 'git' and path[0] in BACKENDS.keys():
211 212 repos[name] = klass(path[1])
212 213 except OSError:
213 214 continue
214 215
215 216 return repos
216 217
217 218 def get_repos(self, all_repos=None, sort_key=None):
218 219 """
219 220 Get all repos from db and for each repo create it's
220 221 backend instance and fill that backed with information from database
221 222
222 223 :param all_repos: list of repository names as strings
223 224 give specific repositories list, good for filtering
224 225 """
225 226 if all_repos is None:
226 227 all_repos = self.sa.query(Repository)\
227 228 .filter(Repository.group_id == None)\
228 229 .order_by(func.lower(Repository.repo_name)).all()
229 230
230 231 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
231 232 order_by=sort_key)
232 233
233 234 return repo_iter
234 235
235 236 def get_repos_groups(self, all_groups=None):
236 237 if all_groups is None:
237 238 all_groups = RepoGroup.query()\
238 239 .filter(RepoGroup.group_parent_id == None).all()
239 240 group_iter = GroupList(all_groups)
240 241
241 242 return group_iter
242 243
243 244 def mark_for_invalidation(self, repo_name):
244 245 """
245 246 Puts cache invalidation task into db for
246 247 further global cache invalidation
247 248
248 249 :param repo_name: this repo that should invalidation take place
249 250 """
250 251 CacheInvalidation.set_invalidate(repo_name)
251 252
252 253 def toggle_following_repo(self, follow_repo_id, user_id):
253 254
254 255 f = self.sa.query(UserFollowing)\
255 256 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
256 257 .filter(UserFollowing.user_id == user_id).scalar()
257 258
258 259 if f is not None:
259 260 try:
260 261 self.sa.delete(f)
261 262 action_logger(UserTemp(user_id),
262 263 'stopped_following_repo',
263 264 RepoTemp(follow_repo_id))
264 265 return
265 266 except:
266 267 log.error(traceback.format_exc())
267 268 raise
268 269
269 270 try:
270 271 f = UserFollowing()
271 272 f.user_id = user_id
272 273 f.follows_repo_id = follow_repo_id
273 274 self.sa.add(f)
274 275
275 276 action_logger(UserTemp(user_id),
276 277 'started_following_repo',
277 278 RepoTemp(follow_repo_id))
278 279 except:
279 280 log.error(traceback.format_exc())
280 281 raise
281 282
282 283 def toggle_following_user(self, follow_user_id, user_id):
283 284 f = self.sa.query(UserFollowing)\
284 285 .filter(UserFollowing.follows_user_id == follow_user_id)\
285 286 .filter(UserFollowing.user_id == user_id).scalar()
286 287
287 288 if f is not None:
288 289 try:
289 290 self.sa.delete(f)
290 291 return
291 292 except:
292 293 log.error(traceback.format_exc())
293 294 raise
294 295
295 296 try:
296 297 f = UserFollowing()
297 298 f.user_id = user_id
298 299 f.follows_user_id = follow_user_id
299 300 self.sa.add(f)
300 301 except:
301 302 log.error(traceback.format_exc())
302 303 raise
303 304
304 305 def is_following_repo(self, repo_name, user_id, cache=False):
305 306 r = self.sa.query(Repository)\
306 307 .filter(Repository.repo_name == repo_name).scalar()
307 308
308 309 f = self.sa.query(UserFollowing)\
309 310 .filter(UserFollowing.follows_repository == r)\
310 311 .filter(UserFollowing.user_id == user_id).scalar()
311 312
312 313 return f is not None
313 314
314 315 def is_following_user(self, username, user_id, cache=False):
315 316 u = User.get_by_username(username)
316 317
317 318 f = self.sa.query(UserFollowing)\
318 319 .filter(UserFollowing.follows_user == u)\
319 320 .filter(UserFollowing.user_id == user_id).scalar()
320 321
321 322 return f is not None
322 323
323 324 def get_followers(self, repo_id):
324 325 if not isinstance(repo_id, int):
325 326 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
326 327
327 328 return self.sa.query(UserFollowing)\
328 329 .filter(UserFollowing.follows_repo_id == repo_id).count()
329 330
330 331 def get_forks(self, repo_id):
331 332 if not isinstance(repo_id, int):
332 333 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
333 334
334 335 return self.sa.query(Repository)\
335 336 .filter(Repository.fork_id == repo_id).count()
336 337
337 338 def mark_as_fork(self, repo, fork, user):
338 339 repo = self.__get_repo(repo)
339 340 fork = self.__get_repo(fork)
340 341 repo.fork = fork
341 342 self.sa.add(repo)
342 343 return repo
343 344
344 345 def pull_changes(self, repo_name, username):
345 346 dbrepo = Repository.get_by_repo_name(repo_name)
346 347 clone_uri = dbrepo.clone_uri
347 348 if not clone_uri:
348 349 raise Exception("This repository doesn't have a clone uri")
349 350
350 351 repo = dbrepo.scm_instance
351 352 try:
352 353 extras = {
353 354 'ip': '',
354 355 'username': username,
355 356 'action': 'push_remote',
356 357 'repository': repo_name,
357 358 'scm': repo.alias,
358 359 }
359 360
360 361 # inject ui extra param to log this action via push logger
361 362 for k, v in extras.items():
362 363 repo._repo.ui.setconfig('rhodecode_extras', k, v)
363 364 if repo.alias == 'git':
364 365 repo.fetch(clone_uri)
365 366 else:
366 367 repo.pull(clone_uri)
367 368 self.mark_for_invalidation(repo_name)
368 369 except:
369 370 log.error(traceback.format_exc())
370 371 raise
371 372
372 373 def commit_change(self, repo, repo_name, cs, user, author, message,
373 374 content, f_path):
374 375
375 376 if repo.alias == 'hg':
376 377 from rhodecode.lib.vcs.backends.hg import \
377 378 MercurialInMemoryChangeset as IMC
378 379 elif repo.alias == 'git':
379 380 from rhodecode.lib.vcs.backends.git import \
380 381 GitInMemoryChangeset as IMC
381 382
382 383 # decoding here will force that we have proper encoded values
383 384 # in any other case this will throw exceptions and deny commit
384 385 content = safe_str(content)
385 386 path = safe_str(f_path)
386 387 # message and author needs to be unicode
387 388 # proper backend should then translate that into required type
388 389 message = safe_unicode(message)
389 390 author = safe_unicode(author)
390 391 m = IMC(repo)
391 392 m.change(FileNode(path, content))
392 393 tip = m.commit(message=message,
393 394 author=author,
394 395 parents=[cs], branch=cs.branch)
395 396
396 397 new_cs = tip.short_id
397 398 action = 'push_local:%s' % new_cs
398 399
399 400 action_logger(user, action, repo_name)
400 401
401 402 self.mark_for_invalidation(repo_name)
402 403
403 404 def create_node(self, repo, repo_name, cs, user, author, message, content,
404 405 f_path):
405 406 if repo.alias == 'hg':
406 407 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
407 408 elif repo.alias == 'git':
408 409 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
409 410 # decoding here will force that we have proper encoded values
410 411 # in any other case this will throw exceptions and deny commit
411 412
412 413 if isinstance(content, (basestring,)):
413 414 content = safe_str(content)
414 415 elif isinstance(content, (file, cStringIO.OutputType,)):
415 416 content = content.read()
416 417 else:
417 418 raise Exception('Content is of unrecognized type %s' % (
418 419 type(content)
419 420 ))
420 421
421 422 message = safe_unicode(message)
422 423 author = safe_unicode(author)
423 424 path = safe_str(f_path)
424 425 m = IMC(repo)
425 426
426 427 if isinstance(cs, EmptyChangeset):
427 428 # EmptyChangeset means we we're editing empty repository
428 429 parents = None
429 430 else:
430 431 parents = [cs]
431 432
432 433 m.add(FileNode(path, content=content))
433 434 tip = m.commit(message=message,
434 435 author=author,
435 436 parents=parents, branch=cs.branch)
436 437 new_cs = tip.short_id
437 438 action = 'push_local:%s' % new_cs
438 439
439 440 action_logger(user, action, repo_name)
440 441
441 442 self.mark_for_invalidation(repo_name)
442 443
443 444 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
444 445 """
445 446 recursive walk in root dir and return a set of all path in that dir
446 447 based on repository walk function
447 448
448 449 :param repo_name: name of repository
449 450 :param revision: revision for which to list nodes
450 451 :param root_path: root path to list
451 452 :param flat: return as a list, if False returns a dict with decription
452 453
453 454 """
454 455 _files = list()
455 456 _dirs = list()
456 457 try:
457 458 _repo = self.__get_repo(repo_name)
458 459 changeset = _repo.scm_instance.get_changeset(revision)
459 460 root_path = root_path.lstrip('/')
460 461 for topnode, dirs, files in changeset.walk(root_path):
461 462 for f in files:
462 463 _files.append(f.path if flat else {"name": f.path,
463 464 "type": "file"})
464 465 for d in dirs:
465 466 _dirs.append(d.path if flat else {"name": d.path,
466 467 "type": "dir"})
467 468 except RepositoryError:
468 469 log.debug(traceback.format_exc())
469 470 raise
470 471
471 472 return _dirs, _files
472 473
473 474 def get_unread_journal(self):
474 475 return self.sa.query(UserLog).count()
476
477 def get_repo_landing_revs(self, repo=None):
478 """
479 Generates select option with tags branches and bookmarks (for hg only)
480 grouped by type
481
482 :param repo:
483 :type repo:
484 """
485 hist_l = []
486 repo = self.__get_repo(repo)
487 hist_l.append(['tip', _('latest tip')])
488 if not repo:
489 return hist_l
490
491 repo = repo.scm_instance
492 branches_group = ([(k, k) for k, v in
493 repo.branches.iteritems()], _("Branches"))
494 hist_l.append(branches_group)
495
496 if repo.alias == 'hg':
497 bookmarks_group = ([(k, k) for k, v in
498 repo.bookmarks.iteritems()], _("Bookmarks"))
499 hist_l.append(bookmarks_group)
500
501 tags_group = ([(k, k) for k, v in
502 repo.tags.iteritems()], _("Tags"))
503 hist_l.append(tags_group)
504
505 return hist_l
@@ -1,68 +1,77
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 ${h.form(url('repos'))}
4 4 <div class="form">
5 5 <!-- fields -->
6 6 <div class="fields">
7 7 <div class="field">
8 8 <div class="label">
9 9 <label for="repo_name">${_('Name')}:</label>
10 10 </div>
11 11 <div class="input">
12 12 ${h.text('repo_name',c.new_repo,class_="small")}
13 13 %if not h.HasPermissionAll('hg.admin')('repo create form'):
14 14 ${h.hidden('user_created',True)}
15 15 %endif
16 16 </div>
17 17 </div>
18 18 <div class="field">
19 19 <div class="label">
20 20 <label for="clone_uri">${_('Clone from')}:</label>
21 21 </div>
22 22 <div class="input">
23 23 ${h.text('clone_uri',class_="small")}
24 24 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
25 25 </div>
26 26 </div>
27 27 <div class="field">
28 28 <div class="label">
29 29 <label for="repo_group">${_('Repository group')}:</label>
30 30 </div>
31 31 <div class="input">
32 32 ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
33 33 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
34 34 </div>
35 35 </div>
36 36 <div class="field">
37 37 <div class="label">
38 38 <label for="repo_type">${_('Type')}:</label>
39 39 </div>
40 40 <div class="input">
41 41 ${h.select('repo_type','hg',c.backends,class_="small")}
42 42 <span class="help-block">${_('Type of repository to create.')}</span>
43 43 </div>
44 44 </div>
45 <div class="field">
46 <div class="label">
47 <label for="landing_rev">${_('Landing revision')}:</label>
48 </div>
49 <div class="input">
50 ${h.select('landing_rev','',c.landing_revs,class_="medium")}
51 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
52 </div>
53 </div>
45 54 <div class="field">
46 55 <div class="label label-textarea">
47 56 <label for="description">${_('Description')}:</label>
48 57 </div>
49 58 <div class="textarea text-area editor">
50 59 ${h.textarea('description')}
51 60 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
52 61 </div>
53 62 </div>
54 63 <div class="field">
55 64 <div class="label label-checkbox">
56 65 <label for="private">${_('Private repository')}:</label>
57 66 </div>
58 67 <div class="checkboxes">
59 68 ${h.checkbox('private',value="True")}
60 69 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
61 70 </div>
62 71 </div>
63 72 <div class="buttons">
64 73 ${h.submit('add',_('add'),class_="ui-button")}
65 74 </div>
66 75 </div>
67 76 </div>
68 77 ${h.end_form()}
@@ -1,231 +1,240
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit repository')} ${c.repo_info.repo_name} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('Repositories'),h.url('repos'))}
12 12 &raquo;
13 13 ${_('edit')} &raquo; ${h.link_to(c.repo_info.just_name,h.url('summary_home',repo_name=c.repo_name))}
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 32 <label for="repo_name">${_('Name')}:</label>
33 33 </div>
34 34 <div class="input">
35 35 ${h.text('repo_name',class_="medium")}
36 36 </div>
37 37 </div>
38 38 <div class="field">
39 39 <div class="label">
40 40 <label for="clone_uri">${_('Clone uri')}:</label>
41 41 </div>
42 42 <div class="input">
43 43 ${h.text('clone_uri',class_="medium")}
44 44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
45 45 </div>
46 46 </div>
47 47 <div class="field">
48 48 <div class="label">
49 49 <label for="repo_group">${_('Repository group')}:</label>
50 50 </div>
51 51 <div class="input">
52 52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
53 53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
54 54 </div>
55 55 </div>
56 56 <div class="field">
57 57 <div class="label">
58 58 <label for="repo_type">${_('Type')}:</label>
59 59 </div>
60 60 <div class="input">
61 61 ${h.select('repo_type','hg',c.backends,class_="medium")}
62 62 </div>
63 63 </div>
64 64 <div class="field">
65 <div class="label">
66 <label for="landing_rev">${_('Landing revision')}:</label>
67 </div>
68 <div class="input">
69 ${h.select('landing_rev','',c.landing_revs,class_="medium")}
70 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
71 </div>
72 </div>
73 <div class="field">
65 74 <div class="label label-textarea">
66 75 <label for="description">${_('Description')}:</label>
67 76 </div>
68 77 <div class="textarea text-area editor">
69 78 ${h.textarea('description')}
70 79 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
71 80 </div>
72 81 </div>
73 82
74 83 <div class="field">
75 84 <div class="label label-checkbox">
76 85 <label for="private">${_('Private repository')}:</label>
77 86 </div>
78 87 <div class="checkboxes">
79 88 ${h.checkbox('private',value="True")}
80 89 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
81 90 </div>
82 91 </div>
83 92 <div class="field">
84 93 <div class="label label-checkbox">
85 94 <label for="enable_statistics">${_('Enable statistics')}:</label>
86 95 </div>
87 96 <div class="checkboxes">
88 97 ${h.checkbox('enable_statistics',value="True")}
89 98 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
90 99 </div>
91 100 </div>
92 101 <div class="field">
93 102 <div class="label label-checkbox">
94 103 <label for="enable_downloads">${_('Enable downloads')}:</label>
95 104 </div>
96 105 <div class="checkboxes">
97 106 ${h.checkbox('enable_downloads',value="True")}
98 107 <span class="help-block">${_('Enable download menu on summary page.')}</span>
99 108 </div>
100 109 </div>
101 110 <div class="field">
102 111 <div class="label">
103 112 <label for="user">${_('Owner')}:</label>
104 113 </div>
105 114 <div class="input input-medium ac">
106 115 <div class="perm_ac">
107 116 ${h.text('user',class_='yui-ac-input')}
108 117 <span class="help-block">${_('Change owner of this repository.')}</span>
109 118 <div id="owner_container"></div>
110 119 </div>
111 120 </div>
112 121 </div>
113 122
114 123 <div class="field">
115 124 <div class="label">
116 125 <label for="input">${_('Permissions')}:</label>
117 126 </div>
118 127 <div class="input">
119 128 <%include file="repo_edit_perms.html"/>
120 129 </div>
121 130
122 131 <div class="buttons">
123 132 ${h.submit('save','Save',class_="ui-button")}
124 133 ${h.reset('reset','Reset',class_="ui-button")}
125 134 </div>
126 135 </div>
127 136 </div>
128 137 </div>
129 138 ${h.end_form()}
130 139 </div>
131 140
132 141 <div class="box box-right">
133 142 <div class="title">
134 143 <h5>${_('Administration')}</h5>
135 144 </div>
136 145
137 146 <h3>${_('Statistics')}</h3>
138 147 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
139 148 <div class="form">
140 149 <div class="fields">
141 150 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
142 151 <div class="field" style="border:none;color:#888">
143 152 <ul>
144 153 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
145 154 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
146 155 </ul>
147 156 </div>
148 157 </div>
149 158 </div>
150 159 ${h.end_form()}
151 160
152 161 %if c.repo_info.clone_uri:
153 162 <h3>${_('Remote')}</h3>
154 163 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
155 164 <div class="form">
156 165 <div class="fields">
157 166 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
158 167 <div class="field" style="border:none">
159 168 <ul>
160 169 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
161 170 </ul>
162 171 </div>
163 172 </div>
164 173 </div>
165 174 ${h.end_form()}
166 175 %endif
167 176
168 177 <h3>${_('Cache')}</h3>
169 178 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
170 179 <div class="form">
171 180 <div class="fields">
172 181 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
173 182 </div>
174 183 </div>
175 184 ${h.end_form()}
176 185
177 186 <h3>${_('Public journal')}</h3>
178 187 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
179 188 <div class="form">
180 189 ${h.hidden('auth_token',str(h.get_token()))}
181 190 <div class="field">
182 191 %if c.in_public_journal:
183 192 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
184 193 %else:
185 194 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
186 195 %endif
187 196 </div>
188 197 <div class="field" style="border:none;color:#888">
189 198 <ul>
190 199 <li>${_('''All actions made on this repository will be accessible to everyone in public journal''')}
191 200 </li>
192 201 </ul>
193 202 </div>
194 203 </div>
195 204 ${h.end_form()}
196 205
197 206 <h3>${_('Delete')}</h3>
198 207 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
199 208 <div class="form">
200 209 <div class="fields">
201 210 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
202 211 </div>
203 212 <div class="field" style="border:none;color:#888">
204 213 <ul>
205 214 <li>${_('''This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems.
206 215 If you need fully delete it from filesystem please do it manually''')}
207 216 </li>
208 217 </ul>
209 218 </div>
210 219 </div>
211 220 ${h.end_form()}
212 221
213 <h3>${_('Set as fork')}</h3>
222 <h3>${_('Set as fork of')}</h3>
214 223 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
215 224 <div class="form">
216 225 <div class="fields">
217 226 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
218 227 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
219 228 </div>
220 229 <div class="field" style="border:none;color:#888">
221 230 <ul>
222 <li>${_('''Manually set this repository as a fork of another''')}</li>
231 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
223 232 </ul>
224 233 </div>
225 234 </div>
226 235 ${h.end_form()}
227 236
228 237 </div>
229 238
230 239
231 240 </%def>
@@ -1,158 +1,158
1 1 """Pylons application test package
2 2
3 3 This package assumes the Pylons environment is already loaded, such as
4 4 when this script is imported from the `nosetests --with-pylons=test.ini`
5 5 command.
6 6
7 7 This module initializes the application via ``websetup`` (`paster
8 8 setup-app`) and provides the base testing objects.
9 9 """
10 10 import os
11 11 import time
12 12 import logging
13 13 import datetime
14 14 import hashlib
15 15 import tempfile
16 16 from os.path import join as jn
17 17
18 18 from unittest import TestCase
19 19 from tempfile import _RandomNameSequence
20 20
21 21 from paste.deploy import loadapp
22 22 from paste.script.appinstall import SetupCommand
23 23 from pylons import config, url
24 24 from routes.util import URLGenerator
25 25 from webtest import TestApp
26 26
27 27 from rhodecode import is_windows
28 28 from rhodecode.model.meta import Session
29 29 from rhodecode.model.db import User
30 30
31 31 import pylons.test
32 32
33 33
34 34 os.environ['TZ'] = 'UTC'
35 35 if not is_windows:
36 36 time.tzset()
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40 __all__ = [
41 'environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO',
42 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK',
43 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_REGULAR_LOGIN',
41 'environ', 'url', 'get_new_dir', 'TestController', 'TESTS_TMP_PATH',
42 'HG_REPO', 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK',
43 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_REGULAR_LOGIN',
44 44 'TEST_USER_REGULAR_PASS', 'TEST_USER_REGULAR_EMAIL',
45 45 'TEST_USER_REGULAR2_LOGIN', 'TEST_USER_REGULAR2_PASS',
46 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO', 'TEST_GIT_REPO',
47 'HG_REMOTE_REPO', 'GIT_REMOTE_REPO', 'SCM_TESTS',
46 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO', 'TEST_HG_REPO_CLONE',
47 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO', 'TEST_GIT_REPO_CLONE',
48 'TEST_GIT_REPO_PULL', 'HG_REMOTE_REPO', 'GIT_REMOTE_REPO', 'SCM_TESTS',
48 49 ]
49 50
50 51 # Invoke websetup with the current config file
51 52 # SetupCommand('setup-app').run([config_file])
52 53
53 54 ##RUNNING DESIRED TESTS
54 55 # nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account
55 56 # nosetests --pdb --pdb-failures
56 57 environ = {}
57 58
58 59 #SOME GLOBALS FOR TESTS
59 60
60 61 TESTS_TMP_PATH = jn('/', 'tmp', 'rc_test_%s' % _RandomNameSequence().next())
61 62 TEST_USER_ADMIN_LOGIN = 'test_admin'
62 63 TEST_USER_ADMIN_PASS = 'test12'
63 64 TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
64 65
65 66 TEST_USER_REGULAR_LOGIN = 'test_regular'
66 67 TEST_USER_REGULAR_PASS = 'test12'
67 68 TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
68 69
69 70 TEST_USER_REGULAR2_LOGIN = 'test_regular2'
70 71 TEST_USER_REGULAR2_PASS = 'test12'
71 72 TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
72 73
73 74 HG_REPO = 'vcs_test_hg'
74 75 GIT_REPO = 'vcs_test_git'
75 76
76 77 NEW_HG_REPO = 'vcs_test_hg_new'
77 78 NEW_GIT_REPO = 'vcs_test_git_new'
78 79
79 80 HG_FORK = 'vcs_test_hg_fork'
80 81 GIT_FORK = 'vcs_test_git_fork'
81 82
82 83 ## VCS
83 84 SCM_TESTS = ['hg', 'git']
84 85 uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
85 86
86 THIS = os.path.abspath(os.path.dirname(__file__))
87 GIT_REMOTE_REPO = 'git://github.com/codeinn/vcs.git'
87 88
88 GIT_REMOTE_REPO = 'git://github.com/codeinn/vcs.git'
89 89 TEST_GIT_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
90 90 TEST_GIT_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcsgitclone%s' % uniq_suffix)
91 91 TEST_GIT_REPO_PULL = jn(TESTS_TMP_PATH, 'vcsgitpull%s' % uniq_suffix)
92 92
93 93
94 94 HG_REMOTE_REPO = 'http://bitbucket.org/marcinkuzminski/vcs'
95 TEST_HG_REPO = jn(TESTS_TMP_PATH, 'vcs-hg')
95
96 TEST_HG_REPO = jn(TESTS_TMP_PATH, HG_REPO)
96 97 TEST_HG_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcshgclone%s' % uniq_suffix)
97 98 TEST_HG_REPO_PULL = jn(TESTS_TMP_PATH, 'vcshgpull%s' % uniq_suffix)
98 99
99 100 TEST_DIR = tempfile.gettempdir()
100 101 TEST_REPO_PREFIX = 'vcs-test'
101 102
103 # cached repos if any !
104 # comment out to get some other repos from bb or github
105 GIT_REMOTE_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
106 HG_REMOTE_REPO = jn(TESTS_TMP_PATH, HG_REPO)
107
102 108
103 109 def get_new_dir(title):
104 110 """
105 111 Returns always new directory path.
106 112 """
107 113 from rhodecode.tests.vcs.utils import get_normalized_path
108 114 name = TEST_REPO_PREFIX
109 115 if title:
110 116 name = '-'.join((name, title))
111 117 hex = hashlib.sha1(str(time.time())).hexdigest()
112 118 name = '-'.join((name, hex))
113 119 path = os.path.join(TEST_DIR, name)
114 120 return get_normalized_path(path)
115 121
116 122
117 PACKAGE_DIR = os.path.abspath(os.path.join(
118 os.path.dirname(__file__), '..'))
119
120 TEST_USER_CONFIG_FILE = jn(THIS, 'aconfig')
121
122
123 123 class TestController(TestCase):
124 124
125 125 def __init__(self, *args, **kwargs):
126 126 wsgiapp = pylons.test.pylonsapp
127 127 config = wsgiapp.config
128 128
129 129 self.app = TestApp(wsgiapp)
130 130 url._push_object(URLGenerator(config['routes.map'], environ))
131 131 self.Session = Session
132 132 self.index_location = config['app_conf']['index_dir']
133 133 TestCase.__init__(self, *args, **kwargs)
134 134
135 135 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
136 136 password=TEST_USER_ADMIN_PASS):
137 137 self._logged_username = username
138 138 response = self.app.post(url(controller='login', action='index'),
139 139 {'username': username,
140 140 'password': password})
141 141
142 142 if 'invalid user name' in response.body:
143 143 self.fail('could not login using %s %s' % (username, password))
144 144
145 145 self.assertEqual(response.status, '302 Found')
146 146 ses = response.session['rhodecode_user']
147 147 self.assertEqual(ses.get('username'), username)
148 148 response = response.follow()
149 149 self.assertEqual(ses.get('is_authenticated'), True)
150 150
151 151 return response.session['rhodecode_user']
152 152
153 153 def _get_logged_user(self):
154 154 return User.get_by_username(self._logged_username)
155 155
156 156 def checkSessionFlash(self, response, msg):
157 157 self.assertTrue('flash' in response.session)
158 158 self.assertTrue(msg in response.session['flash'][0][1])
@@ -1,210 +1,295
1 1 # -*- coding: utf-8 -*-
2 2
3 3 import os
4 4 from rhodecode.lib import vcs
5 5
6 6 from rhodecode.model.db import Repository
7 7 from rhodecode.tests import *
8 8
9
9 10 class TestAdminReposController(TestController):
10 11
11 12 def __make_repo(self):
12 13 pass
13 14
14 15 def test_index(self):
15 16 self.log_user()
16 17 response = self.app.get(url('repos'))
17 18 # Test response...
18 19
19 20 def test_index_as_xml(self):
20 21 response = self.app.get(url('formatted_repos', format='xml'))
21 22
22 23 def test_create_hg(self):
23 24 self.log_user()
24 25 repo_name = NEW_HG_REPO
25 26 description = 'description for newly created repo'
26 27 private = False
27 response = self.app.post(url('repos'), {'repo_name':repo_name,
28 'repo_type':'hg',
29 'clone_uri':'',
30 'repo_group':'',
31 'description':description,
32 'private':private})
33 self.checkSessionFlash(response, 'created repository %s' % (repo_name))
28 response = self.app.post(url('repos'), {'repo_name': repo_name,
29 'repo_type': 'hg',
30 'clone_uri': '',
31 'repo_group': '',
32 'description': description,
33 'private': private,
34 'landing_rev': 'tip'})
35 self.checkSessionFlash(response,
36 'created repository %s' % (repo_name))
34 37
35 38 #test if the repo was created in the database
36 new_repo = self.Session.query(Repository).filter(Repository.repo_name ==
37 repo_name).one()
39 new_repo = self.Session.query(Repository)\
40 .filter(Repository.repo_name == repo_name).one()
38 41
39 42 self.assertEqual(new_repo.repo_name, repo_name)
40 43 self.assertEqual(new_repo.description, description)
41 44
42 45 #test if repository is visible in the list ?
43 46 response = response.follow()
44 47
45 self.assertTrue(repo_name in response.body)
46
48 response.mustcontain(repo_name)
47 49
48 50 #test if repository was created on filesystem
49 51 try:
50 52 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
51 53 except:
52 self.fail('no repo in filesystem')
53
54 self.fail('no repo %s in filesystem' % repo_name)
54 55
55 56 def test_create_hg_non_ascii(self):
56 57 self.log_user()
57 58 non_ascii = "ąęł"
58 59 repo_name = "%s%s" % (NEW_HG_REPO, non_ascii)
59 60 repo_name_unicode = repo_name.decode('utf8')
60 61 description = 'description for newly created repo' + non_ascii
61 62 description_unicode = description.decode('utf8')
62 63 private = False
63 response = self.app.post(url('repos'), {'repo_name':repo_name,
64 'repo_type':'hg',
65 'clone_uri':'',
66 'repo_group':'',
67 'description':description,
68 'private':private})
64 response = self.app.post(url('repos'), {'repo_name': repo_name,
65 'repo_type': 'hg',
66 'clone_uri': '',
67 'repo_group': '',
68 'description': description,
69 'private': private,
70 'landing_rev': 'tip'})
69 71 self.checkSessionFlash(response,
70 72 'created repository %s' % (repo_name_unicode))
71 73
72 74 #test if the repo was created in the database
73 new_repo = self.Session.query(Repository).filter(Repository.repo_name ==
74 repo_name_unicode).one()
75 new_repo = self.Session.query(Repository)\
76 .filter(Repository.repo_name == repo_name_unicode).one()
75 77
76 78 self.assertEqual(new_repo.repo_name, repo_name_unicode)
77 79 self.assertEqual(new_repo.description, description_unicode)
78 80
79 81 #test if repository is visible in the list ?
80 82 response = response.follow()
81 83
82 self.assertTrue(repo_name in response.body)
84 response.mustcontain(repo_name)
83 85
84 86 #test if repository was created on filesystem
85 87 try:
86 88 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
87 89 except:
88 self.fail('no repo in filesystem')
89
90 self.fail('no repo %s in filesystem' % repo_name)
90 91
91 92 def test_create_hg_in_group(self):
92 93 #TODO: write test !
93 94 pass
94 95
95 96 def test_create_git(self):
96 return
97 97 self.log_user()
98 98 repo_name = NEW_GIT_REPO
99 99 description = 'description for newly created repo'
100 100 private = False
101 response = self.app.post(url('repos'), {'repo_name':repo_name,
102 'repo_type':'git',
103 'clone_uri':'',
104 'repo_group':'',
105 'description':description,
106 'private':private})
107
101 response = self.app.post(url('repos'), {'repo_name': repo_name,
102 'repo_type': 'git',
103 'clone_uri': '',
104 'repo_group': '',
105 'description': description,
106 'private': private,
107 'landing_rev': 'tip'})
108 self.checkSessionFlash(response,
109 'created repository %s' % (repo_name))
108 110
109 #test if we have a message for that repository
110 assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo'
111 #test if the repo was created in the database
112 new_repo = self.Session.query(Repository)\
113 .filter(Repository.repo_name == repo_name).one()
111 114
112 #test if the fork was created in the database
113 new_repo = self.Session.query(Repository).filter(Repository.repo_name == repo_name).one()
114
115 assert new_repo.repo_name == repo_name, 'wrong name of repo name in db'
116 assert new_repo.description == description, 'wrong description'
115 self.assertEqual(new_repo.repo_name, repo_name)
116 self.assertEqual(new_repo.description, description)
117 117
118 118 #test if repository is visible in the list ?
119 119 response = response.follow()
120 120
121 assert repo_name in response.body, 'missing new repo from the main repos list'
121 response.mustcontain(repo_name)
122 122
123 123 #test if repository was created on filesystem
124 124 try:
125 125 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
126 126 except:
127 assert False , 'no repo in filesystem'
127 self.fail('no repo %s in filesystem' % repo_name)
128
129 def test_create_git_non_ascii(self):
130 self.log_user()
131 non_ascii = "ąęł"
132 repo_name = "%s%s" % (NEW_GIT_REPO, non_ascii)
133 repo_name_unicode = repo_name.decode('utf8')
134 description = 'description for newly created repo' + non_ascii
135 description_unicode = description.decode('utf8')
136 private = False
137 response = self.app.post(url('repos'), {'repo_name': repo_name,
138 'repo_type': 'git',
139 'clone_uri': '',
140 'repo_group': '',
141 'description': description,
142 'private': private,
143 'landing_rev': 'tip'})
144 self.checkSessionFlash(response,
145 'created repository %s' % (repo_name_unicode))
146
147 #test if the repo was created in the database
148 new_repo = self.Session.query(Repository)\
149 .filter(Repository.repo_name == repo_name_unicode).one()
150
151 self.assertEqual(new_repo.repo_name, repo_name_unicode)
152 self.assertEqual(new_repo.description, description_unicode)
153
154 #test if repository is visible in the list ?
155 response = response.follow()
156
157 response.mustcontain(repo_name)
158
159 #test if repository was created on filesystem
160 try:
161 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
162 except:
163 self.fail('no repo %s in filesystem' % repo_name)
128 164
129 165 def test_new(self):
130 166 self.log_user()
131 167 response = self.app.get(url('new_repo'))
132 168
133 169 def test_new_as_xml(self):
134 170 response = self.app.get(url('formatted_new_repo', format='xml'))
135 171
136 172 def test_update(self):
137 173 response = self.app.put(url('repo', repo_name=HG_REPO))
138 174
139 175 def test_update_browser_fakeout(self):
140 176 response = self.app.post(url('repo', repo_name=HG_REPO),
141 177 params=dict(_method='put'))
142 178
143 def test_delete(self):
179 def test_delete_hg(self):
144 180 self.log_user()
145 181 repo_name = 'vcs_test_new_to_delete'
146 182 description = 'description for newly created repo'
147 183 private = False
148
149 response = self.app.post(url('repos'), {'repo_name':repo_name,
150 'repo_type':'hg',
151 'clone_uri':'',
152 'repo_group':'',
153 'description':description,
154 'private':private})
155 self.assertTrue('flash' in response.session)
156
157 #test if we have a message for that repository
158 self.assertTrue('''created repository %s''' % (repo_name) in
159 response.session['flash'][0])
184 response = self.app.post(url('repos'), {'repo_name': repo_name,
185 'repo_type': 'hg',
186 'clone_uri': '',
187 'repo_group': '',
188 'description': description,
189 'private': private,
190 'landing_rev': 'tip'})
191 self.checkSessionFlash(response,
192 'created repository %s' % (repo_name))
160 193
161 194 #test if the repo was created in the database
162 new_repo = self.Session.query(Repository).filter(Repository.repo_name ==
163 repo_name).one()
195 new_repo = self.Session.query(Repository)\
196 .filter(Repository.repo_name == repo_name).one()
164 197
165 198 self.assertEqual(new_repo.repo_name, repo_name)
166 199 self.assertEqual(new_repo.description, description)
167 200
168 201 #test if repository is visible in the list ?
169 202 response = response.follow()
170 203
171 self.assertTrue(repo_name in response.body)
204 response.mustcontain(repo_name)
172 205
206 #test if repository was created on filesystem
207 try:
208 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
209 except:
210 self.fail('no repo %s in filesystem' % repo_name)
173 211
174 212 response = self.app.delete(url('repo', repo_name=repo_name))
175 213
176 214 self.assertTrue('''deleted repository %s''' % (repo_name) in
177 215 response.session['flash'][0])
178 216
179 217 response.follow()
180 218
181 219 #check if repo was deleted from db
182 deleted_repo = self.Session.query(Repository).filter(Repository.repo_name
183 == repo_name).scalar()
220 deleted_repo = self.Session.query(Repository)\
221 .filter(Repository.repo_name == repo_name).scalar()
184 222
185 223 self.assertEqual(deleted_repo, None)
186 224
225 self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)),
226 False)
227
228 def test_delete_git(self):
229 self.log_user()
230 repo_name = 'vcs_test_new_to_delete'
231 description = 'description for newly created repo'
232 private = False
233 response = self.app.post(url('repos'), {'repo_name': repo_name,
234 'repo_type': 'git',
235 'clone_uri': '',
236 'repo_group': '',
237 'description': description,
238 'private': private,
239 'landing_rev': 'tip'})
240 self.checkSessionFlash(response,
241 'created repository %s' % (repo_name))
242
243 #test if the repo was created in the database
244 new_repo = self.Session.query(Repository)\
245 .filter(Repository.repo_name == repo_name).one()
246
247 self.assertEqual(new_repo.repo_name, repo_name)
248 self.assertEqual(new_repo.description, description)
249
250 #test if repository is visible in the list ?
251 response = response.follow()
252
253 response.mustcontain(repo_name)
254
255 #test if repository was created on filesystem
256 try:
257 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
258 except:
259 self.fail('no repo %s in filesystem' % repo_name)
260
261 response = self.app.delete(url('repo', repo_name=repo_name))
262
263 self.assertTrue('''deleted repository %s''' % (repo_name) in
264 response.session['flash'][0])
265
266 response.follow()
267
268 #check if repo was deleted from db
269 deleted_repo = self.Session.query(Repository)\
270 .filter(Repository.repo_name == repo_name).scalar()
271
272 self.assertEqual(deleted_repo, None)
273
274 self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)),
275 False)
187 276
188 277 def test_delete_repo_with_group(self):
189 278 #TODO:
190 279 pass
191 280
192
193 281 def test_delete_browser_fakeout(self):
194 282 response = self.app.post(url('repo', repo_name=HG_REPO),
195 283 params=dict(_method='delete'))
196 284
197 def test_show(self):
285 def test_show_hg(self):
198 286 self.log_user()
199 287 response = self.app.get(url('repo', repo_name=HG_REPO))
200 288
201 def test_show_as_xml(self):
202 response = self.app.get(url('formatted_repo', repo_name=HG_REPO,
203 format='xml'))
289 def test_show_git(self):
290 self.log_user()
291 response = self.app.get(url('repo', repo_name=GIT_REPO))
292
204 293
205 294 def test_edit(self):
206 295 response = self.app.get(url('edit_repo', repo_name=HG_REPO))
207
208 def test_edit_as_xml(self):
209 response = self.app.get(url('formatted_edit_repo', repo_name=HG_REPO,
210 format='xml'))
@@ -1,133 +1,160
1 1 from rhodecode.tests import *
2 2
3 3 from rhodecode.model.db import Repository
4 4 from rhodecode.model.repo import RepoModel
5 5 from rhodecode.model.user import UserModel
6 6
7 7
8 8 class TestForksController(TestController):
9 9
10 10 def setUp(self):
11 11 self.username = u'forkuser'
12 12 self.password = u'qweqwe'
13 13 self.u1 = UserModel().create_or_update(
14 14 username=self.username, password=self.password,
15 15 email=u'fork_king@rhodecode.org', name=u'u1', lastname=u'u1'
16 16 )
17 17 self.Session.commit()
18 18
19 19 def tearDown(self):
20 20 self.Session.delete(self.u1)
21 21 self.Session.commit()
22 22
23 23 def test_index(self):
24 24 self.log_user()
25 25 repo_name = HG_REPO
26 26 response = self.app.get(url(controller='forks', action='forks',
27 27 repo_name=repo_name))
28 28
29 29 self.assertTrue("""There are no forks yet""" in response.body)
30 30
31 def test_index_with_fork(self):
31 def test_index_with_fork_hg(self):
32 32 self.log_user()
33 33
34 34 # create a fork
35 35 fork_name = HG_FORK
36 36 description = 'fork of vcs test'
37 37 repo_name = HG_REPO
38 38 org_repo = Repository.get_by_repo_name(repo_name)
39 39 response = self.app.post(url(controller='forks',
40 40 action='fork_create',
41 41 repo_name=repo_name),
42 {'repo_name':fork_name,
43 'repo_group':'',
44 'fork_parent_id':org_repo.repo_id,
45 'repo_type':'hg',
46 'description':description,
47 'private':'False'})
42 {'repo_name': fork_name,
43 'repo_group': '',
44 'fork_parent_id': org_repo.repo_id,
45 'repo_type': 'hg',
46 'description': description,
47 'private': 'False',
48 'landing_rev': 'tip'})
48 49
49 50 response = self.app.get(url(controller='forks', action='forks',
50 51 repo_name=repo_name))
51 52
52 self.assertTrue("""<a href="/%s/summary">"""
53 """vcs_test_hg_fork</a>""" % fork_name
54 in response.body)
53 response.mustcontain(
54 """<a href="/%s/summary">%s</a>""" % (fork_name, fork_name)
55 )
56
57 #remove this fork
58 response = self.app.delete(url('repo', repo_name=fork_name))
59
60 def test_index_with_fork_git(self):
61 self.log_user()
62
63 # create a fork
64 fork_name = GIT_FORK
65 description = 'fork of vcs test'
66 repo_name = GIT_REPO
67 org_repo = Repository.get_by_repo_name(repo_name)
68 response = self.app.post(url(controller='forks',
69 action='fork_create',
70 repo_name=repo_name),
71 {'repo_name': fork_name,
72 'repo_group': '',
73 'fork_parent_id': org_repo.repo_id,
74 'repo_type': 'git',
75 'description': description,
76 'private': 'False',
77 'landing_rev': 'tip'})
78
79 response = self.app.get(url(controller='forks', action='forks',
80 repo_name=repo_name))
81
82 response.mustcontain(
83 """<a href="/%s/summary">%s</a>""" % (fork_name, fork_name)
84 )
55 85
56 86 #remove this fork
57 87 response = self.app.delete(url('repo', repo_name=fork_name))
58 88
59 89 def test_z_fork_create(self):
60 90 self.log_user()
61 91 fork_name = HG_FORK
62 92 description = 'fork of vcs test'
63 93 repo_name = HG_REPO
64 94 org_repo = Repository.get_by_repo_name(repo_name)
65 95 response = self.app.post(url(controller='forks', action='fork_create',
66 96 repo_name=repo_name),
67 97 {'repo_name':fork_name,
68 98 'repo_group':'',
69 99 'fork_parent_id':org_repo.repo_id,
70 100 'repo_type':'hg',
71 101 'description':description,
72 'private':'False'})
102 'private':'False',
103 'landing_rev': 'tip'})
73 104
74 105 #test if we have a message that fork is ok
75 self.assertTrue('forked %s repository as %s' \
76 % (repo_name, fork_name) in response.session['flash'][0])
106 self.checkSessionFlash(response,
107 'forked %s repository as %s' % (repo_name, fork_name))
77 108
78 109 #test if the fork was created in the database
79 110 fork_repo = self.Session.query(Repository)\
80 111 .filter(Repository.repo_name == fork_name).one()
81 112
82 113 self.assertEqual(fork_repo.repo_name, fork_name)
83 114 self.assertEqual(fork_repo.fork.repo_name, repo_name)
84 115
85 116 #test if fork is visible in the list ?
86 117 response = response.follow()
87 118
88 # check if fork is marked as fork
89 # wait for cache to expire
90 import time
91 time.sleep(10)
92 119 response = self.app.get(url(controller='summary', action='index',
93 120 repo_name=fork_name))
94 121
95 122 self.assertTrue('Fork of %s' % repo_name in response.body)
96 123
97 124 def test_zz_fork_permission_page(self):
98 125 usr = self.log_user(self.username, self.password)['user_id']
99 126 repo_name = HG_REPO
100 127
101 128 forks = self.Session.query(Repository)\
102 129 .filter(Repository.fork_id != None)\
103 130 .all()
104 131 self.assertEqual(1, len(forks))
105 132
106 133 # set read permissions for this
107 134 RepoModel().grant_user_permission(repo=forks[0],
108 135 user=usr,
109 136 perm='repository.read')
110 137 self.Session.commit()
111 138
112 139 response = self.app.get(url(controller='forks', action='forks',
113 140 repo_name=repo_name))
114 141
115 142 response.mustcontain('<div style="padding:5px 3px 3px 42px;">fork of vcs test</div>')
116 143
117 144 def test_zzz_fork_permission_page(self):
118 145 usr = self.log_user(self.username, self.password)['user_id']
119 146 repo_name = HG_REPO
120 147
121 148 forks = self.Session.query(Repository)\
122 149 .filter(Repository.fork_id != None)\
123 150 .all()
124 151 self.assertEqual(1, len(forks))
125 152
126 153 # set none
127 154 RepoModel().grant_user_permission(repo=forks[0],
128 155 user=usr, perm='repository.none')
129 156 self.Session.commit()
130 157 # fork shouldn't be there
131 158 response = self.app.get(url(controller='forks', action='forks',
132 159 repo_name=repo_name))
133 160 response.mustcontain('There are no forks yet')
@@ -1,39 +1,37
1 import os
1 2 from rhodecode.tests import *
2 import os
3 3 from nose.plugins.skip import SkipTest
4 4
5 5
6 6 class TestSearchController(TestController):
7 7
8 8 def test_index(self):
9 9 self.log_user()
10 10 response = self.app.get(url(controller='search', action='index'))
11 11
12 12 self.assertTrue('class="small" id="q" name="q" type="text"' in
13 13 response.body)
14 14 # Test response...
15 15
16 16 def test_empty_search(self):
17 17 if os.path.isdir(self.index_location):
18 18 raise SkipTest('skipped due to existing index')
19 19 else:
20 20 self.log_user()
21 21 response = self.app.get(url(controller='search', action='index'),
22 22 {'q': HG_REPO})
23 23 self.assertTrue('There is no index to search in. '
24 24 'Please run whoosh indexer' in response.body)
25 25
26 26 def test_normal_search(self):
27 27 self.log_user()
28 28 response = self.app.get(url(controller='search', action='index'),
29 29 {'q': 'def repo'})
30 response.mustcontain('10 results')
31 response.mustcontain('Permission denied')
30 response.mustcontain('39 results')
32 31
33 32 def test_repo_search(self):
34 33 self.log_user()
35 34 response = self.app.get(url(controller='search', action='index'),
36 35 {'q': 'repository:%s def test' % HG_REPO})
37 36
38 37 response.mustcontain('4 results')
39 response.mustcontain('Permission denied')
@@ -1,104 +1,104
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import Repository
3 3 from rhodecode.lib.utils import invalidate_cache
4 4
5 5
6 6 class TestSummaryController(TestController):
7 7
8 8 def test_index(self):
9 9 self.log_user()
10 10 ID = Repository.get_by_repo_name(HG_REPO).repo_id
11 11 response = self.app.get(url(controller='summary',
12 12 action='index',
13 13 repo_name=HG_REPO))
14 14
15 15 #repo type
16 16 response.mustcontain(
17 17 """<img style="margin-bottom:2px" class="icon" """
18 18 """title="Mercurial repository" alt="Mercurial repository" """
19 19 """src="/images/icons/hgicon.png"/>"""
20 20 )
21 21 response.mustcontain(
22 22 """<img style="margin-bottom:2px" class="icon" """
23 23 """title="public repository" alt="public """
24 24 """repository" src="/images/icons/lock_open.png"/>"""
25 25 )
26 26
27 27 #codes stats
28 28 self._enable_stats()
29 29
30 30 invalidate_cache('get_repo_cached_%s' % HG_REPO)
31 31 response = self.app.get(url(controller='summary', action='index',
32 32 repo_name=HG_REPO))
33 33 response.mustcontain(
34 34 """var data = [["py", {"count": 42, "desc": ["Python"]}], """
35 35 """["rst", {"count": 11, "desc": ["Rst"]}], """
36 36 """["sh", {"count": 2, "desc": ["Bash"]}], """
37 37 """["makefile", {"count": 1, "desc": ["Makefile", "Makefile"]}],"""
38 38 """ ["cfg", {"count": 1, "desc": ["Ini"]}], """
39 39 """["css", {"count": 1, "desc": ["Css"]}], """
40 40 """["bat", {"count": 1, "desc": ["Batch"]}]];"""
41 41 )
42 42
43 43 # clone url...
44 44 response.mustcontain("""<input style="width:80%%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="http://test_admin@localhost:80/%s"/>""" % HG_REPO)
45 45 response.mustcontain("""<input style="display:none;width:80%%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="http://test_admin@localhost:80/_%s"/>""" % ID)
46 46
47 47 def test_index_git(self):
48 48 self.log_user()
49 49 ID = Repository.get_by_repo_name(GIT_REPO).repo_id
50 50 response = self.app.get(url(controller='summary',
51 51 action='index',
52 52 repo_name=GIT_REPO))
53 53
54 54 #repo type
55 55 response.mustcontain(
56 56 """<img style="margin-bottom:2px" class="icon" """
57 57 """title="Git repository" alt="Git repository" """
58 58 """src="/images/icons/giticon.png"/>"""
59 59 )
60 60 response.mustcontain(
61 61 """<img style="margin-bottom:2px" class="icon" """
62 62 """title="public repository" alt="public """
63 63 """repository" src="/images/icons/lock_open.png"/>"""
64 64 )
65 65
66 66 # clone url...
67 67 response.mustcontain("""<input style="width:80%%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="http://test_admin@localhost:80/%s"/>""" % GIT_REPO)
68 68 response.mustcontain("""<input style="display:none;width:80%%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="http://test_admin@localhost:80/_%s"/>""" % ID)
69 69
70 70 def test_index_by_id_hg(self):
71 71 self.log_user()
72 72 ID = Repository.get_by_repo_name(HG_REPO).repo_id
73 73 response = self.app.get(url(controller='summary',
74 74 action='index',
75 75 repo_name='_%s' % ID))
76 76
77 77 #repo type
78 78 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
79 79 """title="Mercurial repository" alt="Mercurial """
80 80 """repository" src="/images/icons/hgicon.png"/>""")
81 81 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
82 82 """title="public repository" alt="public """
83 83 """repository" src="/images/icons/lock_open.png"/>""")
84 84
85 85 def test_index_by_id_git(self):
86 86 self.log_user()
87 87 ID = Repository.get_by_repo_name(GIT_REPO).repo_id
88 88 response = self.app.get(url(controller='summary',
89 89 action='index',
90 90 repo_name='_%s' % ID))
91 91
92 92 #repo type
93 93 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
94 94 """title="Git repository" alt="Git """
95 """repository" src="/images/icons/hgicon.png"/>""")
95 """repository" src="/images/icons/giticon.png"/>""")
96 96 response.mustcontain("""<img style="margin-bottom:2px" class="icon" """
97 97 """title="public repository" alt="public """
98 98 """repository" src="/images/icons/lock_open.png"/>""")
99 99
100 100 def _enable_stats(self):
101 101 r = Repository.get_by_repo_name(HG_REPO)
102 102 r.enable_statistics = True
103 103 self.Session.add(r)
104 104 self.Session.commit()
@@ -1,715 +1,716
1 1 import os
2 2 import unittest
3 3 from rhodecode.tests import *
4 4
5 5 from rhodecode.model.repos_group import ReposGroupModel
6 6 from rhodecode.model.repo import RepoModel
7 7 from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \
8 8 UsersGroup, UsersGroupMember, Permission, UsersGroupRepoGroupToPerm,\
9 9 Repository
10 10 from sqlalchemy.exc import IntegrityError
11 11 from rhodecode.model.user import UserModel
12 12
13 13 from rhodecode.model.meta import Session
14 14 from rhodecode.model.notification import NotificationModel
15 15 from rhodecode.model.users_group import UsersGroupModel
16 16 from rhodecode.lib.auth import AuthUser
17 17
18 18
19 19 def _make_group(path, desc='desc', parent_id=None,
20 20 skip_if_exists=False):
21 21
22 22 gr = RepoGroup.get_by_group_name(path)
23 23 if gr and skip_if_exists:
24 24 return gr
25 25
26 26 gr = ReposGroupModel().create(path, desc, parent_id)
27 27 return gr
28 28
29 29
30 30 class TestReposGroups(unittest.TestCase):
31 31
32 32 def setUp(self):
33 33 self.g1 = _make_group('test1', skip_if_exists=True)
34 34 Session.commit()
35 35 self.g2 = _make_group('test2', skip_if_exists=True)
36 36 Session.commit()
37 37 self.g3 = _make_group('test3', skip_if_exists=True)
38 38 Session.commit()
39 39
40 40 def tearDown(self):
41 41 print 'out'
42 42
43 43 def __check_path(self, *path):
44 44 """
45 45 Checks the path for existance !
46 46 """
47 47 path = [TESTS_TMP_PATH] + list(path)
48 48 path = os.path.join(*path)
49 49 return os.path.isdir(path)
50 50
51 51 def _check_folders(self):
52 52 print os.listdir(TESTS_TMP_PATH)
53 53
54 54 def __delete_group(self, id_):
55 55 ReposGroupModel().delete(id_)
56 56
57 57 def __update_group(self, id_, path, desc='desc', parent_id=None):
58 58 form_data = dict(
59 59 group_name=path,
60 60 group_description=desc,
61 61 group_parent_id=parent_id,
62 62 perms_updates=[],
63 63 perms_new=[]
64 64 )
65 65 gr = ReposGroupModel().update(id_, form_data)
66 66 return gr
67 67
68 68 def test_create_group(self):
69 69 g = _make_group('newGroup')
70 70 self.assertEqual(g.full_path, 'newGroup')
71 71
72 72 self.assertTrue(self.__check_path('newGroup'))
73 73
74 74 def test_create_same_name_group(self):
75 75 self.assertRaises(IntegrityError, lambda:_make_group('newGroup'))
76 76 Session.rollback()
77 77
78 78 def test_same_subgroup(self):
79 79 sg1 = _make_group('sub1', parent_id=self.g1.group_id)
80 80 self.assertEqual(sg1.parent_group, self.g1)
81 81 self.assertEqual(sg1.full_path, 'test1/sub1')
82 82 self.assertTrue(self.__check_path('test1', 'sub1'))
83 83
84 84 ssg1 = _make_group('subsub1', parent_id=sg1.group_id)
85 85 self.assertEqual(ssg1.parent_group, sg1)
86 86 self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
87 87 self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
88 88
89 89 def test_remove_group(self):
90 90 sg1 = _make_group('deleteme')
91 91 self.__delete_group(sg1.group_id)
92 92
93 93 self.assertEqual(RepoGroup.get(sg1.group_id), None)
94 94 self.assertFalse(self.__check_path('deteteme'))
95 95
96 96 sg1 = _make_group('deleteme', parent_id=self.g1.group_id)
97 97 self.__delete_group(sg1.group_id)
98 98
99 99 self.assertEqual(RepoGroup.get(sg1.group_id), None)
100 100 self.assertFalse(self.__check_path('test1', 'deteteme'))
101 101
102 102 def test_rename_single_group(self):
103 103 sg1 = _make_group('initial')
104 104
105 105 new_sg1 = self.__update_group(sg1.group_id, 'after')
106 106 self.assertTrue(self.__check_path('after'))
107 107 self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
108 108
109 109 def test_update_group_parent(self):
110 110
111 111 sg1 = _make_group('initial', parent_id=self.g1.group_id)
112 112
113 113 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
114 114 self.assertTrue(self.__check_path('test1', 'after'))
115 115 self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
116 116
117 117 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
118 118 self.assertTrue(self.__check_path('test3', 'after'))
119 119 self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
120 120
121 121 new_sg1 = self.__update_group(sg1.group_id, 'hello')
122 122 self.assertTrue(self.__check_path('hello'))
123 123
124 124 self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
125 125
126 126 def test_subgrouping_with_repo(self):
127 127
128 128 g1 = _make_group('g1')
129 129 g2 = _make_group('g2')
130 130
131 131 # create new repo
132 132 form_data = dict(repo_name='john',
133 133 repo_name_full='john',
134 134 fork_name=None,
135 135 description=None,
136 136 repo_group=None,
137 137 private=False,
138 138 repo_type='hg',
139 clone_uri=None)
139 clone_uri=None,
140 landing_rev='tip')
140 141 cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
141 142 r = RepoModel().create(form_data, cur_user)
142 143
143 144 self.assertEqual(r.repo_name, 'john')
144 145
145 146 # put repo into group
146 147 form_data = form_data
147 148 form_data['repo_group'] = g1.group_id
148 149 form_data['perms_new'] = []
149 150 form_data['perms_updates'] = []
150 151 RepoModel().update(r.repo_name, form_data)
151 152 self.assertEqual(r.repo_name, 'g1/john')
152 153
153 154 self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
154 155 self.assertTrue(self.__check_path('g2', 'g1'))
155 156
156 157 # test repo
157 158 self.assertEqual(r.repo_name, RepoGroup.url_sep().join(['g2', 'g1', r.just_name]))
158 159
159 160 def test_move_to_root(self):
160 161 g1 = _make_group('t11')
161 162 Session.commit()
162 163 g2 = _make_group('t22', parent_id=g1.group_id)
163 164 Session.commit()
164 165
165 166 self.assertEqual(g2.full_path, 't11/t22')
166 167 self.assertTrue(self.__check_path('t11', 't22'))
167 168
168 169 g2 = self.__update_group(g2.group_id, 'g22', parent_id=None)
169 170 Session.commit()
170 171
171 172 self.assertEqual(g2.group_name, 'g22')
172 173 # we moved out group from t1 to '' so it's full path should be 'g2'
173 174 self.assertEqual(g2.full_path, 'g22')
174 175 self.assertFalse(self.__check_path('t11', 't22'))
175 176 self.assertTrue(self.__check_path('g22'))
176 177
177 178
178 179 class TestUser(unittest.TestCase):
179 180 def __init__(self, methodName='runTest'):
180 181 Session.remove()
181 182 super(TestUser, self).__init__(methodName=methodName)
182 183
183 184 def test_create_and_remove(self):
184 185 usr = UserModel().create_or_update(username=u'test_user', password=u'qweqwe',
185 186 email=u'u232@rhodecode.org',
186 187 name=u'u1', lastname=u'u1')
187 188 Session.commit()
188 189 self.assertEqual(User.get_by_username(u'test_user'), usr)
189 190
190 191 # make users group
191 192 users_group = UsersGroupModel().create('some_example_group')
192 193 Session.commit()
193 194
194 195 UsersGroupModel().add_user_to_group(users_group, usr)
195 196 Session.commit()
196 197
197 198 self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group)
198 199 self.assertEqual(UsersGroupMember.query().count(), 1)
199 200 UserModel().delete(usr.user_id)
200 201 Session.commit()
201 202
202 203 self.assertEqual(UsersGroupMember.query().all(), [])
203 204
204 205
205 206 class TestNotifications(unittest.TestCase):
206 207
207 208 def __init__(self, methodName='runTest'):
208 209 Session.remove()
209 210 self.u1 = UserModel().create_or_update(username=u'u1',
210 211 password=u'qweqwe',
211 212 email=u'u1@rhodecode.org',
212 213 name=u'u1', lastname=u'u1')
213 214 Session.commit()
214 215 self.u1 = self.u1.user_id
215 216
216 217 self.u2 = UserModel().create_or_update(username=u'u2',
217 218 password=u'qweqwe',
218 219 email=u'u2@rhodecode.org',
219 220 name=u'u2', lastname=u'u3')
220 221 Session.commit()
221 222 self.u2 = self.u2.user_id
222 223
223 224 self.u3 = UserModel().create_or_update(username=u'u3',
224 225 password=u'qweqwe',
225 226 email=u'u3@rhodecode.org',
226 227 name=u'u3', lastname=u'u3')
227 228 Session.commit()
228 229 self.u3 = self.u3.user_id
229 230
230 231 super(TestNotifications, self).__init__(methodName=methodName)
231 232
232 233 def _clean_notifications(self):
233 234 for n in Notification.query().all():
234 235 Session.delete(n)
235 236
236 237 Session.commit()
237 238 self.assertEqual(Notification.query().all(), [])
238 239
239 240 def tearDown(self):
240 241 self._clean_notifications()
241 242
242 243 def test_create_notification(self):
243 244 self.assertEqual([], Notification.query().all())
244 245 self.assertEqual([], UserNotification.query().all())
245 246
246 247 usrs = [self.u1, self.u2]
247 248 notification = NotificationModel().create(created_by=self.u1,
248 249 subject=u'subj', body=u'hi there',
249 250 recipients=usrs)
250 251 Session.commit()
251 252 u1 = User.get(self.u1)
252 253 u2 = User.get(self.u2)
253 254 u3 = User.get(self.u3)
254 255 notifications = Notification.query().all()
255 256 self.assertEqual(len(notifications), 1)
256 257
257 258 unotification = UserNotification.query()\
258 259 .filter(UserNotification.notification == notification).all()
259 260
260 261 self.assertEqual(notifications[0].recipients, [u1, u2])
261 262 self.assertEqual(notification.notification_id,
262 263 notifications[0].notification_id)
263 264 self.assertEqual(len(unotification), len(usrs))
264 265 self.assertEqual([x.user.user_id for x in unotification], usrs)
265 266
266 267 def test_user_notifications(self):
267 268 self.assertEqual([], Notification.query().all())
268 269 self.assertEqual([], UserNotification.query().all())
269 270
270 271 notification1 = NotificationModel().create(created_by=self.u1,
271 272 subject=u'subj', body=u'hi there1',
272 273 recipients=[self.u3])
273 274 Session.commit()
274 275 notification2 = NotificationModel().create(created_by=self.u1,
275 276 subject=u'subj', body=u'hi there2',
276 277 recipients=[self.u3])
277 278 Session.commit()
278 279 u3 = Session.query(User).get(self.u3)
279 280
280 281 self.assertEqual(sorted([x.notification for x in u3.notifications]),
281 282 sorted([notification2, notification1]))
282 283
283 284 def test_delete_notifications(self):
284 285 self.assertEqual([], Notification.query().all())
285 286 self.assertEqual([], UserNotification.query().all())
286 287
287 288 notification = NotificationModel().create(created_by=self.u1,
288 289 subject=u'title', body=u'hi there3',
289 290 recipients=[self.u3, self.u1, self.u2])
290 291 Session.commit()
291 292 notifications = Notification.query().all()
292 293 self.assertTrue(notification in notifications)
293 294
294 295 Notification.delete(notification.notification_id)
295 296 Session.commit()
296 297
297 298 notifications = Notification.query().all()
298 299 self.assertFalse(notification in notifications)
299 300
300 301 un = UserNotification.query().filter(UserNotification.notification
301 302 == notification).all()
302 303 self.assertEqual(un, [])
303 304
304 305 def test_delete_association(self):
305 306
306 307 self.assertEqual([], Notification.query().all())
307 308 self.assertEqual([], UserNotification.query().all())
308 309
309 310 notification = NotificationModel().create(created_by=self.u1,
310 311 subject=u'title', body=u'hi there3',
311 312 recipients=[self.u3, self.u1, self.u2])
312 313 Session.commit()
313 314
314 315 unotification = UserNotification.query()\
315 316 .filter(UserNotification.notification ==
316 317 notification)\
317 318 .filter(UserNotification.user_id == self.u3)\
318 319 .scalar()
319 320
320 321 self.assertEqual(unotification.user_id, self.u3)
321 322
322 323 NotificationModel().delete(self.u3,
323 324 notification.notification_id)
324 325 Session.commit()
325 326
326 327 u3notification = UserNotification.query()\
327 328 .filter(UserNotification.notification ==
328 329 notification)\
329 330 .filter(UserNotification.user_id == self.u3)\
330 331 .scalar()
331 332
332 333 self.assertEqual(u3notification, None)
333 334
334 335 # notification object is still there
335 336 self.assertEqual(Notification.query().all(), [notification])
336 337
337 338 #u1 and u2 still have assignments
338 339 u1notification = UserNotification.query()\
339 340 .filter(UserNotification.notification ==
340 341 notification)\
341 342 .filter(UserNotification.user_id == self.u1)\
342 343 .scalar()
343 344 self.assertNotEqual(u1notification, None)
344 345 u2notification = UserNotification.query()\
345 346 .filter(UserNotification.notification ==
346 347 notification)\
347 348 .filter(UserNotification.user_id == self.u2)\
348 349 .scalar()
349 350 self.assertNotEqual(u2notification, None)
350 351
351 352 def test_notification_counter(self):
352 353 self._clean_notifications()
353 354 self.assertEqual([], Notification.query().all())
354 355 self.assertEqual([], UserNotification.query().all())
355 356
356 357 NotificationModel().create(created_by=self.u1,
357 358 subject=u'title', body=u'hi there_delete',
358 359 recipients=[self.u3, self.u1])
359 360 Session.commit()
360 361
361 362 self.assertEqual(NotificationModel()
362 363 .get_unread_cnt_for_user(self.u1), 1)
363 364 self.assertEqual(NotificationModel()
364 365 .get_unread_cnt_for_user(self.u2), 0)
365 366 self.assertEqual(NotificationModel()
366 367 .get_unread_cnt_for_user(self.u3), 1)
367 368
368 369 notification = NotificationModel().create(created_by=self.u1,
369 370 subject=u'title', body=u'hi there3',
370 371 recipients=[self.u3, self.u1, self.u2])
371 372 Session.commit()
372 373
373 374 self.assertEqual(NotificationModel()
374 375 .get_unread_cnt_for_user(self.u1), 2)
375 376 self.assertEqual(NotificationModel()
376 377 .get_unread_cnt_for_user(self.u2), 1)
377 378 self.assertEqual(NotificationModel()
378 379 .get_unread_cnt_for_user(self.u3), 2)
379 380
380 381
381 382 class TestUsers(unittest.TestCase):
382 383
383 384 def __init__(self, methodName='runTest'):
384 385 super(TestUsers, self).__init__(methodName=methodName)
385 386
386 387 def setUp(self):
387 388 self.u1 = UserModel().create_or_update(username=u'u1',
388 389 password=u'qweqwe',
389 390 email=u'u1@rhodecode.org',
390 391 name=u'u1', lastname=u'u1')
391 392
392 393 def tearDown(self):
393 394 perm = Permission.query().all()
394 395 for p in perm:
395 396 UserModel().revoke_perm(self.u1, p)
396 397
397 398 UserModel().delete(self.u1)
398 399 Session.commit()
399 400
400 401 def test_add_perm(self):
401 402 perm = Permission.query().all()[0]
402 403 UserModel().grant_perm(self.u1, perm)
403 404 Session.commit()
404 405 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
405 406
406 407 def test_has_perm(self):
407 408 perm = Permission.query().all()
408 409 for p in perm:
409 410 has_p = UserModel().has_perm(self.u1, p)
410 411 self.assertEqual(False, has_p)
411 412
412 413 def test_revoke_perm(self):
413 414 perm = Permission.query().all()[0]
414 415 UserModel().grant_perm(self.u1, perm)
415 416 Session.commit()
416 417 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
417 418
418 419 #revoke
419 420 UserModel().revoke_perm(self.u1, perm)
420 421 Session.commit()
421 422 self.assertEqual(UserModel().has_perm(self.u1, perm), False)
422 423
423 424
424 425 class TestPermissions(unittest.TestCase):
425 426 def __init__(self, methodName='runTest'):
426 427 super(TestPermissions, self).__init__(methodName=methodName)
427 428
428 429 def setUp(self):
429 430 self.u1 = UserModel().create_or_update(
430 431 username=u'u1', password=u'qweqwe',
431 432 email=u'u1@rhodecode.org', name=u'u1', lastname=u'u1'
432 433 )
433 434 self.u2 = UserModel().create_or_update(
434 435 username=u'u2', password=u'qweqwe',
435 436 email=u'u2@rhodecode.org', name=u'u2', lastname=u'u2'
436 437 )
437 438 self.anon = User.get_by_username('default')
438 439 self.a1 = UserModel().create_or_update(
439 440 username=u'a1', password=u'qweqwe',
440 441 email=u'a1@rhodecode.org', name=u'a1', lastname=u'a1', admin=True
441 442 )
442 443 Session.commit()
443 444
444 445 def tearDown(self):
445 446 if hasattr(self, 'test_repo'):
446 447 RepoModel().delete(repo=self.test_repo)
447 448 UserModel().delete(self.u1)
448 449 UserModel().delete(self.u2)
449 450 UserModel().delete(self.a1)
450 451 if hasattr(self, 'g1'):
451 452 ReposGroupModel().delete(self.g1.group_id)
452 453 if hasattr(self, 'g2'):
453 454 ReposGroupModel().delete(self.g2.group_id)
454 455
455 456 if hasattr(self, 'ug1'):
456 457 UsersGroupModel().delete(self.ug1, force=True)
457 458
458 459 Session.commit()
459 460
460 461 def test_default_perms_set(self):
461 462 u1_auth = AuthUser(user_id=self.u1.user_id)
462 463 perms = {
463 464 'repositories_groups': {},
464 465 'global': set([u'hg.create.repository', u'repository.read',
465 466 u'hg.register.manual_activate']),
466 467 'repositories': {u'vcs_test_hg': u'repository.read'}
467 468 }
468 469 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
469 470 perms['repositories'][HG_REPO])
470 471 new_perm = 'repository.write'
471 472 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
472 473 Session.commit()
473 474
474 475 u1_auth = AuthUser(user_id=self.u1.user_id)
475 476 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], new_perm)
476 477
477 478 def test_default_admin_perms_set(self):
478 479 a1_auth = AuthUser(user_id=self.a1.user_id)
479 480 perms = {
480 481 'repositories_groups': {},
481 482 'global': set([u'hg.admin']),
482 483 'repositories': {u'vcs_test_hg': u'repository.admin'}
483 484 }
484 485 self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
485 486 perms['repositories'][HG_REPO])
486 487 new_perm = 'repository.write'
487 488 RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1, perm=new_perm)
488 489 Session.commit()
489 490 # cannot really downgrade admins permissions !? they still get's set as
490 491 # admin !
491 492 u1_auth = AuthUser(user_id=self.a1.user_id)
492 493 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
493 494 perms['repositories'][HG_REPO])
494 495
495 496 def test_default_group_perms(self):
496 497 self.g1 = _make_group('test1', skip_if_exists=True)
497 498 self.g2 = _make_group('test2', skip_if_exists=True)
498 499 u1_auth = AuthUser(user_id=self.u1.user_id)
499 500 perms = {
500 501 'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'},
501 502 'global': set([u'hg.create.repository', u'repository.read', u'hg.register.manual_activate']),
502 503 'repositories': {u'vcs_test_hg': u'repository.read'}
503 504 }
504 505 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
505 506 perms['repositories'][HG_REPO])
506 507 self.assertEqual(u1_auth.permissions['repositories_groups'],
507 508 perms['repositories_groups'])
508 509
509 510 def test_default_admin_group_perms(self):
510 511 self.g1 = _make_group('test1', skip_if_exists=True)
511 512 self.g2 = _make_group('test2', skip_if_exists=True)
512 513 a1_auth = AuthUser(user_id=self.a1.user_id)
513 514 perms = {
514 515 'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'},
515 516 'global': set(['hg.admin']),
516 517 'repositories': {u'vcs_test_hg': 'repository.admin'}
517 518 }
518 519
519 520 self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
520 521 perms['repositories'][HG_REPO])
521 522 self.assertEqual(a1_auth.permissions['repositories_groups'],
522 523 perms['repositories_groups'])
523 524
524 525 def test_propagated_permission_from_users_group(self):
525 526 # make group
526 527 self.ug1 = UsersGroupModel().create('G1')
527 528 # add user to group
528 529 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
529 530
530 531 # set permission to lower
531 532 new_perm = 'repository.none'
532 533 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
533 534 Session.commit()
534 535 u1_auth = AuthUser(user_id=self.u1.user_id)
535 536 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
536 537 new_perm)
537 538
538 539 # grant perm for group this should override permission from user
539 540 new_perm = 'repository.write'
540 541 RepoModel().grant_users_group_permission(repo=HG_REPO,
541 542 group_name=self.ug1,
542 543 perm=new_perm)
543 544 # check perms
544 545 u1_auth = AuthUser(user_id=self.u1.user_id)
545 546 perms = {
546 547 'repositories_groups': {},
547 548 'global': set([u'hg.create.repository', u'repository.read',
548 549 u'hg.register.manual_activate']),
549 550 'repositories': {u'vcs_test_hg': u'repository.read'}
550 551 }
551 552 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
552 553 new_perm)
553 554 self.assertEqual(u1_auth.permissions['repositories_groups'],
554 555 perms['repositories_groups'])
555 556
556 557 def test_propagated_permission_from_users_group_lower_weight(self):
557 558 # make group
558 559 self.ug1 = UsersGroupModel().create('G1')
559 560 # add user to group
560 561 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
561 562
562 563 # set permission to lower
563 564 new_perm_h = 'repository.write'
564 565 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
565 566 perm=new_perm_h)
566 567 Session.commit()
567 568 u1_auth = AuthUser(user_id=self.u1.user_id)
568 569 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
569 570 new_perm_h)
570 571
571 572 # grant perm for group this should NOT override permission from user
572 573 # since it's lower than granted
573 574 new_perm_l = 'repository.read'
574 575 RepoModel().grant_users_group_permission(repo=HG_REPO,
575 576 group_name=self.ug1,
576 577 perm=new_perm_l)
577 578 # check perms
578 579 u1_auth = AuthUser(user_id=self.u1.user_id)
579 580 perms = {
580 581 'repositories_groups': {},
581 582 'global': set([u'hg.create.repository', u'repository.read',
582 583 u'hg.register.manual_activate']),
583 584 'repositories': {u'vcs_test_hg': u'repository.write'}
584 585 }
585 586 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
586 587 new_perm_h)
587 588 self.assertEqual(u1_auth.permissions['repositories_groups'],
588 589 perms['repositories_groups'])
589 590
590 591 def test_repo_in_group_permissions(self):
591 592 self.g1 = _make_group('group1', skip_if_exists=True)
592 593 self.g2 = _make_group('group2', skip_if_exists=True)
593 594 Session.commit()
594 595 # both perms should be read !
595 596 u1_auth = AuthUser(user_id=self.u1.user_id)
596 597 self.assertEqual(u1_auth.permissions['repositories_groups'],
597 598 {u'group1': u'group.read', u'group2': u'group.read'})
598 599
599 600 a1_auth = AuthUser(user_id=self.anon.user_id)
600 601 self.assertEqual(a1_auth.permissions['repositories_groups'],
601 602 {u'group1': u'group.read', u'group2': u'group.read'})
602 603
603 604 #Change perms to none for both groups
604 605 ReposGroupModel().grant_user_permission(repos_group=self.g1,
605 606 user=self.anon,
606 607 perm='group.none')
607 608 ReposGroupModel().grant_user_permission(repos_group=self.g2,
608 609 user=self.anon,
609 610 perm='group.none')
610 611
611
612 612 u1_auth = AuthUser(user_id=self.u1.user_id)
613 613 self.assertEqual(u1_auth.permissions['repositories_groups'],
614 614 {u'group1': u'group.none', u'group2': u'group.none'})
615 615
616 616 a1_auth = AuthUser(user_id=self.anon.user_id)
617 617 self.assertEqual(a1_auth.permissions['repositories_groups'],
618 618 {u'group1': u'group.none', u'group2': u'group.none'})
619 619
620 620 # add repo to group
621 621 form_data = {
622 'repo_name':HG_REPO,
623 'repo_name_full':RepoGroup.url_sep().join([self.g1.group_name,HG_REPO]),
624 'repo_type':'hg',
625 'clone_uri':'',
626 'repo_group':self.g1.group_id,
627 'description':'desc',
628 'private':False
622 'repo_name': HG_REPO,
623 'repo_name_full': RepoGroup.url_sep().join([self.g1.group_name,HG_REPO]),
624 'repo_type': 'hg',
625 'clone_uri': '',
626 'repo_group': self.g1.group_id,
627 'description': 'desc',
628 'private': False,
629 'landing_rev': 'tip'
629 630 }
630 631 self.test_repo = RepoModel().create(form_data, cur_user=self.u1)
631 632 Session.commit()
632 633
633 634 u1_auth = AuthUser(user_id=self.u1.user_id)
634 635 self.assertEqual(u1_auth.permissions['repositories_groups'],
635 636 {u'group1': u'group.none', u'group2': u'group.none'})
636 637
637 638 a1_auth = AuthUser(user_id=self.anon.user_id)
638 639 self.assertEqual(a1_auth.permissions['repositories_groups'],
639 640 {u'group1': u'group.none', u'group2': u'group.none'})
640 641
641 642 #grant permission for u2 !
642 643 ReposGroupModel().grant_user_permission(repos_group=self.g1,
643 644 user=self.u2,
644 645 perm='group.read')
645 646 ReposGroupModel().grant_user_permission(repos_group=self.g2,
646 647 user=self.u2,
647 648 perm='group.read')
648 649 Session.commit()
649 650 self.assertNotEqual(self.u1, self.u2)
650 651 #u1 and anon should have not change perms while u2 should !
651 652 u1_auth = AuthUser(user_id=self.u1.user_id)
652 653 self.assertEqual(u1_auth.permissions['repositories_groups'],
653 654 {u'group1': u'group.none', u'group2': u'group.none'})
654 655
655 656 u2_auth = AuthUser(user_id=self.u2.user_id)
656 657 self.assertEqual(u2_auth.permissions['repositories_groups'],
657 658 {u'group1': u'group.read', u'group2': u'group.read'})
658 659
659 660 a1_auth = AuthUser(user_id=self.anon.user_id)
660 661 self.assertEqual(a1_auth.permissions['repositories_groups'],
661 662 {u'group1': u'group.none', u'group2': u'group.none'})
662 663
663 664 def test_repo_group_user_as_user_group_member(self):
664 665 # create Group1
665 666 self.g1 = _make_group('group1', skip_if_exists=True)
666 667 Session.commit()
667 668 a1_auth = AuthUser(user_id=self.anon.user_id)
668 669
669 670 self.assertEqual(a1_auth.permissions['repositories_groups'],
670 671 {u'group1': u'group.read'})
671 672
672 673 # set default permission to none
673 674 ReposGroupModel().grant_user_permission(repos_group=self.g1,
674 675 user=self.anon,
675 676 perm='group.none')
676 677 # make group
677 678 self.ug1 = UsersGroupModel().create('G1')
678 679 # add user to group
679 680 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
680 681 Session.commit()
681 682
682 683 # check if user is in the group
683 684 membrs = [x.user_id for x in UsersGroupModel().get(self.ug1.users_group_id).members]
684 685 self.assertEqual(membrs, [self.u1.user_id])
685 686 # add some user to that group
686 687
687 688 # check his permissions
688 689 a1_auth = AuthUser(user_id=self.anon.user_id)
689 690 self.assertEqual(a1_auth.permissions['repositories_groups'],
690 691 {u'group1': u'group.none'})
691 692
692 693 u1_auth = AuthUser(user_id=self.u1.user_id)
693 694 self.assertEqual(u1_auth.permissions['repositories_groups'],
694 695 {u'group1': u'group.none'})
695 696
696 697 # grant ug1 read permissions for
697 698 ReposGroupModel().grant_users_group_permission(repos_group=self.g1,
698 699 group_name=self.ug1,
699 700 perm='group.read')
700 701 Session.commit()
701 702 # check if the
702 703 obj = Session.query(UsersGroupRepoGroupToPerm)\
703 704 .filter(UsersGroupRepoGroupToPerm.group == self.g1)\
704 705 .filter(UsersGroupRepoGroupToPerm.users_group == self.ug1)\
705 706 .scalar()
706 707 self.assertEqual(obj.permission.permission_name, 'group.read')
707 708
708 709 a1_auth = AuthUser(user_id=self.anon.user_id)
709 710
710 711 self.assertEqual(a1_auth.permissions['repositories_groups'],
711 712 {u'group1': u'group.none'})
712 713
713 714 u1_auth = AuthUser(user_id=self.u1.user_id)
714 715 self.assertEqual(u1_auth.permissions['repositories_groups'],
715 716 {u'group1': u'group.read'})
@@ -1,56 +1,56
1 1 """
2 2 Unit tests for vcs_ library.
3 3
4 4 In order to run tests we need to prepare our environment first. Tests would be
5 5 run for each engine listed at ``conf.SCM_TESTS`` - keys are aliases from
6 6 ``vcs.backends.BACKENDS``.
7 7
8 8 For each SCM we run tests for, we need some repository. We would use
9 9 repositories location from system environment variables or test suite defaults
10 10 - see ``conf`` module for more detail. We simply try to check if repository at
11 11 certain location exists, if not we would try to fetch them. At ``test_vcs`` or
12 12 ``test_common`` we run unit tests common for each repository type and for
13 13 example specific mercurial tests are located at ``test_hg`` module.
14 14
15 15 Oh, and tests are run with ``unittest.collector`` wrapped by ``collector``
16 16 function at ``tests/__init__.py``.
17 17
18 18 .. _vcs: http://bitbucket.org/marcinkuzminski/vcs
19 19 .. _unittest: http://pypi.python.org/pypi/unittest
20 20
21 21 """
22 22 import os
23 23 from rhodecode.lib import vcs
24 24 from rhodecode.lib.vcs.utils.compat import unittest
25 25 from rhodecode.tests import *
26 26 from utils import VCSTestError, SCMFetcher
27 27
28 28
29 29 def setup_package():
30 30 """
31 31 Prepares whole package for tests which mainly means it would try to fetch
32 32 test repositories or use already existing ones.
33 33 """
34 34 fetchers = {
35 35 'hg': {
36 36 'alias': 'hg',
37 37 'test_repo_path': TEST_HG_REPO,
38 38 'remote_repo': HG_REMOTE_REPO,
39 39 'clone_cmd': 'hg clone',
40 40 },
41 41 'git': {
42 42 'alias': 'git',
43 43 'test_repo_path': TEST_GIT_REPO,
44 44 'remote_repo': GIT_REMOTE_REPO,
45 45 'clone_cmd': 'git clone --bare',
46 46 },
47 47 }
48 48 try:
49 49 for scm, fetcher_info in fetchers.items():
50 50 fetcher = SCMFetcher(**fetcher_info)
51 51 fetcher.setup()
52 52 except VCSTestError, err:
53 53 raise RuntimeError(str(err))
54 54
55 start_dir = os.path.abspath(os.path.dirname(__file__))
56 unittest.defaultTestLoader.discover(start_dir)
55 #start_dir = os.path.abspath(os.path.dirname(__file__))
56 #unittest.defaultTestLoader.discover(start_dir)
@@ -1,61 +1,62
1 1 """
2 2 Unit tests configuration module for vcs.
3 3 """
4
4 5 import os
5 6 import time
6 7 import hashlib
7 8 import tempfile
8 9 import datetime
9
10 from rhodecode.tests import *
10 11 from utils import get_normalized_path
11 12 from os.path import join as jn
12 13
13 __all__ = (
14 'TEST_HG_REPO', 'TEST_GIT_REPO', 'HG_REMOTE_REPO', 'GIT_REMOTE_REPO',
15 'SCM_TESTS',
16 )
17
18 SCM_TESTS = ['hg', 'git']
19 uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
20
14 TEST_TMP_PATH = TESTS_TMP_PATH
15 #__all__ = (
16 # 'TEST_HG_REPO', 'TEST_GIT_REPO', 'HG_REMOTE_REPO', 'GIT_REMOTE_REPO',
17 # 'SCM_TESTS',
18 #)
19 #
20 #SCM_TESTS = ['hg', 'git']
21 #uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
22 #
21 23 THIS = os.path.abspath(os.path.dirname(__file__))
22
23 GIT_REMOTE_REPO = 'git://github.com/codeinn/vcs.git'
24
25 TEST_TMP_PATH = os.environ.get('VCS_TEST_ROOT', '/tmp')
26 TEST_GIT_REPO = os.environ.get('VCS_TEST_GIT_REPO',
27 jn(TEST_TMP_PATH, 'vcs-git'))
28 TEST_GIT_REPO_CLONE = os.environ.get('VCS_TEST_GIT_REPO_CLONE',
29 jn(TEST_TMP_PATH, 'vcsgitclone%s' % uniq_suffix))
30 TEST_GIT_REPO_PULL = os.environ.get('VCS_TEST_GIT_REPO_PULL',
31 jn(TEST_TMP_PATH, 'vcsgitpull%s' % uniq_suffix))
32
33 HG_REMOTE_REPO = 'http://bitbucket.org/marcinkuzminski/vcs'
34 TEST_HG_REPO = os.environ.get('VCS_TEST_HG_REPO',
35 jn(TEST_TMP_PATH, 'vcs-hg'))
36 TEST_HG_REPO_CLONE = os.environ.get('VCS_TEST_HG_REPO_CLONE',
37 jn(TEST_TMP_PATH, 'vcshgclone%s' % uniq_suffix))
38 TEST_HG_REPO_PULL = os.environ.get('VCS_TEST_HG_REPO_PULL',
39 jn(TEST_TMP_PATH, 'vcshgpull%s' % uniq_suffix))
40
41 TEST_DIR = os.environ.get('VCS_TEST_ROOT', tempfile.gettempdir())
42 TEST_REPO_PREFIX = 'vcs-test'
43
44
45 def get_new_dir(title):
46 """
47 Returns always new directory path.
48 """
49 name = TEST_REPO_PREFIX
50 if title:
51 name = '-'.join((name, title))
52 hex = hashlib.sha1(str(time.time())).hexdigest()
53 name = '-'.join((name, hex))
54 path = os.path.join(TEST_DIR, name)
55 return get_normalized_path(path)
56
24 #
25 #GIT_REMOTE_REPO = 'git://github.com/codeinn/vcs.git'
26 #
27 #TEST_TMP_PATH = os.environ.get('VCS_TEST_ROOT', '/tmp')
28 #TEST_GIT_REPO = os.environ.get('VCS_TEST_GIT_REPO',
29 # jn(TEST_TMP_PATH, 'vcs-git'))
30 #TEST_GIT_REPO_CLONE = os.environ.get('VCS_TEST_GIT_REPO_CLONE',
31 # jn(TEST_TMP_PATH, 'vcsgitclone%s' % uniq_suffix))
32 #TEST_GIT_REPO_PULL = os.environ.get('VCS_TEST_GIT_REPO_PULL',
33 # jn(TEST_TMP_PATH, 'vcsgitpull%s' % uniq_suffix))
34 #
35 #HG_REMOTE_REPO = 'http://bitbucket.org/marcinkuzminski/vcs'
36 #TEST_HG_REPO = os.environ.get('VCS_TEST_HG_REPO',
37 # jn(TEST_TMP_PATH, 'vcs-hg'))
38 #TEST_HG_REPO_CLONE = os.environ.get('VCS_TEST_HG_REPO_CLONE',
39 # jn(TEST_TMP_PATH, 'vcshgclone%s' % uniq_suffix))
40 #TEST_HG_REPO_PULL = os.environ.get('VCS_TEST_HG_REPO_PULL',
41 # jn(TEST_TMP_PATH, 'vcshgpull%s' % uniq_suffix))
42 #
43 #TEST_DIR = os.environ.get('VCS_TEST_ROOT', tempfile.gettempdir())
44 #TEST_REPO_PREFIX = 'vcs-test'
45 #
46 #
47 #def get_new_dir(title):
48 # """
49 # Returns always new directory path.
50 # """
51 # name = TEST_REPO_PREFIX
52 # if title:
53 # name = '-'.join((name, title))
54 # hex = hashlib.sha1(str(time.time())).hexdigest()
55 # name = '-'.join((name, hex))
56 # path = os.path.join(TEST_DIR, name)
57 # return get_normalized_path(path)
57 58
58 59 PACKAGE_DIR = os.path.abspath(os.path.join(
59 60 os.path.dirname(__file__), '..'))
60 61
61 62 TEST_USER_CONFIG_FILE = jn(THIS, 'aconfig')
@@ -1,561 +1,557
1 1 from __future__ import with_statement
2 2
3 3 import os
4 4 from rhodecode.lib.vcs.backends.hg import MercurialRepository, MercurialChangeset
5 5 from rhodecode.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError
6 6 from rhodecode.lib.vcs.nodes import NodeKind, NodeState
7 7 from conf import PACKAGE_DIR, TEST_HG_REPO, TEST_HG_REPO_CLONE, \
8 8 TEST_HG_REPO_PULL
9 9 from rhodecode.lib.vcs.utils.compat import unittest
10 10
11 11
12 12 # Use only clean mercurial's ui
13 13 import mercurial.scmutil
14 14 mercurial.scmutil.rcpath()
15 15 if mercurial.scmutil._rcpath:
16 16 mercurial.scmutil._rcpath = mercurial.scmutil._rcpath[:1]
17 17
18 18
19 19 class MercurialRepositoryTest(unittest.TestCase):
20 20
21 21 def __check_for_existing_repo(self):
22 22 if os.path.exists(TEST_HG_REPO_CLONE):
23 23 self.fail('Cannot test mercurial clone repo as location %s already '
24 24 'exists. You should manually remove it first.'
25 25 % TEST_HG_REPO_CLONE)
26 26
27 27 def setUp(self):
28 28 self.repo = MercurialRepository(TEST_HG_REPO)
29 29
30 30 def test_wrong_repo_path(self):
31 31 wrong_repo_path = '/tmp/errorrepo'
32 32 self.assertRaises(RepositoryError, MercurialRepository, wrong_repo_path)
33 33
34 34 def test_unicode_path_repo(self):
35 35 self.assertRaises(VCSError,lambda:MercurialRepository(u'iShouldFail'))
36 36
37 37 def test_repo_clone(self):
38 38 self.__check_for_existing_repo()
39 39 repo = MercurialRepository(TEST_HG_REPO)
40 40 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE,
41 41 src_url=TEST_HG_REPO, update_after_clone=True)
42 42 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
43 43 # Checking hashes of changesets should be enough
44 44 for changeset in repo.get_changesets():
45 45 raw_id = changeset.raw_id
46 46 self.assertEqual(raw_id, repo_clone.get_changeset(raw_id).raw_id)
47 47
48 48 def test_repo_clone_with_update(self):
49 49 repo = MercurialRepository(TEST_HG_REPO)
50 50 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_w_update',
51 51 src_url=TEST_HG_REPO, update_after_clone=True)
52 52 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
53 53
54 54 #check if current workdir was updated
55 55 self.assertEqual(os.path.isfile(os.path.join(TEST_HG_REPO_CLONE \
56 56 + '_w_update',
57 57 'MANIFEST.in')), True,)
58 58
59 59 def test_repo_clone_without_update(self):
60 60 repo = MercurialRepository(TEST_HG_REPO)
61 61 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_wo_update',
62 62 src_url=TEST_HG_REPO, update_after_clone=False)
63 63 self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
64 64 self.assertEqual(os.path.isfile(os.path.join(TEST_HG_REPO_CLONE \
65 65 + '_wo_update',
66 66 'MANIFEST.in')), False,)
67 67
68 68 def test_pull(self):
69 69 if os.path.exists(TEST_HG_REPO_PULL):
70 70 self.fail('Cannot test mercurial pull command as location %s '
71 71 'already exists. You should manually remove it first'
72 72 % TEST_HG_REPO_PULL)
73 73 repo_new = MercurialRepository(TEST_HG_REPO_PULL, create=True)
74 74 self.assertTrue(len(self.repo.revisions) > len(repo_new.revisions))
75 75
76 76 repo_new.pull(self.repo.path)
77 77 repo_new = MercurialRepository(TEST_HG_REPO_PULL)
78 78 self.assertTrue(len(self.repo.revisions) == len(repo_new.revisions))
79 79
80 80 def test_revisions(self):
81 81 # there are 21 revisions at bitbucket now
82 82 # so we can assume they would be available from now on
83 83 subset = set(['b986218ba1c9b0d6a259fac9b050b1724ed8e545',
84 84 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
85 85 '6cba7170863a2411822803fa77a0a264f1310b35',
86 86 '56349e29c2af3ac913b28bde9a2c6154436e615b',
87 87 '2dda4e345facb0ccff1a191052dd1606dba6781d',
88 88 '6fff84722075f1607a30f436523403845f84cd9e',
89 89 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
90 90 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
91 91 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
92 92 'be90031137367893f1c406e0a8683010fd115b79',
93 93 'db8e58be770518cbb2b1cdfa69146e47cd481481',
94 94 '84478366594b424af694a6c784cb991a16b87c21',
95 95 '17f8e105dddb9f339600389c6dc7175d395a535c',
96 96 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
97 97 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
98 98 '786facd2c61deb9cf91e9534735124fb8fc11842',
99 99 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
100 100 'aa6a0de05b7612707db567078e130a6cd114a9a7',
101 101 'eada5a770da98ab0dd7325e29d00e0714f228d09'
102 102 ])
103 103 self.assertTrue(subset.issubset(set(self.repo.revisions)))
104 104
105 105
106 106 # check if we have the proper order of revisions
107 107 org = ['b986218ba1c9b0d6a259fac9b050b1724ed8e545',
108 108 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
109 109 '6cba7170863a2411822803fa77a0a264f1310b35',
110 110 '56349e29c2af3ac913b28bde9a2c6154436e615b',
111 111 '2dda4e345facb0ccff1a191052dd1606dba6781d',
112 112 '6fff84722075f1607a30f436523403845f84cd9e',
113 113 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
114 114 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
115 115 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
116 116 'be90031137367893f1c406e0a8683010fd115b79',
117 117 'db8e58be770518cbb2b1cdfa69146e47cd481481',
118 118 '84478366594b424af694a6c784cb991a16b87c21',
119 119 '17f8e105dddb9f339600389c6dc7175d395a535c',
120 120 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
121 121 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
122 122 '786facd2c61deb9cf91e9534735124fb8fc11842',
123 123 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
124 124 'aa6a0de05b7612707db567078e130a6cd114a9a7',
125 125 'eada5a770da98ab0dd7325e29d00e0714f228d09',
126 126 '2c1885c735575ca478bf9e17b0029dca68824458',
127 127 'd9bcd465040bf869799b09ad732c04e0eea99fe9',
128 128 '469e9c847fe1f6f7a697b8b25b4bc5b48780c1a7',
129 129 '4fb8326d78e5120da2c7468dcf7098997be385da',
130 130 '62b4a097164940bd66030c4db51687f3ec035eed',
131 131 '536c1a19428381cfea92ac44985304f6a8049569',
132 132 '965e8ab3c44b070cdaa5bf727ddef0ada980ecc4',
133 133 '9bb326a04ae5d98d437dece54be04f830cf1edd9',
134 134 'f8940bcb890a98c4702319fbe36db75ea309b475',
135 135 'ff5ab059786ebc7411e559a2cc309dfae3625a3b',
136 136 '6b6ad5f82ad5bb6190037671bd254bd4e1f4bf08',
137 137 'ee87846a61c12153b51543bf860e1026c6d3dcba', ]
138 138 self.assertEqual(org, self.repo.revisions[:31])
139 139
140 140 def test_iter_slice(self):
141 141 sliced = list(self.repo[:10])
142 142 itered = list(self.repo)[:10]
143 143 self.assertEqual(sliced, itered)
144 144
145 145 def test_slicing(self):
146 146 #4 1 5 10 95
147 147 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
148 148 (10, 20, 10), (5, 100, 95)]:
149 149 revs = list(self.repo[sfrom:sto])
150 150 self.assertEqual(len(revs), size)
151 151 self.assertEqual(revs[0], self.repo.get_changeset(sfrom))
152 152 self.assertEqual(revs[-1], self.repo.get_changeset(sto - 1))
153 153
154 154 def test_branches(self):
155 155 # TODO: Need more tests here
156 156
157 157 #active branches
158 158 self.assertTrue('default' in self.repo.branches)
159
160 #closed branches
161 self.assertFalse('web' in self.repo.branches)
162 self.assertFalse('git' in self.repo.branches)
159 self.assertTrue('git' in self.repo.branches)
163 160
164 161 # closed
165 self.assertTrue('workdir' in self.repo._get_branches(closed=True))
166 self.assertTrue('webvcs' in self.repo._get_branches(closed=True))
162 self.assertTrue('web' in self.repo._get_branches(closed=True))
167 163
168 164 for name, id in self.repo.branches.items():
169 165 self.assertTrue(isinstance(
170 166 self.repo.get_changeset(id), MercurialChangeset))
171 167
172 168 def test_tip_in_tags(self):
173 169 # tip is always a tag
174 170 self.assertIn('tip', self.repo.tags)
175 171
176 172 def test_tip_changeset_in_tags(self):
177 173 tip = self.repo.get_changeset()
178 174 self.assertEqual(self.repo.tags['tip'], tip.raw_id)
179 175
180 176 def test_initial_changeset(self):
181 177
182 178 init_chset = self.repo.get_changeset(0)
183 179 self.assertEqual(init_chset.message, 'initial import')
184 180 self.assertEqual(init_chset.author,
185 181 'Marcin Kuzminski <marcin@python-blog.com>')
186 182 self.assertEqual(sorted(init_chset._file_paths),
187 183 sorted([
188 184 'vcs/__init__.py',
189 185 'vcs/backends/BaseRepository.py',
190 186 'vcs/backends/__init__.py',
191 187 ])
192 188 )
193 189 self.assertEqual(sorted(init_chset._dir_paths),
194 190 sorted(['', 'vcs', 'vcs/backends']))
195 191
196 192 self.assertRaises(NodeDoesNotExistError, init_chset.get_node, path='foobar')
197 193
198 194 node = init_chset.get_node('vcs/')
199 195 self.assertTrue(hasattr(node, 'kind'))
200 196 self.assertEqual(node.kind, NodeKind.DIR)
201 197
202 198 node = init_chset.get_node('vcs')
203 199 self.assertTrue(hasattr(node, 'kind'))
204 200 self.assertEqual(node.kind, NodeKind.DIR)
205 201
206 202 node = init_chset.get_node('vcs/__init__.py')
207 203 self.assertTrue(hasattr(node, 'kind'))
208 204 self.assertEqual(node.kind, NodeKind.FILE)
209 205
210 206 def test_not_existing_changeset(self):
211 207 #rawid
212 208 self.assertRaises(RepositoryError, self.repo.get_changeset,
213 209 'abcd' * 10)
214 210 #shortid
215 211 self.assertRaises(RepositoryError, self.repo.get_changeset,
216 212 'erro' * 4)
217 213 #numeric
218 214 self.assertRaises(RepositoryError, self.repo.get_changeset,
219 215 self.repo.count() + 1)
220 216
221 217
222 218 # Small chance we ever get to this one
223 219 revision = pow(2, 30)
224 220 self.assertRaises(RepositoryError, self.repo.get_changeset, revision)
225 221
226 222 def test_changeset10(self):
227 223
228 224 chset10 = self.repo.get_changeset(10)
229 225 README = """===
230 226 VCS
231 227 ===
232 228
233 229 Various Version Control System management abstraction layer for Python.
234 230
235 231 Introduction
236 232 ------------
237 233
238 234 TODO: To be written...
239 235
240 236 """
241 237 node = chset10.get_node('README.rst')
242 238 self.assertEqual(node.kind, NodeKind.FILE)
243 239 self.assertEqual(node.content, README)
244 240
245 241
246 242 class MercurialChangesetTest(unittest.TestCase):
247 243
248 244 def setUp(self):
249 245 self.repo = MercurialRepository(TEST_HG_REPO)
250 246
251 247 def _test_equality(self, changeset):
252 248 revision = changeset.revision
253 249 self.assertEqual(changeset, self.repo.get_changeset(revision))
254 250
255 251 def test_equality(self):
256 252 self.setUp()
257 253 revs = [0, 10, 20]
258 254 changesets = [self.repo.get_changeset(rev) for rev in revs]
259 255 for changeset in changesets:
260 256 self._test_equality(changeset)
261 257
262 258 def test_default_changeset(self):
263 259 tip = self.repo.get_changeset('tip')
264 260 self.assertEqual(tip, self.repo.get_changeset())
265 261 self.assertEqual(tip, self.repo.get_changeset(revision=None))
266 262 self.assertEqual(tip, list(self.repo[-1:])[0])
267 263
268 264 def test_root_node(self):
269 265 tip = self.repo.get_changeset('tip')
270 266 self.assertTrue(tip.root is tip.get_node(''))
271 267
272 268 def test_lazy_fetch(self):
273 269 """
274 270 Test if changeset's nodes expands and are cached as we walk through
275 271 the revision. This test is somewhat hard to write as order of tests
276 272 is a key here. Written by running command after command in a shell.
277 273 """
278 274 self.setUp()
279 275 chset = self.repo.get_changeset(45)
280 276 self.assertTrue(len(chset.nodes) == 0)
281 277 root = chset.root
282 278 self.assertTrue(len(chset.nodes) == 1)
283 279 self.assertTrue(len(root.nodes) == 8)
284 280 # accessing root.nodes updates chset.nodes
285 281 self.assertTrue(len(chset.nodes) == 9)
286 282
287 283 docs = root.get_node('docs')
288 284 # we haven't yet accessed anything new as docs dir was already cached
289 285 self.assertTrue(len(chset.nodes) == 9)
290 286 self.assertTrue(len(docs.nodes) == 8)
291 287 # accessing docs.nodes updates chset.nodes
292 288 self.assertTrue(len(chset.nodes) == 17)
293 289
294 290 self.assertTrue(docs is chset.get_node('docs'))
295 291 self.assertTrue(docs is root.nodes[0])
296 292 self.assertTrue(docs is root.dirs[0])
297 293 self.assertTrue(docs is chset.get_node('docs'))
298 294
299 295 def test_nodes_with_changeset(self):
300 296 self.setUp()
301 297 chset = self.repo.get_changeset(45)
302 298 root = chset.root
303 299 docs = root.get_node('docs')
304 300 self.assertTrue(docs is chset.get_node('docs'))
305 301 api = docs.get_node('api')
306 302 self.assertTrue(api is chset.get_node('docs/api'))
307 303 index = api.get_node('index.rst')
308 304 self.assertTrue(index is chset.get_node('docs/api/index.rst'))
309 305 self.assertTrue(index is chset.get_node('docs')\
310 306 .get_node('api')\
311 307 .get_node('index.rst'))
312 308
313 309 def test_branch_and_tags(self):
314 310 chset0 = self.repo.get_changeset(0)
315 311 self.assertEqual(chset0.branch, 'default')
316 312 self.assertEqual(chset0.tags, [])
317 313
318 314 chset10 = self.repo.get_changeset(10)
319 315 self.assertEqual(chset10.branch, 'default')
320 316 self.assertEqual(chset10.tags, [])
321 317
322 318 chset44 = self.repo.get_changeset(44)
323 319 self.assertEqual(chset44.branch, 'web')
324 320
325 321 tip = self.repo.get_changeset('tip')
326 322 self.assertTrue('tip' in tip.tags)
327 323
328 324 def _test_file_size(self, revision, path, size):
329 325 node = self.repo.get_changeset(revision).get_node(path)
330 326 self.assertTrue(node.is_file())
331 327 self.assertEqual(node.size, size)
332 328
333 329 def test_file_size(self):
334 330 to_check = (
335 331 (10, 'setup.py', 1068),
336 332 (20, 'setup.py', 1106),
337 333 (60, 'setup.py', 1074),
338 334
339 335 (10, 'vcs/backends/base.py', 2921),
340 336 (20, 'vcs/backends/base.py', 3936),
341 337 (60, 'vcs/backends/base.py', 6189),
342 338 )
343 339 for revision, path, size in to_check:
344 340 self._test_file_size(revision, path, size)
345 341
346 342 def test_file_history(self):
347 343 # we can only check if those revisions are present in the history
348 344 # as we cannot update this test every time file is changed
349 345 files = {
350 346 'setup.py': [7, 18, 45, 46, 47, 69, 77],
351 347 'vcs/nodes.py': [7, 8, 24, 26, 30, 45, 47, 49, 56, 57, 58, 59, 60,
352 348 61, 73, 76],
353 349 'vcs/backends/hg.py': [4, 5, 6, 11, 12, 13, 14, 15, 16, 21, 22, 23,
354 350 26, 27, 28, 30, 31, 33, 35, 36, 37, 38, 39, 40, 41, 44, 45, 47,
355 351 48, 49, 53, 54, 55, 58, 60, 61, 67, 68, 69, 70, 73, 77, 78, 79,
356 352 82],
357 353 }
358 354 for path, revs in files.items():
359 355 tip = self.repo.get_changeset(revs[-1])
360 356 node = tip.get_node(path)
361 357 node_revs = [chset.revision for chset in node.history]
362 358 self.assertTrue(set(revs).issubset(set(node_revs)),
363 359 "We assumed that %s is subset of revisions for which file %s "
364 360 "has been changed, and history of that node returned: %s"
365 361 % (revs, path, node_revs))
366 362
367 363 def test_file_annotate(self):
368 364 files = {
369 365 'vcs/backends/__init__.py':
370 366 {89: {'lines_no': 31,
371 367 'changesets': [32, 32, 61, 32, 32, 37, 32, 32, 32, 44,
372 368 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
373 369 32, 32, 32, 32, 37, 32, 37, 37, 32,
374 370 32, 32]},
375 371 20: {'lines_no': 1,
376 372 'changesets': [4]},
377 373 55: {'lines_no': 31,
378 374 'changesets': [32, 32, 45, 32, 32, 37, 32, 32, 32, 44,
379 375 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
380 376 32, 32, 32, 32, 37, 32, 37, 37, 32,
381 377 32, 32]}},
382 378 'vcs/exceptions.py':
383 379 {89: {'lines_no': 18,
384 380 'changesets': [16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
385 381 16, 16, 17, 16, 16, 18, 18, 18]},
386 382 20: {'lines_no': 18,
387 383 'changesets': [16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
388 384 16, 16, 17, 16, 16, 18, 18, 18]},
389 385 55: {'lines_no': 18, 'changesets': [16, 16, 16, 16, 16, 16,
390 386 16, 16, 16, 16, 16, 16,
391 387 17, 16, 16, 18, 18, 18]}},
392 388 'MANIFEST.in': {89: {'lines_no': 5,
393 389 'changesets': [7, 7, 7, 71, 71]},
394 390 20: {'lines_no': 3,
395 391 'changesets': [7, 7, 7]},
396 392 55: {'lines_no': 3,
397 393 'changesets': [7, 7, 7]}}}
398 394
399 395
400 396 for fname, revision_dict in files.items():
401 397 for rev, data in revision_dict.items():
402 398 cs = self.repo.get_changeset(rev)
403 399 ann = cs.get_file_annotate(fname)
404 400
405 401 l1 = [x[1].revision for x in ann]
406 402 l2 = files[fname][rev]['changesets']
407 403 self.assertTrue(l1 == l2 , "The lists of revision for %s@rev%s"
408 404 "from annotation list should match each other,"
409 405 "got \n%s \nvs \n%s " % (fname, rev, l1, l2))
410 406
411 407 def test_changeset_state(self):
412 408 """
413 409 Tests which files have been added/changed/removed at particular revision
414 410 """
415 411
416 412 # rev 46ad32a4f974:
417 413 # hg st --rev 46ad32a4f974
418 414 # changed: 13
419 415 # added: 20
420 416 # removed: 1
421 417 changed = set(['.hgignore'
422 418 , 'README.rst' , 'docs/conf.py' , 'docs/index.rst' , 'setup.py'
423 419 , 'tests/test_hg.py' , 'tests/test_nodes.py' , 'vcs/__init__.py'
424 420 , 'vcs/backends/__init__.py' , 'vcs/backends/base.py'
425 421 , 'vcs/backends/hg.py' , 'vcs/nodes.py' , 'vcs/utils/__init__.py'])
426 422
427 423 added = set(['docs/api/backends/hg.rst'
428 424 , 'docs/api/backends/index.rst' , 'docs/api/index.rst'
429 425 , 'docs/api/nodes.rst' , 'docs/api/web/index.rst'
430 426 , 'docs/api/web/simplevcs.rst' , 'docs/installation.rst'
431 427 , 'docs/quickstart.rst' , 'setup.cfg' , 'vcs/utils/baseui_config.py'
432 428 , 'vcs/utils/web.py' , 'vcs/web/__init__.py' , 'vcs/web/exceptions.py'
433 429 , 'vcs/web/simplevcs/__init__.py' , 'vcs/web/simplevcs/exceptions.py'
434 430 , 'vcs/web/simplevcs/middleware.py' , 'vcs/web/simplevcs/models.py'
435 431 , 'vcs/web/simplevcs/settings.py' , 'vcs/web/simplevcs/utils.py'
436 432 , 'vcs/web/simplevcs/views.py'])
437 433
438 434 removed = set(['docs/api.rst'])
439 435
440 436 chset64 = self.repo.get_changeset('46ad32a4f974')
441 437 self.assertEqual(set((node.path for node in chset64.added)), added)
442 438 self.assertEqual(set((node.path for node in chset64.changed)), changed)
443 439 self.assertEqual(set((node.path for node in chset64.removed)), removed)
444 440
445 441 # rev b090f22d27d6:
446 442 # hg st --rev b090f22d27d6
447 443 # changed: 13
448 444 # added: 20
449 445 # removed: 1
450 446 chset88 = self.repo.get_changeset('b090f22d27d6')
451 447 self.assertEqual(set((node.path for node in chset88.added)), set())
452 448 self.assertEqual(set((node.path for node in chset88.changed)),
453 449 set(['.hgignore']))
454 450 self.assertEqual(set((node.path for node in chset88.removed)), set())
455 451 #
456 452 # 85:
457 453 # added: 2 ['vcs/utils/diffs.py', 'vcs/web/simplevcs/views/diffs.py']
458 454 # changed: 4 ['vcs/web/simplevcs/models.py', ...]
459 455 # removed: 1 ['vcs/utils/web.py']
460 456 chset85 = self.repo.get_changeset(85)
461 457 self.assertEqual(set((node.path for node in chset85.added)), set([
462 458 'vcs/utils/diffs.py',
463 459 'vcs/web/simplevcs/views/diffs.py']))
464 460 self.assertEqual(set((node.path for node in chset85.changed)), set([
465 461 'vcs/web/simplevcs/models.py',
466 462 'vcs/web/simplevcs/utils.py',
467 463 'vcs/web/simplevcs/views/__init__.py',
468 464 'vcs/web/simplevcs/views/repository.py',
469 465 ]))
470 466 self.assertEqual(set((node.path for node in chset85.removed)),
471 467 set(['vcs/utils/web.py']))
472 468
473 469
474 470 def test_files_state(self):
475 471 """
476 472 Tests state of FileNodes.
477 473 """
478 474 chset = self.repo.get_changeset(85)
479 475 node = chset.get_node('vcs/utils/diffs.py')
480 476 self.assertTrue(node.state, NodeState.ADDED)
481 477 self.assertTrue(node.added)
482 478 self.assertFalse(node.changed)
483 479 self.assertFalse(node.not_changed)
484 480 self.assertFalse(node.removed)
485 481
486 482 chset = self.repo.get_changeset(88)
487 483 node = chset.get_node('.hgignore')
488 484 self.assertTrue(node.state, NodeState.CHANGED)
489 485 self.assertFalse(node.added)
490 486 self.assertTrue(node.changed)
491 487 self.assertFalse(node.not_changed)
492 488 self.assertFalse(node.removed)
493 489
494 490 chset = self.repo.get_changeset(85)
495 491 node = chset.get_node('setup.py')
496 492 self.assertTrue(node.state, NodeState.NOT_CHANGED)
497 493 self.assertFalse(node.added)
498 494 self.assertFalse(node.changed)
499 495 self.assertTrue(node.not_changed)
500 496 self.assertFalse(node.removed)
501 497
502 498 # If node has REMOVED state then trying to fetch it would raise
503 499 # ChangesetError exception
504 500 chset = self.repo.get_changeset(2)
505 501 path = 'vcs/backends/BaseRepository.py'
506 502 self.assertRaises(NodeDoesNotExistError, chset.get_node, path)
507 503 # but it would be one of ``removed`` (changeset's attribute)
508 504 self.assertTrue(path in [rf.path for rf in chset.removed])
509 505
510 506 def test_commit_message_is_unicode(self):
511 507 for cm in self.repo:
512 508 self.assertEqual(type(cm.message), unicode)
513 509
514 510 def test_changeset_author_is_unicode(self):
515 511 for cm in self.repo:
516 512 self.assertEqual(type(cm.author), unicode)
517 513
518 514 def test_repo_files_content_is_unicode(self):
519 515 test_changeset = self.repo.get_changeset(100)
520 516 for node in test_changeset.get_node('/'):
521 517 if node.is_file():
522 518 self.assertEqual(type(node.content), unicode)
523 519
524 520 def test_wrong_path(self):
525 521 # There is 'setup.py' in the root dir but not there:
526 522 path = 'foo/bar/setup.py'
527 523 self.assertRaises(VCSError, self.repo.get_changeset().get_node, path)
528 524
529 525
530 526 def test_archival_file(self):
531 527 #TODO:
532 528 pass
533 529
534 530 def test_archival_as_generator(self):
535 531 #TODO:
536 532 pass
537 533
538 534 def test_archival_wrong_kind(self):
539 535 tip = self.repo.get_changeset()
540 536 self.assertRaises(VCSError, tip.fill_archive, kind='error')
541 537
542 538 def test_archival_empty_prefix(self):
543 539 #TODO:
544 540 pass
545 541
546 542
547 543 def test_author_email(self):
548 544 self.assertEqual('marcin@python-blog.com',
549 545 self.repo.get_changeset('b986218ba1c9').author_email)
550 546 self.assertEqual('lukasz.balcerzak@python-center.pl',
551 547 self.repo.get_changeset('3803844fdbd3').author_email)
552 548 self.assertEqual('',
553 549 self.repo.get_changeset('84478366594b').author_email)
554 550
555 551 def test_author_username(self):
556 552 self.assertEqual('Marcin Kuzminski',
557 553 self.repo.get_changeset('b986218ba1c9').author_name)
558 554 self.assertEqual('Lukasz Balcerzak',
559 555 self.repo.get_changeset('3803844fdbd3').author_name)
560 556 self.assertEqual('marcink',
561 557 self.repo.get_changeset('84478366594b').author_name)
1 NO CONTENT: modified file, binary diff hidden
General Comments 0
You need to be logged in to leave comments. Login now