##// END OF EJS Templates
fixes #200, rewrote the whole caching mechanism to get rid of such problems. Now cached instances are attached...
marcink -
r1366:9c0f5d55 beta
parent child Browse files
Show More
@@ -89,10 +89,8 b' class ReposController(BaseController):'
89 """
89 """
90 self.__load_defaults()
90 self.__load_defaults()
91
91
92 repo, dbrepo = ScmModel().get(repo_name, retval='repo')
92 c.repo_info = db_repo = Repository.by_repo_name(repo_name)
93
93 repo = scm_repo = db_repo.scm_instance
94 repo_model = RepoModel()
95 c.repo_info = repo_model.get_by_repo_name(repo_name)
96
94
97 if c.repo_info is None:
95 if c.repo_info is None:
98 h.flash(_('%s repository is not mapped to db perhaps'
96 h.flash(_('%s repository is not mapped to db perhaps'
@@ -153,10 +151,9 b' class ReposController(BaseController):'
153 """GET /repos: All items in the collection"""
151 """GET /repos: All items in the collection"""
154 # url('repos')
152 # url('repos')
155
153
156 all_repos = [r.repo_name for r in Repository.query().all()]
154 c.repos_list = ScmModel().get_repos(Repository.query()
157
155 .order_by(Repository.repo_name)
158 cached_repo_list = ScmModel().get_repos(all_repos)
156 .all(), sort_key='name_sort')
159 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
160 return render('admin/repos/repos.html')
157 return render('admin/repos/repos.html')
161
158
162 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
159 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
@@ -181,12 +181,14 b' class ReposGroupsController(BaseControll'
181 """GET /repos_groups/id: Show a specific item"""
181 """GET /repos_groups/id: Show a specific item"""
182 # url('repos_group', id=ID)
182 # url('repos_group', id=ID)
183
183
184 c.group = Group.get(id)
184 gr = c.group = Group.get(id)
185
185 if c.group:
186 if c.group:
186 c.group_repos = c.group.repositories.all()
187 c.group_repos = c.group.repositories.all()
187 else:
188 else:
188 return redirect(url('repos_group'))
189 return redirect(url('repos_group'))
189
190
191
190 sortables = ['name', 'description', 'last_change', 'tip', 'owner']
192 sortables = ['name', 'description', 'last_change', 'tip', 'owner']
191 current_sort = request.GET.get('sort', 'name')
193 current_sort = request.GET.get('sort', 'name')
192 current_sort_slug = current_sort.replace('-', '')
194 current_sort_slug = current_sort.replace('-', '')
@@ -201,18 +203,12 b' class ReposGroupsController(BaseControll'
201 sort_key = current_sort_slug + '_sort'
203 sort_key = current_sort_slug + '_sort'
202
204
203 #overwrite our cached list with current filter
205 #overwrite our cached list with current filter
204 gr_filter = [r.repo_name for r in c.group_repos]
206 gr_filter = c.group_repos
205 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
207 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
206
208
207 if c.sort_by.startswith('-'):
209 c.repos_list = c.cached_repo_list
208 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
209 reverse=True)
210 else:
211 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
212 reverse=False)
213
210
214 c.repo_cnt = len(c.repos_list)
211 c.repo_cnt = 0
215
216
212
217 c.groups = self.sa.query(Group).order_by(Group.group_name)\
213 c.groups = self.sa.query(Group).order_by(Group.group_name)\
218 .filter(Group.group_parent_id == id).all()
214 .filter(Group.group_parent_id == id).all()
@@ -258,9 +258,10 b' class SettingsController(BaseController)'
258 # url('admin_settings_my_account')
258 # url('admin_settings_my_account')
259
259
260 c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
260 c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
261 all_repos = [r.repo_name for r in self.sa.query(Repository)\
261 all_repos = self.sa.query(Repository)\
262 .filter(Repository.user_id == c.user.user_id)\
262 .filter(Repository.user_id == c.user.user_id)\
263 .order_by(func.lower(Repository.repo_name)).all()]
263 .order_by(func.lower(Repository.repo_name)).all()
264
264 c.user_repos = ScmModel().get_repos(all_repos)
265 c.user_repos = ScmModel().get_repos(all_repos)
265
266
266 if c.user.username == 'default':
267 if c.user.username == 'default':
@@ -31,7 +31,7 b' from paste.httpexceptions import HTTPBad'
31
31
32 from rhodecode.lib.auth import LoginRequired
32 from rhodecode.lib.auth import LoginRequired
33 from rhodecode.lib.base import BaseController, render
33 from rhodecode.lib.base import BaseController, render
34 from rhodecode.model.db import Group
34 from rhodecode.model.db import Group, Repository
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
@@ -56,16 +56,11 b' class HomeController(BaseController):'
56
56
57 sort_key = current_sort_slug + '_sort'
57 sort_key = current_sort_slug + '_sort'
58
58
59 if c.sort_by.startswith('-'):
59
60 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
60 c.repos_list = self.scm_model.get_repos(sort_key=sort_key)
61 reverse=True)
62 else:
63 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
64 reverse=False)
65
61
66 c.repo_cnt = len(c.repos_list)
62 c.repo_cnt = len(c.repos_list)
67
63
68
69 c.groups = Group.query().filter(Group.group_parent_id == None).all()
64 c.groups = Group.query().filter(Group.group_parent_id == None).all()
70
65
71
66
@@ -73,8 +68,9 b' class HomeController(BaseController):'
73
68
74 def repo_switcher(self):
69 def repo_switcher(self):
75 if request.is_xhr:
70 if request.is_xhr:
76 c.repos_list = sorted(c.cached_repo_list,
71 all_repos = Repository.query().order_by(Repository.repo_name).all()
77 key=itemgetter('name_sort'), reverse=False)
72 c.repos_list = self.scm_model.get_repos(all_repos,
73 sort_key='name_sort')
78 return render('/repo_switcher_list.html')
74 return render('/repo_switcher_list.html')
79 else:
75 else:
80 return HTTPBadRequest()
76 return HTTPBadRequest()
@@ -155,6 +155,7 b' class SettingsController(BaseRepoControl'
155 invalidate_cache('get_repo_cached_%s' % repo_name)
155 invalidate_cache('get_repo_cached_%s' % repo_name)
156 h.flash(_('deleted repository %s') % repo_name, category='success')
156 h.flash(_('deleted repository %s') % repo_name, category='success')
157 except Exception:
157 except Exception:
158 log.error(traceback.format_exc())
158 h.flash(_('An error occurred during deletion of %s') % repo_name,
159 h.flash(_('An error occurred during deletion of %s') % repo_name,
159 category='error')
160 category='error')
160
161
@@ -205,4 +206,9 b' class SettingsController(BaseRepoControl'
205 errors=errors.error_dict or {},
206 errors=errors.error_dict or {},
206 prefix_error=False,
207 prefix_error=False,
207 encoding="UTF-8")
208 encoding="UTF-8")
209 except Exception:
210 log.error(traceback.format_exc())
211 h.flash(_('An error occurred during repository forking %s') %
212 repo_name, category='error')
213
208 return redirect(url('home'))
214 return redirect(url('home'))
@@ -12,6 +12,7 b' from rhodecode.lib.utils import get_repo'
12 from rhodecode.model import meta
12 from rhodecode.model import meta
13 from rhodecode.model.scm import ScmModel
13 from rhodecode.model.scm import ScmModel
14 from rhodecode import BACKENDS
14 from rhodecode import BACKENDS
15 from rhodecode.model.db import Repository
15
16
16
17
17 class BaseController(WSGIController):
18 class BaseController(WSGIController):
@@ -26,7 +27,7 b' class BaseController(WSGIController):'
26
27
27 self.sa = meta.Session()
28 self.sa = meta.Session()
28 self.scm_model = ScmModel(self.sa)
29 self.scm_model = ScmModel(self.sa)
29 c.cached_repo_list = self.scm_model.get_repos()
30
30 #c.unread_journal = scm_model.get_unread_journal()
31 #c.unread_journal = scm_model.get_unread_journal()
31
32
32 def __call__(self, environ, start_response):
33 def __call__(self, environ, start_response):
@@ -62,8 +63,7 b' class BaseRepoController(BaseController)'
62 super(BaseRepoController, self).__before__()
63 super(BaseRepoController, self).__before__()
63 if c.repo_name:
64 if c.repo_name:
64
65
65 c.rhodecode_repo, dbrepo = self.scm_model.get(c.repo_name,
66 c.rhodecode_repo = Repository.by_repo_name(c.repo_name).scm_instance
66 retval='repo')
67
67
68 if c.rhodecode_repo is not None:
68 if c.rhodecode_repo is not None:
69 c.repository_followers = \
69 c.repository_followers = \
@@ -102,7 +102,6 b' def get_commits_stats(repo_name, ts_min_'
102 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
102 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
103 ts_max_y)
103 ts_max_y)
104 lockkey_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
104 lockkey_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
105 print jn(lockkey_path, lockkey)
106 log.info('running task with lockkey %s', lockkey)
105 log.info('running task with lockkey %s', lockkey)
107 try:
106 try:
108 lock = l = DaemonLock(jn(lockkey_path, lockkey))
107 lock = l = DaemonLock(jn(lockkey_path, lockkey))
@@ -372,8 +372,7 b' def action_parser(user_log, feed=False):'
372 repo_name = user_log.repository.repo_name
372 repo_name = user_log.repository.repo_name
373
373
374 from rhodecode.model.scm import ScmModel
374 from rhodecode.model.scm import ScmModel
375 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
375 repo = user_log.repository.scm_instance
376 invalidation_list=[])
377
376
378 message = lambda rev: get_changeset_safe(repo, rev).message
377 message = lambda rev: get_changeset_safe(repo, rev).message
379 cs_links = []
378 cs_links = []
@@ -472,7 +472,7 b' def create_test_index(repo_location, ful'
472 shutil.rmtree(index_location)
472 shutil.rmtree(index_location)
473
473
474 try:
474 try:
475 l = DaemonLock(file=jn(dn(dn(index_location)), 'make_index.lock'))
475 l = DaemonLock(file=jn(dn(index_location), 'make_index.lock'))
476 WhooshIndexingDaemon(index_location=index_location,
476 WhooshIndexingDaemon(index_location=index_location,
477 repo_location=repo_location)\
477 repo_location=repo_location)\
478 .run(full_index=full_index)
478 .run(full_index=full_index)
@@ -26,13 +26,23 b''
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 from datetime import date
30 from datetime import date
30
31
31 from sqlalchemy import *
32 from sqlalchemy import *
32 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.orm import relationship, backref
34 from sqlalchemy.orm import relationship, backref, joinedload
34 from sqlalchemy.orm.interfaces import MapperExtension
35 from sqlalchemy.orm.interfaces import MapperExtension
35
36
37 from beaker.cache import cache_region, region_invalidate
38
39
40 from vcs import get_backend
41 from vcs.utils.helpers import get_scm
42 from vcs.exceptions import RepositoryError, VCSError
43 from vcs.utils.lazy import LazyProperty
44 from vcs.nodes import FileNode
45
36 from rhodecode.lib import str2bool
46 from rhodecode.lib import str2bool
37 from rhodecode.model.meta import Base, Session
47 from rhodecode.model.meta import Base, Session
38 from rhodecode.model.caching_query import FromCache
48 from rhodecode.model.caching_query import FromCache
@@ -150,6 +160,7 b' class User(Base):'
150 return self.admin
160 return self.admin
151
161
152 def __repr__(self):
162 def __repr__(self):
163 return 'ahmmm'
153 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
164 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
154 self.user_id, self.username)
165 self.user_id, self.username)
155
166
@@ -266,8 +277,13 b' class Repository(Base):'
266
277
267 @classmethod
278 @classmethod
268 def by_repo_name(cls, repo_name):
279 def by_repo_name(cls, repo_name):
269 return Session.query(cls).filter(cls.repo_name == repo_name).one()
280 q = Session.query(cls).filter(cls.repo_name == repo_name)
270
281
282 q = q.options(joinedload(Repository.fork))\
283 .options(joinedload(Repository.user))\
284 .options(joinedload(Repository.group))\
285
286 return q.one()
271
287
272 @classmethod
288 @classmethod
273 def get_repo_forks(cls, repo_id):
289 def get_repo_forks(cls, repo_id):
@@ -298,6 +314,127 b' class Repository(Base):'
298 def groups_and_repo(self):
314 def groups_and_repo(self):
299 return self.groups_with_parents, self.just_name
315 return self.groups_with_parents, self.just_name
300
316
317 @LazyProperty
318 def repo_path(self):
319 """
320 Returns base full path for that repository means where it actually
321 exists on a filesystem
322 """
323
324 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
325 return q.ui_value
326
327 @property
328 def repo_full_path(self):
329 p = [self.repo_path]
330 # we need to split the name by / since this is how we store the
331 # names in the database, but that eventually needs to be converted
332 # into a valid system path
333 p += self.repo_name.split('/')
334 return os.path.join(*p)
335
336 @property
337 def _ui(self):
338 """
339 Creates an db based ui object for this repository
340 """
341 from mercurial import ui
342 from mercurial import config
343 baseui = ui.ui()
344
345 #clean the baseui object
346 baseui._ocfg = config.config()
347 baseui._ucfg = config.config()
348 baseui._tcfg = config.config()
349
350
351 ret = Session.query(RhodeCodeUi)\
352 .options(FromCache("sql_cache_short",
353 "repository_repo_ui")).all()
354
355 hg_ui = ret
356 for ui_ in hg_ui:
357 if ui_.ui_active:
358 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
359 ui_.ui_key, ui_.ui_value)
360 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
361
362 return baseui
363
364 #==========================================================================
365 # SCM CACHE INSTANCE
366 #==========================================================================
367
368 @property
369 def invalidate(self):
370 """
371 Returns Invalidation object if this repo should be invalidated
372 None otherwise. `cache_active = False` means that this cache
373 state is not valid and needs to be invalidated
374 """
375 return Session.query(CacheInvalidation)\
376 .filter(CacheInvalidation.cache_key == self.repo_name)\
377 .filter(CacheInvalidation.cache_active == False)\
378 .scalar()
379
380 @property
381 def set_invalidate(self):
382 """
383 set a cache for invalidation for this instance
384 """
385 inv = Session.query(CacheInvalidation)\
386 .filter(CacheInvalidation.cache_key == self.repo_name)\
387 .scalar()
388
389 if inv is None:
390 inv = CacheInvalidation(self.repo_name)
391 inv.cache_active = True
392 Session.add(inv)
393 Session.commit()
394
395 @property
396 def scm_instance(self):
397 return self.__get_instance(self.repo_name)
398
399 @property
400 def scm_instance_cached(self):
401 @cache_region('long_term')
402 def _c(repo_name):
403 return self.__get_instance(repo_name)
404
405 inv = self.invalidate
406 if inv:
407 region_invalidate(_c, None, self.repo_name)
408 #update our cache
409 inv.cache_key.cache_active = True
410 Session.add(inv)
411 Session.commit()
412
413 return _c(self.repo_name)
414
415 def __get_instance(self, repo_name):
416 try:
417 alias = get_scm(self.repo_full_path)[0]
418 log.debug('Creating instance of %s repository', alias)
419 backend = get_backend(alias)
420 except VCSError:
421 log.error(traceback.format_exc())
422 log.error('Perhaps this repository is in db and not in '
423 'filesystem run rescan repositories with '
424 '"destroy old data " option from admin panel')
425 return
426
427 if alias == 'hg':
428 repo = backend(self.repo_full_path, create=False,
429 baseui=self._ui)
430 #skip hidden web repository
431 if repo._get_hidden():
432 return
433 else:
434 repo = backend(self.repo_full_path, create=False)
435
436 return repo
437
301
438
302 class Group(Base):
439 class Group(Base):
303 __tablename__ = 'groups'
440 __tablename__ = 'groups'
@@ -280,6 +280,13 b' def ValidRepoName(edit, old_data):'
280
280
281 return _ValidRepoName
281 return _ValidRepoName
282
282
283 def ValidForkName():
284 class _ValidForkName(formencode.validators.FancyValidator):
285 def to_python(self, value, state):
286 return value
287 return _ValidForkName
288
289
283 def SlugifyName():
290 def SlugifyName():
284 class _SlugifyName(formencode.validators.FancyValidator):
291 class _SlugifyName(formencode.validators.FancyValidator):
285
292
@@ -326,6 +333,7 b' def ValidForkType(old_data):'
326 if old_data['repo_type'] != value:
333 if old_data['repo_type'] != value:
327 raise formencode.Invalid(_('Fork have to be the same '
334 raise formencode.Invalid(_('Fork have to be the same '
328 'type as original'), value, state)
335 'type as original'), value, state)
336
329 return value
337 return value
330 return _ValidForkType
338 return _ValidForkType
331
339
@@ -583,6 +591,9 b' def RepoForkForm(edit=False, old_data={}'
583 description = UnicodeString(strip=True, min=1, not_empty=True)
591 description = UnicodeString(strip=True, min=1, not_empty=True)
584 private = StringBoolean(if_missing=False)
592 private = StringBoolean(if_missing=False)
585 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
593 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
594
595 chained_validators = [ValidForkName()]
596
586 return _RepoForkForm
597 return _RepoForkForm
587
598
588 def RepoSettingsForm(edit=False, old_data={}):
599 def RepoSettingsForm(edit=False, old_data={}):
@@ -70,28 +70,6 b' class RepoModel(BaseModel):'
70 "get_repo_%s" % repo_name))
70 "get_repo_%s" % repo_name))
71 return repo.scalar()
71 return repo.scalar()
72
72
73 def get_full(self, repo_name, cache=False, invalidate=False):
74 repo = self.sa.query(Repository)\
75 .options(joinedload(Repository.fork))\
76 .options(joinedload(Repository.user))\
77 .options(joinedload(Repository.group))\
78 .filter(Repository.repo_name == repo_name)\
79
80 if cache:
81 repo = repo.options(FromCache("sql_cache_long",
82 "get_repo_full_%s" % repo_name))
83 if invalidate and cache:
84 repo.invalidate()
85
86 ret = repo.scalar()
87
88 #make transient for sake of errors
89 make_transient(ret)
90 for k in ['fork', 'user', 'group']:
91 attr = getattr(ret, k, False)
92 if attr:
93 make_transient(attr)
94 return ret
95
73
96 def get_users_js(self):
74 def get_users_js(self):
97
75
@@ -193,12 +171,13 b' class RepoModel(BaseModel):'
193 raise
171 raise
194
172
195 def create(self, form_data, cur_user, just_db=False, fork=False):
173 def create(self, form_data, cur_user, just_db=False, fork=False):
174
196 try:
175 try:
197 if fork:
176 if fork:
198 #force str since hg doesn't go with unicode
177 #force str since hg doesn't go with unicode
199 repo_name = str(form_data['fork_name'])
178 repo_name = str(form_data['fork_name'])
200 org_name = str(form_data['repo_name'])
179 org_name = str(form_data['repo_name'])
201 org_full_name = str(form_data['repo_name_full'])
180 org_full_name = org_name#str(form_data['fork_name_full'])
202
181
203 else:
182 else:
204 org_name = repo_name = str(form_data['repo_name'])
183 org_name = repo_name = str(form_data['repo_name'])
@@ -208,7 +187,10 b' class RepoModel(BaseModel):'
208 new_repo.enable_statistics = False
187 new_repo.enable_statistics = False
209 for k, v in form_data.items():
188 for k, v in form_data.items():
210 if k == 'repo_name':
189 if k == 'repo_name':
211 v = repo_name_full
190 if fork:
191 v = repo_name
192 else:
193 v = repo_name_full
212 if k == 'repo_group':
194 if k == 'repo_group':
213 k = 'group_id'
195 k = 'group_id'
214
196
@@ -216,7 +198,7 b' class RepoModel(BaseModel):'
216
198
217 if fork:
199 if fork:
218 parent_repo = self.sa.query(Repository)\
200 parent_repo = self.sa.query(Repository)\
219 .filter(Repository.repo_name == org_full_name).scalar()
201 .filter(Repository.repo_name == org_full_name).one()
220 new_repo.fork = parent_repo
202 new_repo.fork = parent_repo
221
203
222 new_repo.user_id = cur_user.user_id
204 new_repo.user_id = cur_user.user_id
@@ -70,6 +70,75 b' class RepoTemp(object):'
70 def __repr__(self):
70 def __repr__(self):
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72
72
73 class CachedRepoList(object):
74
75 def __init__(self, db_repo_list, invalidation_list, repos_path,
76 order_by=None):
77 self.db_repo_list = db_repo_list
78 self.invalidation_list = invalidation_list
79 self.repos_path = repos_path
80 self.order_by = order_by
81 self.reversed = (order_by or '').startswith('-')
82
83 def __len__(self):
84 return len(self.db_repo_list)
85
86 def __repr__(self):
87 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
88
89 def __iter__(self):
90 for db_repo in self.db_repo_list:
91 dbr = db_repo
92
93 # invalidate the repo cache if needed before getting the
94 # scm instance
95
96 scm_invalidate = False
97 if self.invalidation_list is not None:
98 scm_invalidate = dbr.repo_name in self.invalidation_list
99
100 if scm_invalidate:
101 log.info('invalidating cache for repository %s',
102 dbr.repo_name)
103 db_repo.set_invalidate
104
105 scmr = db_repo.scm_instance_cached
106
107 #check permission at this level
108 if not HasRepoPermissionAny('repository.read',
109 'repository.write',
110 'repository.admin')(dbr.repo_name,
111 'get repo check'):
112 continue
113
114
115
116
117
118 last_change = scmr.last_change
119 tip = h.get_changeset_safe(scmr, 'tip')
120
121 tmp_d = {}
122 tmp_d['name'] = dbr.repo_name
123 tmp_d['name_sort'] = tmp_d['name'].lower()
124 tmp_d['description'] = dbr.description
125 tmp_d['description_sort'] = tmp_d['description']
126 tmp_d['last_change'] = last_change
127 tmp_d['last_change_sort'] = time.mktime(last_change \
128 .timetuple())
129 tmp_d['tip'] = tip.raw_id
130 tmp_d['tip_sort'] = tip.revision
131 tmp_d['rev'] = tip.revision
132 tmp_d['contact'] = dbr.user.full_contact
133 tmp_d['contact_sort'] = tmp_d['contact']
134 tmp_d['owner_sort'] = tmp_d['contact']
135 tmp_d['repo_archives'] = list(scmr._get_archives())
136 tmp_d['last_msg'] = tip.message
137 tmp_d['repo'] = scmr
138 tmp_d['dbrepo'] = dbr.get_dict()
139 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
140 else {}
141 yield tmp_d
73
142
74 class ScmModel(BaseModel):
143 class ScmModel(BaseModel):
75 """Generic Scm Model
144 """Generic Scm Model
@@ -118,7 +187,7 b' class ScmModel(BaseModel):'
118
187
119 return repos_list
188 return repos_list
120
189
121 def get_repos(self, all_repos=None):
190 def get_repos(self, all_repos=None, sort_key=None):
122 """
191 """
123 Get all repos from db and for each repo create it's
192 Get all repos from db and for each repo create it's
124 backend instance and fill that backed with information from database
193 backend instance and fill that backed with information from database
@@ -127,120 +196,21 b' class ScmModel(BaseModel):'
127 give specific repositories list, good for filtering
196 give specific repositories list, good for filtering
128 """
197 """
129 if all_repos is None:
198 if all_repos is None:
130 repos = self.sa.query(Repository)\
199 all_repos = self.sa.query(Repository)\
131 .filter(Repository.group_id == None)\
200 .filter(Repository.group_id == None)\
132 .order_by(Repository.repo_name).all()
201 .order_by(Repository.repo_name).all()
133 all_repos = [r.repo_name for r in repos]
134
202
135 #get the repositories that should be invalidated
203 #get the repositories that should be invalidated
136 invalidation_list = [str(x.cache_key) for x in \
204 invalidation_list = [str(x.cache_key) for x in \
137 self.sa.query(CacheInvalidation.cache_key)\
205 self.sa.query(CacheInvalidation.cache_key)\
138 .filter(CacheInvalidation.cache_active == False)\
206 .filter(CacheInvalidation.cache_active == False)\
139 .all()]
207 .all()]
140 for r_name in all_repos:
141 r_dbr = self.get(r_name, invalidation_list)
142 if r_dbr is not None:
143 repo, dbrepo = r_dbr
144
145 if repo is None or dbrepo is None:
146 log.error('Repository "%s" looks somehow corrupted '
147 'fs-repo:%s,db-repo:%s both values should be '
148 'present', r_name, repo, dbrepo)
149 continue
150 last_change = repo.last_change
151 tip = h.get_changeset_safe(repo, 'tip')
152
153 tmp_d = {}
154 tmp_d['name'] = dbrepo.repo_name
155 tmp_d['name_sort'] = tmp_d['name'].lower()
156 tmp_d['description'] = dbrepo.description
157 tmp_d['description_sort'] = tmp_d['description']
158 tmp_d['last_change'] = last_change
159 tmp_d['last_change_sort'] = time.mktime(last_change \
160 .timetuple())
161 tmp_d['tip'] = tip.raw_id
162 tmp_d['tip_sort'] = tip.revision
163 tmp_d['rev'] = tip.revision
164 tmp_d['contact'] = dbrepo.user.full_contact
165 tmp_d['contact_sort'] = tmp_d['contact']
166 tmp_d['owner_sort'] = tmp_d['contact']
167 tmp_d['repo_archives'] = list(repo._get_archives())
168 tmp_d['last_msg'] = tip.message
169 tmp_d['repo'] = repo
170 tmp_d['dbrepo'] = dbrepo.get_dict()
171 tmp_d['dbrepo_fork'] = dbrepo.fork.get_dict() if dbrepo.fork \
172 else {}
173 yield tmp_d
174
175 def get(self, repo_name, invalidation_list=None, retval='all'):
176 """Returns a tuple of Repository,DbRepository,
177 Get's repository from given name, creates BackendInstance and
178 propagates it's data from database with all additional information
179
180 :param repo_name:
181 :param invalidation_list: if a invalidation list is given the get
182 method should not manually check if this repository needs
183 invalidation and just invalidate the repositories in list
184 :param retval: string specifing what to return one of 'repo','dbrepo',
185 'all'if repo or dbrepo is given it'll just lazy load chosen type
186 and return None as the second
187 """
188 if not HasRepoPermissionAny('repository.read', 'repository.write',
189 'repository.admin')(repo_name, 'get repo check'):
190 return
191
208
192 #======================================================================
209 repo_iter = CachedRepoList(all_repos, invalidation_list,
193 # CACHE FUNCTION
210 repos_path=self.repos_path,
194 #======================================================================
211 order_by=sort_key)
195 @cache_region('long_term')
196 def _get_repo(repo_name):
197
198 repo_path = os.path.join(self.repos_path, repo_name)
199
200 try:
201 alias = get_scm(repo_path)[0]
202 log.debug('Creating instance of %s repository', alias)
203 backend = get_backend(alias)
204 except VCSError:
205 log.error(traceback.format_exc())
206 log.error('Perhaps this repository is in db and not in '
207 'filesystem run rescan repositories with '
208 '"destroy old data " option from admin panel')
209 return
210
212
211 if alias == 'hg':
213 return repo_iter
212 repo = backend(repo_path, create=False, baseui=make_ui('db'))
213 #skip hidden web repository
214 if repo._get_hidden():
215 return
216 else:
217 repo = backend(repo_path, create=False)
218
219 return repo
220
221 pre_invalidate = True
222 dbinvalidate = False
223
224 if invalidation_list is not None:
225 pre_invalidate = repo_name in invalidation_list
226
227 if pre_invalidate:
228 #this returns object to invalidate
229 invalidate = self._should_invalidate(repo_name)
230 if invalidate:
231 log.info('invalidating cache for repository %s', repo_name)
232 region_invalidate(_get_repo, None, repo_name)
233 self._mark_invalidated(invalidate)
234 dbinvalidate = True
235
236 r, dbr = None, None
237 if retval == 'repo' or 'all':
238 r = _get_repo(repo_name)
239 if retval == 'dbrepo' or 'all':
240 dbr = RepoModel().get_full(repo_name, cache=True,
241 invalidate=dbinvalidate)
242
243 return r, dbr
244
214
245 def mark_for_invalidation(self, repo_name):
215 def mark_for_invalidation(self, repo_name):
246 """Puts cache invalidation task into db for
216 """Puts cache invalidation task into db for
@@ -65,7 +65,7 b''
65 <label for="new_password">${_('New password')}:</label>
65 <label for="new_password">${_('New password')}:</label>
66 </div>
66 </div>
67 <div class="input">
67 <div class="input">
68 ${h.password('new_password',class_='medium')}
68 ${h.password('new_password',class_='medium',autocomplete="off")}
69 </div>
69 </div>
70 </div>
70 </div>
71
71
@@ -54,7 +54,7 b''
54 <label for="new_password">${_('New password')}:</label>
54 <label for="new_password">${_('New password')}:</label>
55 </div>
55 </div>
56 <div class="input">
56 <div class="input">
57 ${h.password('new_password',class_="medium")}
57 ${h.password('new_password',class_="medium",autocomplete="off")}
58 </div>
58 </div>
59 </div>
59 </div>
60
60
@@ -7,6 +7,9 b' command.'
7 This module initializes the application via ``websetup`` (`paster
7 This module initializes the application via ``websetup`` (`paster
8 setup-app`) and provides the base testing objects.
8 setup-app`) and provides the base testing objects.
9 """
9 """
10 import os
11 from os.path import join as jn
12
10 from unittest import TestCase
13 from unittest import TestCase
11
14
12 from paste.deploy import loadapp
15 from paste.deploy import loadapp
@@ -14,7 +17,7 b' from paste.script.appinstall import Setu'
14 from pylons import config, url
17 from pylons import config, url
15 from routes.util import URLGenerator
18 from routes.util import URLGenerator
16 from webtest import TestApp
19 from webtest import TestApp
17 import os
20
18 from rhodecode.model import meta
21 from rhodecode.model import meta
19 import logging
22 import logging
20
23
@@ -35,7 +38,7 b' import pylons.test'
35 environ = {}
38 environ = {}
36
39
37 #SOME GLOBALS FOR TESTS
40 #SOME GLOBALS FOR TESTS
38 TESTS_TMP_PATH = '/tmp'
41 TESTS_TMP_PATH = jn('/', 'tmp')
39
42
40 HG_REPO = 'vcs_test_hg'
43 HG_REPO = 'vcs_test_hg'
41 GIT_REPO = 'vcs_test_git'
44 GIT_REPO = 'vcs_test_git'
@@ -64,8 +67,8 b' class TestController(TestCase):'
64 'password':password})
67 'password':password})
65
68
66 if 'invalid user name' in response.body:
69 if 'invalid user name' in response.body:
67 assert False, 'could not login using %s %s' % (username, password)
70 self.fail('could not login using %s %s' % (username, password))
68
71
69 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
72 self.assertEqual(response.status, '302 Found')
70 assert response.session['rhodecode_user'].username == username, 'wrong logged in user got %s expected %s' % (response.session['rhodecode_user'].username, username)
73 self.assertEqual(response.session['rhodecode_user'].username, username)
71 return response.follow()
74 return response.follow()
@@ -6,6 +6,11 b' from rhodecode.tests import *'
6
6
7 class TestAdminReposController(TestController):
7 class TestAdminReposController(TestController):
8
8
9
10 def __make_repo(self):
11 pass
12
13
9 def test_index(self):
14 def test_index(self):
10 self.log_user()
15 self.log_user()
11 response = self.app.get(url('repos'))
16 response = self.app.get(url('repos'))
@@ -21,31 +26,39 b' class TestAdminReposController(TestContr'
21 private = False
26 private = False
22 response = self.app.post(url('repos'), {'repo_name':repo_name,
27 response = self.app.post(url('repos'), {'repo_name':repo_name,
23 'repo_type':'hg',
28 'repo_type':'hg',
29 'clone_uri':'',
30 'repo_group':'',
24 'description':description,
31 'description':description,
25 'private':private})
32 'private':private})
26
33 self.assertTrue('flash' in response.session)
27
34
28 #test if we have a message for that repository
35 #test if we have a message for that repository
29 assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo'
36 self.assertTrue('''created repository %s''' % (repo_name) in
37 response.session['flash'][0])
30
38
31 #test if the fork was created in the database
39 #test if the repo was created in the database
32 new_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).one()
40 new_repo = self.sa.query(Repository).filter(Repository.repo_name ==
41 repo_name).one()
33
42
34 assert new_repo.repo_name == repo_name, 'wrong name of repo name in db'
43 self.assertEqual(new_repo.repo_name, repo_name)
35 assert new_repo.description == description, 'wrong description'
44 self.assertEqual(new_repo.description, description)
36
45
37 #test if repository is visible in the list ?
46 #test if repository is visible in the list ?
38 response = response.follow()
47 response = response.follow()
39
48
40 assert repo_name in response.body, 'missing new repo from the main repos list'
49 self.assertTrue(repo_name in response.body)
41
50
42
51
43 #test if repository was created on filesystem
52 #test if repository was created on filesystem
44 try:
53 try:
45 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
54 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
46 except:
55 except:
47 assert False , 'no repo in filesystem'
56 self.fail('no repo in filesystem')
57
48
58
59 def test_create_hg_in_group(self):
60 #TODO: write test !
61 pass
49
62
50 def test_create_git(self):
63 def test_create_git(self):
51 return
64 return
@@ -55,6 +68,8 b' class TestAdminReposController(TestContr'
55 private = False
68 private = False
56 response = self.app.post(url('repos'), {'repo_name':repo_name,
69 response = self.app.post(url('repos'), {'repo_name':repo_name,
57 'repo_type':'git',
70 'repo_type':'git',
71 'clone_uri':'',
72 'repo_group':'',
58 'description':description,
73 'description':description,
59 'private':private})
74 'private':private})
60
75
@@ -90,58 +105,74 b' class TestAdminReposController(TestContr'
90 response = self.app.put(url('repo', repo_name=HG_REPO))
105 response = self.app.put(url('repo', repo_name=HG_REPO))
91
106
92 def test_update_browser_fakeout(self):
107 def test_update_browser_fakeout(self):
93 response = self.app.post(url('repo', repo_name=HG_REPO), params=dict(_method='put'))
108 response = self.app.post(url('repo', repo_name=HG_REPO),
109 params=dict(_method='put'))
94
110
95 def test_delete(self):
111 def test_delete(self):
96 self.log_user()
112 self.log_user()
97 repo_name = 'vcs_test_new_to_delete'
113 repo_name = 'vcs_test_new_to_delete'
98 description = 'description for newly created repo'
114 description = 'description for newly created repo'
99 private = False
115 private = False
116
100 response = self.app.post(url('repos'), {'repo_name':repo_name,
117 response = self.app.post(url('repos'), {'repo_name':repo_name,
101 'repo_type':'hg',
118 'repo_type':'hg',
102 'description':description,
119 'clone_uri':'',
103 'private':private})
120 'repo_group':'',
104
121 'description':description,
122 'private':private})
123 self.assertTrue('flash' in response.session)
105
124
106 #test if we have a message for that repository
125 #test if we have a message for that repository
107 assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo'
126 self.assertTrue('''created repository %s''' % (repo_name) in
127 response.session['flash'][0])
108
128
109 #test if the repo was created in the database
129 #test if the repo was created in the database
110 new_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).one()
130 new_repo = self.sa.query(Repository).filter(Repository.repo_name ==
131 repo_name).one()
111
132
112 assert new_repo.repo_name == repo_name, 'wrong name of repo name in db'
133 self.assertEqual(new_repo.repo_name, repo_name)
113 assert new_repo.description == description, 'wrong description'
134 self.assertEqual(new_repo.description, description)
114
135
115 #test if repository is visible in the list ?
136 #test if repository is visible in the list ?
116 response = response.follow()
137 response = response.follow()
117
138
118 assert repo_name in response.body, 'missing new repo from the main repos list'
139 self.assertTrue(repo_name in response.body)
119
140
120
141
121 response = self.app.delete(url('repo', repo_name=repo_name))
142 response = self.app.delete(url('repo', repo_name=repo_name))
122
143
123 assert '''deleted repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about delete repo'
144 self.assertTrue('''deleted repository %s''' % (repo_name) in
145 response.session['flash'][0])
124
146
125 response.follow()
147 response.follow()
126
148
127 #check if repo was deleted from db
149 #check if repo was deleted from db
128 deleted_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).scalar()
150 deleted_repo = self.sa.query(Repository).filter(Repository.repo_name
151 == repo_name).scalar()
152
153 self.assertEqual(deleted_repo, None)
129
154
130 assert deleted_repo is None, 'Deleted repository was found in db'
155
156 def test_delete_repo_with_group(self):
157 #TODO:
158 pass
131
159
132
160
133 def test_delete_browser_fakeout(self):
161 def test_delete_browser_fakeout(self):
134 response = self.app.post(url('repo', repo_name=HG_REPO), params=dict(_method='delete'))
162 response = self.app.post(url('repo', repo_name=HG_REPO),
163 params=dict(_method='delete'))
135
164
136 def test_show(self):
165 def test_show(self):
137 self.log_user()
166 self.log_user()
138 response = self.app.get(url('repo', repo_name=HG_REPO))
167 response = self.app.get(url('repo', repo_name=HG_REPO))
139
168
140 def test_show_as_xml(self):
169 def test_show_as_xml(self):
141 response = self.app.get(url('formatted_repo', repo_name=HG_REPO, format='xml'))
170 response = self.app.get(url('formatted_repo', repo_name=HG_REPO,
171 format='xml'))
142
172
143 def test_edit(self):
173 def test_edit(self):
144 response = self.app.get(url('edit_repo', repo_name=HG_REPO))
174 response = self.app.get(url('edit_repo', repo_name=HG_REPO))
145
175
146 def test_edit_as_xml(self):
176 def test_edit_as_xml(self):
147 response = self.app.get(url('formatted_edit_repo', repo_name=HG_REPO, format='xml'))
177 response = self.app.get(url('formatted_edit_repo', repo_name=HG_REPO,
178 format='xml'))
@@ -1,3 +1,5 b''
1 # -*- coding: utf-8 -*-
2
1 from rhodecode.lib.auth import get_crypt_password, check_password
3 from rhodecode.lib.auth import get_crypt_password, check_password
2 from rhodecode.model.db import User, RhodeCodeSettings
4 from rhodecode.model.db import User, RhodeCodeSettings
3 from rhodecode.tests import *
5 from rhodecode.tests import *
@@ -42,7 +44,8 b' class TestAdminSettingsController(TestCo'
42 response = self.app.get(url('admin_edit_setting', setting_id=1))
44 response = self.app.get(url('admin_edit_setting', setting_id=1))
43
45
44 def test_edit_as_xml(self):
46 def test_edit_as_xml(self):
45 response = self.app.get(url('formatted_admin_edit_setting', setting_id=1, format='xml'))
47 response = self.app.get(url('formatted_admin_edit_setting',
48 setting_id=1, format='xml'))
46
49
47
50
48 def test_ga_code_active(self):
51 def test_ga_code_active(self):
@@ -58,11 +61,14 b' class TestAdminSettingsController(TestCo'
58 rhodecode_ga_code=new_ga_code
61 rhodecode_ga_code=new_ga_code
59 ))
62 ))
60
63
61 assert 'Updated application settings' in response.session['flash'][0][1], 'no flash message about success of change'
64 self.assertTrue('Updated application settings' in
62 assert RhodeCodeSettings.get_app_settings()['rhodecode_ga_code'] == new_ga_code, 'change not in database'
65 response.session['flash'][0][1])
66 self.assertEqual(RhodeCodeSettings
67 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
63
68
64 response = response.follow()
69 response = response.follow()
65 assert """_gaq.push(['_setAccount', '%s']);""" % new_ga_code in response.body
70 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
71 in response.body)
66
72
67 def test_ga_code_inactive(self):
73 def test_ga_code_inactive(self):
68 self.log_user()
74 self.log_user()
@@ -77,11 +83,14 b' class TestAdminSettingsController(TestCo'
77 rhodecode_ga_code=new_ga_code
83 rhodecode_ga_code=new_ga_code
78 ))
84 ))
79
85
80 assert 'Updated application settings' in response.session['flash'][0][1], 'no flash message about success of change'
86 self.assertTrue('Updated application settings' in
81 assert RhodeCodeSettings.get_app_settings()['rhodecode_ga_code'] == new_ga_code, 'change not in database'
87 response.session['flash'][0][1])
88 self.assertEqual(RhodeCodeSettings
89 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
82
90
83 response = response.follow()
91 response = response.follow()
84 assert """_gaq.push(['_setAccount', '%s']);""" % new_ga_code not in response.body
92 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
93 not in response.body)
85
94
86
95
87 def test_title_change(self):
96 def test_title_change(self):
@@ -89,27 +98,33 b' class TestAdminSettingsController(TestCo'
89 old_title = 'RhodeCode'
98 old_title = 'RhodeCode'
90 new_title = old_title + '_changed'
99 new_title = old_title + '_changed'
91 old_realm = 'RhodeCode authentication'
100 old_realm = 'RhodeCode authentication'
92 response = self.app.post(url('admin_setting', setting_id='global'),
101
93 params=dict(
102 for new_title in ['Changed', 'Żółwik', old_title]:
94 _method='put',
103 response = self.app.post(url('admin_setting', setting_id='global'),
95 rhodecode_title=new_title,
104 params=dict(
96 rhodecode_realm=old_realm,
105 _method='put',
97 rhodecode_ga_code=''
106 rhodecode_title=new_title,
98 ))
107 rhodecode_realm=old_realm,
108 rhodecode_ga_code=''
109 ))
99
110
100
111
101 assert 'Updated application settings' in response.session['flash'][0][1], 'no flash message about success of change'
112 self.assertTrue('Updated application settings' in
102 assert RhodeCodeSettings.get_app_settings()['rhodecode_title'] == new_title, 'change not in database'
113 response.session['flash'][0][1])
114 self.assertEqual(RhodeCodeSettings
115 .get_app_settings()['rhodecode_title'],
116 new_title.decode('utf-8'))
103
117
104 response = response.follow()
118 response = response.follow()
105 assert """<h1><a href="/">%s</a></h1>""" % new_title in response.body
119 self.assertTrue("""<h1><a href="/">%s</a></h1>""" % new_title
120 in response.body)
106
121
107
122
108 def test_my_account(self):
123 def test_my_account(self):
109 self.log_user()
124 self.log_user()
110 response = self.app.get(url('admin_settings_my_account'))
125 response = self.app.get(url('admin_settings_my_account'))
111 print response
126
112 assert 'value="test_admin' in response.body
127 self.assertTrue('value="test_admin' in response.body)
113
128
114 def test_my_account_update(self):
129 def test_my_account_update(self):
115 self.log_user()
130 self.log_user()
@@ -120,14 +135,14 b' class TestAdminSettingsController(TestCo'
120 new_password = 'test123'
135 new_password = 'test123'
121
136
122
137
123 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
138 response = self.app.post(url('admin_settings_my_account_update'),
124 _method='put',
139 params=dict(_method='put',
125 username='test_admin',
140 username='test_admin',
126 new_password=new_password,
141 new_password=new_password,
127 password='',
142 password='',
128 name=new_name,
143 name=new_name,
129 lastname=new_lastname,
144 lastname=new_lastname,
130 email=new_email,))
145 email=new_email,))
131 response.follow()
146 response.follow()
132
147
133 assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
148 assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
@@ -45,11 +45,11 b' class TestLoginController(TestController'
45
45
46 def test_login_short_password(self):
46 def test_login_short_password(self):
47 response = self.app.post(url(controller='login', action='index'),
47 response = self.app.post(url(controller='login', action='index'),
48 {'username':'error',
48 {'username':'test_admin',
49 'password':'test'})
49 'password':'as'})
50 assert response.status == '200 OK', 'Wrong response from login page'
50 self.assertEqual(response.status, '200 OK')
51 print response.body
51 print response.body
52 assert 'Enter 6 characters or more' in response.body, 'No error password message in response'
52 self.assertTrue('Enter 3 characters or more' in response.body)
53
53
54 def test_login_wrong_username_password(self):
54 def test_login_wrong_username_password(self):
55 response = self.app.post(url(controller='login', action='index'),
55 response = self.app.post(url(controller='login', action='index'),
@@ -6,22 +6,38 b' class TestSummaryController(TestControll'
6
6
7 def test_index(self):
7 def test_index(self):
8 self.log_user()
8 self.log_user()
9 response = self.app.get(url(controller='summary', action='index', repo_name=HG_REPO))
9 response = self.app.get(url(controller='summary',
10 action='index', repo_name=HG_REPO))
10
11
11 #repo type
12 #repo type
12 assert """<img style="margin-bottom:2px" class="icon" title="Mercurial repository" alt="Mercurial repository" src="/images/icons/hgicon.png"/>""" in response.body
13 self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
13 assert """<img style="margin-bottom:2px" class="icon" title="public repository" alt="public repository" src="/images/icons/lock_open.png"/>""" in response.body
14 """title="Mercurial repository" alt="Mercurial """
15 """repository" src="/images/icons/hgicon.png"/>"""
16 in response.body)
17 self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
18 """title="public repository" alt="public """
19 """repository" src="/images/icons/lock_open.png"/>"""
20 in response.body)
14
21
15 #codes stats
22 #codes stats
23 self._enable_stats()
16
24
17
25
18 self._enable_stats()
19 invalidate_cache('get_repo_cached_%s' % HG_REPO)
26 invalidate_cache('get_repo_cached_%s' % HG_REPO)
20 response = self.app.get(url(controller='summary', action='index', repo_name=HG_REPO))
27 response = self.app.get(url(controller='summary', action='index',
21 assert """var data = {"Python": 42, "Rst": 11, "Bash": 2, "Makefile": 1, "Batch": 1, "Ini": 1, "Css": 1};""" in response.body, 'wrong info about % of codes stats'
28 repo_name=HG_REPO))
29
30 self.assertTrue("""var data = {"py": {"count": 42, "desc": """
31 """["Python"]}, "rst": {"count": 11, "desc": """
32 """["Rst"]}, "sh": {"count": 2, "desc": ["Bash"]}, """
33 """"makefile": {"count": 1, "desc": ["Makefile", """
34 """"Makefile"]}, "cfg": {"count": 1, "desc": ["Ini"]},"""
35 """ "css": {"count": 1, "desc": ["Css"]}, "bat": """
36 """{"count": 1, "desc": ["Batch"]}};"""
37 in response.body)
22
38
23 # clone url...
39 # clone url...
24 assert """<input type="text" id="clone_url" readonly="readonly" value="hg clone http://test_admin@localhost:80/%s" size="70"/>""" % HG_REPO in response.body
40 self.assertTrue("""<input type="text" id="clone_url" readonly="readonly" value="hg clone http://test_admin@localhost:80/%s" size="70"/>""" % HG_REPO in response.body)
25
41
26
42
27 def _enable_stats(self):
43 def _enable_stats(self):
@@ -7,6 +7,7 b''
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 pdebug = false
10 ################################################################################
11 ################################################################################
11 ## Uncomment and replace with the address which should receive ##
12 ## Uncomment and replace with the address which should receive ##
12 ## any error reports after application crash ##
13 ## any error reports after application crash ##
General Comments 0
You need to be logged in to leave comments. Login now