##// END OF EJS Templates
Moved out reposcan into hg Model....
marcink -
r665:070f3274 beta
parent child Browse files
Show More
@@ -1,79 +1,80 b''
1 """Pylons environment configuration"""
1 """Pylons environment configuration"""
2 from mako.lookup import TemplateLookup
2 from mako.lookup import TemplateLookup
3 from pylons.configuration import PylonsConfig
3 from pylons.configuration import PylonsConfig
4 from pylons.error import handle_mako_error
4 from pylons.error import handle_mako_error
5 from rhodecode.config.routing import make_map
5 from rhodecode.config.routing import make_map
6 from rhodecode.lib.auth import set_available_permissions, set_base_path
6 from rhodecode.lib.auth import set_available_permissions, set_base_path
7 from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config
7 from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config
8 from rhodecode.model import init_model
8 from rhodecode.model import init_model
9 from rhodecode.model.hg import _get_repos_cached_initial
9 from rhodecode.model.hg import HgModel
10 from sqlalchemy import engine_from_config
10 from sqlalchemy import engine_from_config
11 import logging
11 import logging
12 import os
12 import os
13 import rhodecode.lib.app_globals as app_globals
13 import rhodecode.lib.app_globals as app_globals
14 import rhodecode.lib.helpers
14 import rhodecode.lib.helpers
15
15
16 log = logging.getLogger(__name__)
16 log = logging.getLogger(__name__)
17
17
18 def load_environment(global_conf, app_conf, initial=False):
18 def load_environment(global_conf, app_conf, initial=False):
19 """Configure the Pylons environment via the ``pylons.config``
19 """Configure the Pylons environment via the ``pylons.config``
20 object
20 object
21 """
21 """
22 config = PylonsConfig()
22 config = PylonsConfig()
23
23
24 # Pylons paths
24 # Pylons paths
25 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
25 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
26 paths = dict(root=root,
26 paths = dict(root=root,
27 controllers=os.path.join(root, 'controllers'),
27 controllers=os.path.join(root, 'controllers'),
28 static_files=os.path.join(root, 'public'),
28 static_files=os.path.join(root, 'public'),
29 templates=[os.path.join(root, 'templates')])
29 templates=[os.path.join(root, 'templates')])
30
30
31 # Initialize config with the basic options
31 # Initialize config with the basic options
32 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
32 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
33
33
34 config['routes.map'] = make_map(config)
34 config['routes.map'] = make_map(config)
35 config['pylons.app_globals'] = app_globals.Globals(config)
35 config['pylons.app_globals'] = app_globals.Globals(config)
36 config['pylons.h'] = rhodecode.lib.helpers
36 config['pylons.h'] = rhodecode.lib.helpers
37
37
38 # Setup cache object as early as possible
38 # Setup cache object as early as possible
39 import pylons
39 import pylons
40 pylons.cache._push_object(config['pylons.app_globals'].cache)
40 pylons.cache._push_object(config['pylons.app_globals'].cache)
41
41
42 # Create the Mako TemplateLookup, with the default auto-escaping
42 # Create the Mako TemplateLookup, with the default auto-escaping
43 config['pylons.app_globals'].mako_lookup = TemplateLookup(
43 config['pylons.app_globals'].mako_lookup = TemplateLookup(
44 directories=paths['templates'],
44 directories=paths['templates'],
45 error_handler=handle_mako_error,
45 error_handler=handle_mako_error,
46 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
46 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
47 input_encoding='utf-8', default_filters=['escape'],
47 input_encoding='utf-8', default_filters=['escape'],
48 imports=['from webhelpers.html import escape'])
48 imports=['from webhelpers.html import escape'])
49
49
50 #sets the c attribute access when don't existing attribute are accessed
50 #sets the c attribute access when don't existing attribute are accessed
51 config['pylons.strict_tmpl_context'] = True
51 config['pylons.strict_tmpl_context'] = True
52 test = os.path.split(config['__file__'])[-1] == 'test.ini'
52 test = os.path.split(config['__file__'])[-1] == 'test.ini'
53 if test:
53 if test:
54 from rhodecode.lib.utils import create_test_env, create_test_index
54 from rhodecode.lib.utils import create_test_env, create_test_index
55 create_test_env('/tmp', config)
55 create_test_env('/tmp', config)
56 create_test_index('/tmp', True)
56 create_test_index('/tmp', True)
57
57
58 #MULTIPLE DB configs
58 #MULTIPLE DB configs
59 # Setup the SQLAlchemy database engine
59 # Setup the SQLAlchemy database engine
60 if config['debug'] and not test:
60 if config['debug'] and not test:
61 #use query time debugging.
61 #use query time debugging.
62 from rhodecode.lib.timerproxy import TimerProxy
62 from rhodecode.lib.timerproxy import TimerProxy
63 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.',
63 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.',
64 proxy=TimerProxy())
64 proxy=TimerProxy())
65 else:
65 else:
66 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
66 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
67
67
68 init_model(sa_engine_db1)
68 init_model(sa_engine_db1)
69 #init baseui
69 #init baseui
70 config['pylons.app_globals'].baseui = make_ui('db')
70 config['pylons.app_globals'].baseui = make_ui('db')
71
71
72 repo2db_mapper(_get_repos_cached_initial(config['pylons.app_globals'], initial))
72 g = config['pylons.app_globals']
73 repo2db_mapper(HgModel().repo_scan(g.paths[0][1], g.baseui, initial))
73 set_available_permissions(config)
74 set_available_permissions(config)
74 set_base_path(config)
75 set_base_path(config)
75 set_rhodecode_config(config)
76 set_rhodecode_config(config)
76 # CONFIGURATION OPTIONS HERE (note: all config options will override
77 # CONFIGURATION OPTIONS HERE (note: all config options will override
77 # any Pylons config options)
78 # any Pylons config options)
78
79
79 return config
80 return config
@@ -1,248 +1,247 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # repos controller for pylons
3 # repos controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 7, 2010
21 Created on April 7, 2010
22 admin controller for pylons
22 admin controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from formencode import htmlfill
25 from formencode import htmlfill
26 from operator import itemgetter
26 from operator import itemgetter
27 from paste.httpexceptions import HTTPInternalServerError
27 from paste.httpexceptions import HTTPInternalServerError
28 from pylons import request, response, session, tmpl_context as c, url
28 from pylons import request, response, session, tmpl_context as c, url
29 from pylons.controllers.util import abort, redirect
29 from pylons.controllers.util import abort, redirect
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
33 HasPermissionAnyDecorator
33 HasPermissionAnyDecorator
34 from rhodecode.lib.base import BaseController, render
34 from rhodecode.lib.base import BaseController, render
35 from rhodecode.lib.utils import invalidate_cache, action_logger
35 from rhodecode.lib.utils import invalidate_cache, action_logger
36 from rhodecode.model.db import User
36 from rhodecode.model.db import User
37 from rhodecode.model.forms import RepoForm
37 from rhodecode.model.forms import RepoForm
38 from rhodecode.model.hg import HgModel
38 from rhodecode.model.hg import HgModel
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40 import formencode
40 import formencode
41 import logging
41 import logging
42 import traceback
42 import traceback
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46 class ReposController(BaseController):
46 class ReposController(BaseController):
47 """REST Controller styled on the Atom Publishing Protocol"""
47 """REST Controller styled on the Atom Publishing Protocol"""
48 # To properly map this controller, ensure your config/routing.py
48 # To properly map this controller, ensure your config/routing.py
49 # file has a resource setup:
49 # file has a resource setup:
50 # map.resource('repo', 'repos')
50 # map.resource('repo', 'repos')
51
51
52 @LoginRequired()
52 @LoginRequired()
53 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
53 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
54 def __before__(self):
54 def __before__(self):
55 c.admin_user = session.get('admin_user')
55 c.admin_user = session.get('admin_user')
56 c.admin_username = session.get('admin_username')
56 c.admin_username = session.get('admin_username')
57 super(ReposController, self).__before__()
57 super(ReposController, self).__before__()
58
58
59 @HasPermissionAllDecorator('hg.admin')
59 @HasPermissionAllDecorator('hg.admin')
60 def index(self, format='html'):
60 def index(self, format='html'):
61 """GET /repos: All items in the collection"""
61 """GET /repos: All items in the collection"""
62 # url('repos')
62 # url('repos')
63 cached_repo_list = HgModel().get_repos()
63 cached_repo_list = HgModel().get_repos()
64 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
64 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
65 return render('admin/repos/repos.html')
65 return render('admin/repos/repos.html')
66
66
67 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
67 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
68 def create(self):
68 def create(self):
69 """POST /repos: Create a new item"""
69 """POST /repos: Create a new item"""
70 # url('repos')
70 # url('repos')
71 repo_model = RepoModel()
71 repo_model = RepoModel()
72 _form = RepoForm()()
72 _form = RepoForm()()
73 form_result = {}
73 form_result = {}
74 try:
74 try:
75 form_result = _form.to_python(dict(request.POST))
75 form_result = _form.to_python(dict(request.POST))
76 repo_model.create(form_result, c.rhodecode_user)
76 repo_model.create(form_result, c.rhodecode_user)
77 invalidate_cache('cached_repo_list')
78 h.flash(_('created repository %s') % form_result['repo_name'],
77 h.flash(_('created repository %s') % form_result['repo_name'],
79 category='success')
78 category='success')
80
79
81 if request.POST.get('user_created'):
80 if request.POST.get('user_created'):
82 action_logger(self.rhodecode_user, 'user_created_repo',
81 action_logger(self.rhodecode_user, 'user_created_repo',
83 form_result['repo_name'], '', self.sa)
82 form_result['repo_name'], '', self.sa)
84 else:
83 else:
85 action_logger(self.rhodecode_user, 'admin_created_repo',
84 action_logger(self.rhodecode_user, 'admin_created_repo',
86 form_result['repo_name'], '', self.sa)
85 form_result['repo_name'], '', self.sa)
87
86
88 except formencode.Invalid, errors:
87 except formencode.Invalid, errors:
89 c.new_repo = errors.value['repo_name']
88 c.new_repo = errors.value['repo_name']
90
89
91 if request.POST.get('user_created'):
90 if request.POST.get('user_created'):
92 r = render('admin/repos/repo_add_create_repository.html')
91 r = render('admin/repos/repo_add_create_repository.html')
93 else:
92 else:
94 r = render('admin/repos/repo_add.html')
93 r = render('admin/repos/repo_add.html')
95
94
96 return htmlfill.render(
95 return htmlfill.render(
97 r,
96 r,
98 defaults=errors.value,
97 defaults=errors.value,
99 errors=errors.error_dict or {},
98 errors=errors.error_dict or {},
100 prefix_error=False,
99 prefix_error=False,
101 encoding="UTF-8")
100 encoding="UTF-8")
102
101
103 except Exception:
102 except Exception:
104 log.error(traceback.format_exc())
103 log.error(traceback.format_exc())
105 msg = _('error occured during creation of repository %s') \
104 msg = _('error occured during creation of repository %s') \
106 % form_result.get('repo_name')
105 % form_result.get('repo_name')
107 h.flash(msg, category='error')
106 h.flash(msg, category='error')
108 if request.POST.get('user_created'):
107 if request.POST.get('user_created'):
109 return redirect(url('home'))
108 return redirect(url('home'))
110 return redirect(url('repos'))
109 return redirect(url('repos'))
111
110
112 @HasPermissionAllDecorator('hg.admin')
111 @HasPermissionAllDecorator('hg.admin')
113 def new(self, format='html'):
112 def new(self, format='html'):
114 """GET /repos/new: Form to create a new item"""
113 """GET /repos/new: Form to create a new item"""
115 new_repo = request.GET.get('repo', '')
114 new_repo = request.GET.get('repo', '')
116 c.new_repo = h.repo_name_slug(new_repo)
115 c.new_repo = h.repo_name_slug(new_repo)
117
116
118 return render('admin/repos/repo_add.html')
117 return render('admin/repos/repo_add.html')
119
118
120 @HasPermissionAllDecorator('hg.admin')
119 @HasPermissionAllDecorator('hg.admin')
121 def update(self, repo_name):
120 def update(self, repo_name):
122 """PUT /repos/repo_name: Update an existing item"""
121 """PUT /repos/repo_name: Update an existing item"""
123 # Forms posted to this method should contain a hidden field:
122 # Forms posted to this method should contain a hidden field:
124 # <input type="hidden" name="_method" value="PUT" />
123 # <input type="hidden" name="_method" value="PUT" />
125 # Or using helpers:
124 # Or using helpers:
126 # h.form(url('repo', repo_name=ID),
125 # h.form(url('repo', repo_name=ID),
127 # method='put')
126 # method='put')
128 # url('repo', repo_name=ID)
127 # url('repo', repo_name=ID)
129 repo_model = RepoModel()
128 repo_model = RepoModel()
130 changed_name = repo_name
129 changed_name = repo_name
131 _form = RepoForm(edit=True, old_data={'repo_name':repo_name})()
130 _form = RepoForm(edit=True, old_data={'repo_name':repo_name})()
132
131
133 try:
132 try:
134 form_result = _form.to_python(dict(request.POST))
133 form_result = _form.to_python(dict(request.POST))
135 repo_model.update(repo_name, form_result)
134 repo_model.update(repo_name, form_result)
136 invalidate_cache('cached_repo_list')
135 invalidate_cache('get_repo_cached_%s' % repo_name)
137 h.flash(_('Repository %s updated succesfully' % repo_name),
136 h.flash(_('Repository %s updated succesfully' % repo_name),
138 category='success')
137 category='success')
139 changed_name = form_result['repo_name']
138 changed_name = form_result['repo_name']
140 action_logger(self.rhodecode_user, 'admin_updated_repo',
139 action_logger(self.rhodecode_user, 'admin_updated_repo',
141 changed_name, '', self.sa)
140 changed_name, '', self.sa)
142
141
143 except formencode.Invalid, errors:
142 except formencode.Invalid, errors:
144 c.repo_info = repo_model.get(repo_name)
143 c.repo_info = repo_model.get(repo_name)
145 c.users_array = repo_model.get_users_js()
144 c.users_array = repo_model.get_users_js()
146 errors.value.update({'user':c.repo_info.user.username})
145 errors.value.update({'user':c.repo_info.user.username})
147 return htmlfill.render(
146 return htmlfill.render(
148 render('admin/repos/repo_edit.html'),
147 render('admin/repos/repo_edit.html'),
149 defaults=errors.value,
148 defaults=errors.value,
150 errors=errors.error_dict or {},
149 errors=errors.error_dict or {},
151 prefix_error=False,
150 prefix_error=False,
152 encoding="UTF-8")
151 encoding="UTF-8")
153
152
154 except Exception:
153 except Exception:
155 log.error(traceback.format_exc())
154 log.error(traceback.format_exc())
156 h.flash(_('error occured during update of repository %s') \
155 h.flash(_('error occured during update of repository %s') \
157 % repo_name, category='error')
156 % repo_name, category='error')
158
157
159 return redirect(url('edit_repo', repo_name=changed_name))
158 return redirect(url('edit_repo', repo_name=changed_name))
160
159
161 @HasPermissionAllDecorator('hg.admin')
160 @HasPermissionAllDecorator('hg.admin')
162 def delete(self, repo_name):
161 def delete(self, repo_name):
163 """DELETE /repos/repo_name: Delete an existing item"""
162 """DELETE /repos/repo_name: Delete an existing item"""
164 # Forms posted to this method should contain a hidden field:
163 # Forms posted to this method should contain a hidden field:
165 # <input type="hidden" name="_method" value="DELETE" />
164 # <input type="hidden" name="_method" value="DELETE" />
166 # Or using helpers:
165 # Or using helpers:
167 # h.form(url('repo', repo_name=ID),
166 # h.form(url('repo', repo_name=ID),
168 # method='delete')
167 # method='delete')
169 # url('repo', repo_name=ID)
168 # url('repo', repo_name=ID)
170
169
171 repo_model = RepoModel()
170 repo_model = RepoModel()
172 repo = repo_model.get(repo_name)
171 repo = repo_model.get(repo_name)
173 if not repo:
172 if not repo:
174 h.flash(_('%s repository is not mapped to db perhaps'
173 h.flash(_('%s repository is not mapped to db perhaps'
175 ' it was moved or renamed from the filesystem'
174 ' it was moved or renamed from the filesystem'
176 ' please run the application again'
175 ' please run the application again'
177 ' in order to rescan repositories') % repo_name,
176 ' in order to rescan repositories') % repo_name,
178 category='error')
177 category='error')
179
178
180 return redirect(url('repos'))
179 return redirect(url('repos'))
181 try:
180 try:
182 action_logger(self.rhodecode_user, 'admin_deleted_repo',
181 action_logger(self.rhodecode_user, 'admin_deleted_repo',
183 repo_name, '', self.sa)
182 repo_name, '', self.sa)
184 repo_model.delete(repo)
183 repo_model.delete(repo)
185 invalidate_cache('cached_repo_list')
184 invalidate_cache('get_repo_cached_%s' % repo_name)
186 h.flash(_('deleted repository %s') % repo_name, category='success')
185 h.flash(_('deleted repository %s') % repo_name, category='success')
187
186
188 except Exception, e:
187 except Exception, e:
189 log.error(traceback.format_exc())
188 log.error(traceback.format_exc())
190 h.flash(_('An error occured during deletion of %s') % repo_name,
189 h.flash(_('An error occured during deletion of %s') % repo_name,
191 category='error')
190 category='error')
192
191
193 return redirect(url('repos'))
192 return redirect(url('repos'))
194
193
195 @HasPermissionAllDecorator('hg.admin')
194 @HasPermissionAllDecorator('hg.admin')
196 def delete_perm_user(self, repo_name):
195 def delete_perm_user(self, repo_name):
197 """
196 """
198 DELETE an existing repository permission user
197 DELETE an existing repository permission user
199 :param repo_name:
198 :param repo_name:
200 """
199 """
201
200
202 try:
201 try:
203 repo_model = RepoModel()
202 repo_model = RepoModel()
204 repo_model.delete_perm_user(request.POST, repo_name)
203 repo_model.delete_perm_user(request.POST, repo_name)
205 except Exception, e:
204 except Exception, e:
206 h.flash(_('An error occured during deletion of repository user'),
205 h.flash(_('An error occured during deletion of repository user'),
207 category='error')
206 category='error')
208 raise HTTPInternalServerError()
207 raise HTTPInternalServerError()
209
208
210 @HasPermissionAllDecorator('hg.admin')
209 @HasPermissionAllDecorator('hg.admin')
211 def show(self, repo_name, format='html'):
210 def show(self, repo_name, format='html'):
212 """GET /repos/repo_name: Show a specific item"""
211 """GET /repos/repo_name: Show a specific item"""
213 # url('repo', repo_name=ID)
212 # url('repo', repo_name=ID)
214
213
215 @HasPermissionAllDecorator('hg.admin')
214 @HasPermissionAllDecorator('hg.admin')
216 def edit(self, repo_name, format='html'):
215 def edit(self, repo_name, format='html'):
217 """GET /repos/repo_name/edit: Form to edit an existing item"""
216 """GET /repos/repo_name/edit: Form to edit an existing item"""
218 # url('edit_repo', repo_name=ID)
217 # url('edit_repo', repo_name=ID)
219 repo_model = RepoModel()
218 repo_model = RepoModel()
220 c.repo_info = repo = repo_model.get(repo_name)
219 c.repo_info = repo = repo_model.get(repo_name)
221 if not repo:
220 if not repo:
222 h.flash(_('%s repository is not mapped to db perhaps'
221 h.flash(_('%s repository is not mapped to db perhaps'
223 ' it was created or renamed from the filesystem'
222 ' it was created or renamed from the filesystem'
224 ' please run the application again'
223 ' please run the application again'
225 ' in order to rescan repositories') % repo_name,
224 ' in order to rescan repositories') % repo_name,
226 category='error')
225 category='error')
227
226
228 return redirect(url('repos'))
227 return redirect(url('repos'))
229 defaults = c.repo_info.__dict__
228 defaults = c.repo_info.__dict__
230 if c.repo_info.user:
229 if c.repo_info.user:
231 defaults.update({'user':c.repo_info.user.username})
230 defaults.update({'user':c.repo_info.user.username})
232 else:
231 else:
233 replacement_user = self.sa.query(User)\
232 replacement_user = self.sa.query(User)\
234 .filter(User.admin == True).first().username
233 .filter(User.admin == True).first().username
235 defaults.update({'user':replacement_user})
234 defaults.update({'user':replacement_user})
236
235
237 c.users_array = repo_model.get_users_js()
236 c.users_array = repo_model.get_users_js()
238
237
239 for p in c.repo_info.repo_to_perm:
238 for p in c.repo_info.repo_to_perm:
240 defaults.update({'perm_%s' % p.user.username:
239 defaults.update({'perm_%s' % p.user.username:
241 p.permission.permission_name})
240 p.permission.permission_name})
242
241
243 return htmlfill.render(
242 return htmlfill.render(
244 render('admin/repos/repo_edit.html'),
243 render('admin/repos/repo_edit.html'),
245 defaults=defaults,
244 defaults=defaults,
246 encoding="UTF-8",
245 encoding="UTF-8",
247 force_defaults=False
246 force_defaults=False
248 )
247 )
@@ -1,306 +1,312 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # settings controller for pylons
3 # settings controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on July 14, 2010
21 Created on July 14, 2010
22 settings controller for pylons
22 settings controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from formencode import htmlfill
25 from formencode import htmlfill
26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
27 config
27 config
28 from pylons.controllers.util import abort, redirect
28 from pylons.controllers.util import abort, redirect
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
31 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
32 HasPermissionAnyDecorator
32 HasPermissionAnyDecorator
33 from rhodecode.lib.base import BaseController, render
33 from rhodecode.lib.base import BaseController, render
34 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
34 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
35 set_rhodecode_config, get_hg_settings, get_hg_ui_settings
35 set_rhodecode_config, get_hg_settings, get_hg_ui_settings
36 from rhodecode.model.db import RhodeCodeSettings, RhodeCodeUi
36 from rhodecode.model.db import RhodeCodeSettings, RhodeCodeUi, Repository
37 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
37 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
38 ApplicationUiSettingsForm
38 ApplicationUiSettingsForm
39 from rhodecode.model.hg import HgModel
39 from rhodecode.model.hg import HgModel
40 from rhodecode.model.user import UserModel
40 from rhodecode.model.user import UserModel
41 from rhodecode.lib.celerylib import tasks, run_task
41 from rhodecode.lib.celerylib import tasks, run_task
42 from sqlalchemy import func
42 import formencode
43 import formencode
43 import logging
44 import logging
44 import traceback
45 import traceback
45
46
46 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
47
48
48
49
49 class SettingsController(BaseController):
50 class SettingsController(BaseController):
50 """REST Controller styled on the Atom Publishing Protocol"""
51 """REST Controller styled on the Atom Publishing Protocol"""
51 # To properly map this controller, ensure your config/routing.py
52 # To properly map this controller, ensure your config/routing.py
52 # file has a resource setup:
53 # file has a resource setup:
53 # map.resource('setting', 'settings', controller='admin/settings',
54 # map.resource('setting', 'settings', controller='admin/settings',
54 # path_prefix='/admin', name_prefix='admin_')
55 # path_prefix='/admin', name_prefix='admin_')
55
56
56
57
57 @LoginRequired()
58 @LoginRequired()
58 def __before__(self):
59 def __before__(self):
59 c.admin_user = session.get('admin_user')
60 c.admin_user = session.get('admin_user')
60 c.admin_username = session.get('admin_username')
61 c.admin_username = session.get('admin_username')
61 super(SettingsController, self).__before__()
62 super(SettingsController, self).__before__()
62
63
63
64
64 @HasPermissionAllDecorator('hg.admin')
65 @HasPermissionAllDecorator('hg.admin')
65 def index(self, format='html'):
66 def index(self, format='html'):
66 """GET /admin/settings: All items in the collection"""
67 """GET /admin/settings: All items in the collection"""
67 # url('admin_settings')
68 # url('admin_settings')
68
69
69 defaults = get_hg_settings()
70 defaults = get_hg_settings()
70 defaults.update(get_hg_ui_settings())
71 defaults.update(get_hg_ui_settings())
71 return htmlfill.render(
72 return htmlfill.render(
72 render('admin/settings/settings.html'),
73 render('admin/settings/settings.html'),
73 defaults=defaults,
74 defaults=defaults,
74 encoding="UTF-8",
75 encoding="UTF-8",
75 force_defaults=False
76 force_defaults=False
76 )
77 )
77
78
78 @HasPermissionAllDecorator('hg.admin')
79 @HasPermissionAllDecorator('hg.admin')
79 def create(self):
80 def create(self):
80 """POST /admin/settings: Create a new item"""
81 """POST /admin/settings: Create a new item"""
81 # url('admin_settings')
82 # url('admin_settings')
82
83
83 @HasPermissionAllDecorator('hg.admin')
84 @HasPermissionAllDecorator('hg.admin')
84 def new(self, format='html'):
85 def new(self, format='html'):
85 """GET /admin/settings/new: Form to create a new item"""
86 """GET /admin/settings/new: Form to create a new item"""
86 # url('admin_new_setting')
87 # url('admin_new_setting')
87
88
88 @HasPermissionAllDecorator('hg.admin')
89 @HasPermissionAllDecorator('hg.admin')
89 def update(self, setting_id):
90 def update(self, setting_id):
90 """PUT /admin/settings/setting_id: Update an existing item"""
91 """PUT /admin/settings/setting_id: Update an existing item"""
91 # Forms posted to this method should contain a hidden field:
92 # Forms posted to this method should contain a hidden field:
92 # <input type="hidden" name="_method" value="PUT" />
93 # <input type="hidden" name="_method" value="PUT" />
93 # Or using helpers:
94 # Or using helpers:
94 # h.form(url('admin_setting', setting_id=ID),
95 # h.form(url('admin_setting', setting_id=ID),
95 # method='put')
96 # method='put')
96 # url('admin_setting', setting_id=ID)
97 # url('admin_setting', setting_id=ID)
97 if setting_id == 'mapping':
98 if setting_id == 'mapping':
98 rm_obsolete = request.POST.get('destroy', False)
99 rm_obsolete = request.POST.get('destroy', False)
99 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
100 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
100
101
101 initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
102 initial = HgModel().repo_scan(g.paths[0][1], g.baseui)
103 for repo_name in initial.keys():
104 invalidate_cache('get_repo_cached_%s' % repo_name)
105
102 repo2db_mapper(initial, rm_obsolete)
106 repo2db_mapper(initial, rm_obsolete)
103 invalidate_cache('cached_repo_list')
107
104 h.flash(_('Repositories successfully rescanned'), category='success')
108 h.flash(_('Repositories successfully rescanned'), category='success')
105
109
106 if setting_id == 'whoosh':
110 if setting_id == 'whoosh':
107 repo_location = get_hg_ui_settings()['paths_root_path']
111 repo_location = get_hg_ui_settings()['paths_root_path']
108 full_index = request.POST.get('full_index', False)
112 full_index = request.POST.get('full_index', False)
109 task = run_task(tasks.whoosh_index, repo_location, full_index)
113 task = run_task(tasks.whoosh_index, repo_location, full_index)
110
114
111 h.flash(_('Whoosh reindex task scheduled'), category='success')
115 h.flash(_('Whoosh reindex task scheduled'), category='success')
112 if setting_id == 'global':
116 if setting_id == 'global':
113
117
114 application_form = ApplicationSettingsForm()()
118 application_form = ApplicationSettingsForm()()
115 try:
119 try:
116 form_result = application_form.to_python(dict(request.POST))
120 form_result = application_form.to_python(dict(request.POST))
117
121
118 try:
122 try:
119 hgsettings1 = self.sa.query(RhodeCodeSettings)\
123 hgsettings1 = self.sa.query(RhodeCodeSettings)\
120 .filter(RhodeCodeSettings.app_settings_name == 'title').one()
124 .filter(RhodeCodeSettings.app_settings_name == 'title').one()
121 hgsettings1.app_settings_value = form_result['rhodecode_title']
125 hgsettings1.app_settings_value = form_result['rhodecode_title']
122
126
123 hgsettings2 = self.sa.query(RhodeCodeSettings)\
127 hgsettings2 = self.sa.query(RhodeCodeSettings)\
124 .filter(RhodeCodeSettings.app_settings_name == 'realm').one()
128 .filter(RhodeCodeSettings.app_settings_name == 'realm').one()
125 hgsettings2.app_settings_value = form_result['rhodecode_realm']
129 hgsettings2.app_settings_value = form_result['rhodecode_realm']
126
130
127
131
128 self.sa.add(hgsettings1)
132 self.sa.add(hgsettings1)
129 self.sa.add(hgsettings2)
133 self.sa.add(hgsettings2)
130 self.sa.commit()
134 self.sa.commit()
131 set_rhodecode_config(config)
135 set_rhodecode_config(config)
132 h.flash(_('Updated application settings'),
136 h.flash(_('Updated application settings'),
133 category='success')
137 category='success')
134
138
135 except:
139 except:
136 log.error(traceback.format_exc())
140 log.error(traceback.format_exc())
137 h.flash(_('error occurred during updating application settings'),
141 h.flash(_('error occurred during updating application settings'),
138 category='error')
142 category='error')
139
143
140 self.sa.rollback()
144 self.sa.rollback()
141
145
142
146
143 except formencode.Invalid, errors:
147 except formencode.Invalid, errors:
144 return htmlfill.render(
148 return htmlfill.render(
145 render('admin/settings/settings.html'),
149 render('admin/settings/settings.html'),
146 defaults=errors.value,
150 defaults=errors.value,
147 errors=errors.error_dict or {},
151 errors=errors.error_dict or {},
148 prefix_error=False,
152 prefix_error=False,
149 encoding="UTF-8")
153 encoding="UTF-8")
150
154
151 if setting_id == 'mercurial':
155 if setting_id == 'mercurial':
152 application_form = ApplicationUiSettingsForm()()
156 application_form = ApplicationUiSettingsForm()()
153 try:
157 try:
154 form_result = application_form.to_python(dict(request.POST))
158 form_result = application_form.to_python(dict(request.POST))
155
159
156 try:
160 try:
157
161
158 hgsettings1 = self.sa.query(RhodeCodeUi)\
162 hgsettings1 = self.sa.query(RhodeCodeUi)\
159 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
163 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
160 hgsettings1.ui_value = form_result['web_push_ssl']
164 hgsettings1.ui_value = form_result['web_push_ssl']
161
165
162 hgsettings2 = self.sa.query(RhodeCodeUi)\
166 hgsettings2 = self.sa.query(RhodeCodeUi)\
163 .filter(RhodeCodeUi.ui_key == '/').one()
167 .filter(RhodeCodeUi.ui_key == '/').one()
164 hgsettings2.ui_value = form_result['paths_root_path']
168 hgsettings2.ui_value = form_result['paths_root_path']
165
169
166
170
167 #HOOKS
171 #HOOKS
168 hgsettings3 = self.sa.query(RhodeCodeUi)\
172 hgsettings3 = self.sa.query(RhodeCodeUi)\
169 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
173 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
170 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
174 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
171
175
172 hgsettings4 = self.sa.query(RhodeCodeUi)\
176 hgsettings4 = self.sa.query(RhodeCodeUi)\
173 .filter(RhodeCodeUi.ui_key == 'changegroup.repo_size').one()
177 .filter(RhodeCodeUi.ui_key == 'changegroup.repo_size').one()
174 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
178 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
175
179
176 hgsettings5 = self.sa.query(RhodeCodeUi)\
180 hgsettings5 = self.sa.query(RhodeCodeUi)\
177 .filter(RhodeCodeUi.ui_key == 'pretxnchangegroup.push_logger').one()
181 .filter(RhodeCodeUi.ui_key == 'pretxnchangegroup.push_logger').one()
178 hgsettings5.ui_active = bool(form_result['hooks_pretxnchangegroup_push_logger'])
182 hgsettings5.ui_active = bool(form_result['hooks_pretxnchangegroup_push_logger'])
179
183
180 hgsettings6 = self.sa.query(RhodeCodeUi)\
184 hgsettings6 = self.sa.query(RhodeCodeUi)\
181 .filter(RhodeCodeUi.ui_key == 'preoutgoing.pull_logger').one()
185 .filter(RhodeCodeUi.ui_key == 'preoutgoing.pull_logger').one()
182 hgsettings6.ui_active = bool(form_result['hooks_preoutgoing_pull_logger'])
186 hgsettings6.ui_active = bool(form_result['hooks_preoutgoing_pull_logger'])
183
187
184
188
185 self.sa.add(hgsettings1)
189 self.sa.add(hgsettings1)
186 self.sa.add(hgsettings2)
190 self.sa.add(hgsettings2)
187 self.sa.add(hgsettings3)
191 self.sa.add(hgsettings3)
188 self.sa.add(hgsettings4)
192 self.sa.add(hgsettings4)
189 self.sa.add(hgsettings5)
193 self.sa.add(hgsettings5)
190 self.sa.add(hgsettings6)
194 self.sa.add(hgsettings6)
191 self.sa.commit()
195 self.sa.commit()
192
196
193 h.flash(_('Updated mercurial settings'),
197 h.flash(_('Updated mercurial settings'),
194 category='success')
198 category='success')
195
199
196 except:
200 except:
197 log.error(traceback.format_exc())
201 log.error(traceback.format_exc())
198 h.flash(_('error occurred during updating application settings'),
202 h.flash(_('error occurred during updating application settings'),
199 category='error')
203 category='error')
200
204
201 self.sa.rollback()
205 self.sa.rollback()
202
206
203
207
204 except formencode.Invalid, errors:
208 except formencode.Invalid, errors:
205 return htmlfill.render(
209 return htmlfill.render(
206 render('admin/settings/settings.html'),
210 render('admin/settings/settings.html'),
207 defaults=errors.value,
211 defaults=errors.value,
208 errors=errors.error_dict or {},
212 errors=errors.error_dict or {},
209 prefix_error=False,
213 prefix_error=False,
210 encoding="UTF-8")
214 encoding="UTF-8")
211
215
212
216
213
217
214 return redirect(url('admin_settings'))
218 return redirect(url('admin_settings'))
215
219
216 @HasPermissionAllDecorator('hg.admin')
220 @HasPermissionAllDecorator('hg.admin')
217 def delete(self, setting_id):
221 def delete(self, setting_id):
218 """DELETE /admin/settings/setting_id: Delete an existing item"""
222 """DELETE /admin/settings/setting_id: Delete an existing item"""
219 # Forms posted to this method should contain a hidden field:
223 # Forms posted to this method should contain a hidden field:
220 # <input type="hidden" name="_method" value="DELETE" />
224 # <input type="hidden" name="_method" value="DELETE" />
221 # Or using helpers:
225 # Or using helpers:
222 # h.form(url('admin_setting', setting_id=ID),
226 # h.form(url('admin_setting', setting_id=ID),
223 # method='delete')
227 # method='delete')
224 # url('admin_setting', setting_id=ID)
228 # url('admin_setting', setting_id=ID)
225
229
226 @HasPermissionAllDecorator('hg.admin')
230 @HasPermissionAllDecorator('hg.admin')
227 def show(self, setting_id, format='html'):
231 def show(self, setting_id, format='html'):
228 """GET /admin/settings/setting_id: Show a specific item"""
232 """GET /admin/settings/setting_id: Show a specific item"""
229 # url('admin_setting', setting_id=ID)
233 # url('admin_setting', setting_id=ID)
230
234
231 @HasPermissionAllDecorator('hg.admin')
235 @HasPermissionAllDecorator('hg.admin')
232 def edit(self, setting_id, format='html'):
236 def edit(self, setting_id, format='html'):
233 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
237 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
234 # url('admin_edit_setting', setting_id=ID)
238 # url('admin_edit_setting', setting_id=ID)
235
239
236
240
237 def my_account(self):
241 def my_account(self):
238 """
242 """
239 GET /_admin/my_account Displays info about my account
243 GET /_admin/my_account Displays info about my account
240 """
244 """
245
241 # url('admin_settings_my_account')
246 # url('admin_settings_my_account')
242 c.user = UserModel(self.sa).get(c.rhodecode_user.user_id, cache=False)
247 c.user = UserModel(self.sa).get(c.rhodecode_user.user_id, cache=False)
243 c.user_repos = []
248 all_repos = self.sa.query(Repository)\
244 for repo in c.cached_repo_list.values():
249 .filter(Repository.user_id == c.user.user_id)\
245 if repo.dbrepo.user.username == c.user.username:
250 .order_by(func.lower(Repository.repo_name))\
246 c.user_repos.append(repo)
251 .all()
252 c.user_repos = HgModel().get_repos(all_repos)
247
253
248 if c.user.username == 'default':
254 if c.user.username == 'default':
249 h.flash(_("You can't edit this user since it's"
255 h.flash(_("You can't edit this user since it's"
250 " crucial for entire application"), category='warning')
256 " crucial for entire application"), category='warning')
251 return redirect(url('users'))
257 return redirect(url('users'))
252
258
253 defaults = c.user.__dict__
259 defaults = c.user.__dict__
254 return htmlfill.render(
260 return htmlfill.render(
255 render('admin/users/user_edit_my_account.html'),
261 render('admin/users/user_edit_my_account.html'),
256 defaults=defaults,
262 defaults=defaults,
257 encoding="UTF-8",
263 encoding="UTF-8",
258 force_defaults=False
264 force_defaults=False
259 )
265 )
260
266
261 def my_account_update(self):
267 def my_account_update(self):
262 """PUT /_admin/my_account_update: Update an existing item"""
268 """PUT /_admin/my_account_update: Update an existing item"""
263 # Forms posted to this method should contain a hidden field:
269 # Forms posted to this method should contain a hidden field:
264 # <input type="hidden" name="_method" value="PUT" />
270 # <input type="hidden" name="_method" value="PUT" />
265 # Or using helpers:
271 # Or using helpers:
266 # h.form(url('admin_settings_my_account_update'),
272 # h.form(url('admin_settings_my_account_update'),
267 # method='put')
273 # method='put')
268 # url('admin_settings_my_account_update', id=ID)
274 # url('admin_settings_my_account_update', id=ID)
269 user_model = UserModel()
275 user_model = UserModel()
270 uid = c.rhodecode_user.user_id
276 uid = c.rhodecode_user.user_id
271 _form = UserForm(edit=True, old_data={'user_id':uid,
277 _form = UserForm(edit=True, old_data={'user_id':uid,
272 'email':c.rhodecode_user.email})()
278 'email':c.rhodecode_user.email})()
273 form_result = {}
279 form_result = {}
274 try:
280 try:
275 form_result = _form.to_python(dict(request.POST))
281 form_result = _form.to_python(dict(request.POST))
276 user_model.update_my_account(uid, form_result)
282 user_model.update_my_account(uid, form_result)
277 h.flash(_('Your account was updated succesfully'),
283 h.flash(_('Your account was updated succesfully'),
278 category='success')
284 category='success')
279
285
280 except formencode.Invalid, errors:
286 except formencode.Invalid, errors:
281 c.user = user_model.get(c.rhodecode_user.user_id, cache=False)
287 c.user = user_model.get(c.rhodecode_user.user_id, cache=False)
282 c.user_repos = []
288 c.user_repos = []
283 for repo in c.cached_repo_list.values():
289 for repo in c.cached_repo_list.values():
284 if repo.dbrepo.user.username == c.user.username:
290 if repo.dbrepo.user.username == c.user.username:
285 c.user_repos.append(repo)
291 c.user_repos.append(repo)
286 return htmlfill.render(
292 return htmlfill.render(
287 render('admin/users/user_edit_my_account.html'),
293 render('admin/users/user_edit_my_account.html'),
288 defaults=errors.value,
294 defaults=errors.value,
289 errors=errors.error_dict or {},
295 errors=errors.error_dict or {},
290 prefix_error=False,
296 prefix_error=False,
291 encoding="UTF-8")
297 encoding="UTF-8")
292 except Exception:
298 except Exception:
293 log.error(traceback.format_exc())
299 log.error(traceback.format_exc())
294 h.flash(_('error occured during update of user %s') \
300 h.flash(_('error occured during update of user %s') \
295 % form_result.get('username'), category='error')
301 % form_result.get('username'), category='error')
296
302
297 return redirect(url('my_account'))
303 return redirect(url('my_account'))
298
304
299 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
305 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
300 def create_repository(self):
306 def create_repository(self):
301 """GET /_admin/create_repository: Form to create a new item"""
307 """GET /_admin/create_repository: Form to create a new item"""
302 new_repo = request.GET.get('repo', '')
308 new_repo = request.GET.get('repo', '')
303 c.new_repo = h.repo_name_slug(new_repo)
309 c.new_repo = h.repo_name_slug(new_repo)
304
310
305 return render('admin/repos/repo_add_create_repository.html')
311 return render('admin/repos/repo_add_create_repository.html')
306
312
@@ -1,177 +1,177 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # settings controller for pylons
3 # settings controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on June 30, 2010
21 Created on June 30, 2010
22 settings controller for pylons
22 settings controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from formencode import htmlfill
25 from formencode import htmlfill
26 from pylons import tmpl_context as c, request, url
26 from pylons import tmpl_context as c, request, url
27 from pylons.controllers.util import redirect
27 from pylons.controllers.util import redirect
28 from pylons.i18n.translation import _
28 from pylons.i18n.translation import _
29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
30 from rhodecode.lib.base import BaseController, render
30 from rhodecode.lib.base import BaseController, render
31 from rhodecode.lib.utils import invalidate_cache, action_logger
31 from rhodecode.lib.utils import invalidate_cache, action_logger
32 from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
32 from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
33 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo import RepoModel
34 import formencode
34 import formencode
35 import logging
35 import logging
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37 import traceback
37 import traceback
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41 class SettingsController(BaseController):
41 class SettingsController(BaseController):
42
42
43 @LoginRequired()
43 @LoginRequired()
44 @HasRepoPermissionAllDecorator('repository.admin')
44 @HasRepoPermissionAllDecorator('repository.admin')
45 def __before__(self):
45 def __before__(self):
46 super(SettingsController, self).__before__()
46 super(SettingsController, self).__before__()
47
47
48 def index(self, repo_name):
48 def index(self, repo_name):
49 repo_model = RepoModel()
49 repo_model = RepoModel()
50 c.repo_info = repo = repo_model.get(repo_name)
50 c.repo_info = repo = repo_model.get(repo_name)
51 if not repo:
51 if not repo:
52 h.flash(_('%s repository is not mapped to db perhaps'
52 h.flash(_('%s repository is not mapped to db perhaps'
53 ' it was created or renamed from the filesystem'
53 ' it was created or renamed from the filesystem'
54 ' please run the application again'
54 ' please run the application again'
55 ' in order to rescan repositories') % repo_name,
55 ' in order to rescan repositories') % repo_name,
56 category='error')
56 category='error')
57
57
58 return redirect(url('home'))
58 return redirect(url('home'))
59 defaults = c.repo_info.__dict__
59 defaults = c.repo_info.__dict__
60 defaults.update({'user':c.repo_info.user.username})
60 defaults.update({'user':c.repo_info.user.username})
61 c.users_array = repo_model.get_users_js()
61 c.users_array = repo_model.get_users_js()
62
62
63 for p in c.repo_info.repo_to_perm:
63 for p in c.repo_info.repo_to_perm:
64 defaults.update({'perm_%s' % p.user.username:
64 defaults.update({'perm_%s' % p.user.username:
65 p.permission.permission_name})
65 p.permission.permission_name})
66
66
67 return htmlfill.render(
67 return htmlfill.render(
68 render('settings/repo_settings.html'),
68 render('settings/repo_settings.html'),
69 defaults=defaults,
69 defaults=defaults,
70 encoding="UTF-8",
70 encoding="UTF-8",
71 force_defaults=False
71 force_defaults=False
72 )
72 )
73
73
74 def update(self, repo_name):
74 def update(self, repo_name):
75 repo_model = RepoModel()
75 repo_model = RepoModel()
76 changed_name = repo_name
76 changed_name = repo_name
77 _form = RepoSettingsForm(edit=True, old_data={'repo_name':repo_name})()
77 _form = RepoSettingsForm(edit=True, old_data={'repo_name':repo_name})()
78 try:
78 try:
79 form_result = _form.to_python(dict(request.POST))
79 form_result = _form.to_python(dict(request.POST))
80 repo_model.update(repo_name, form_result)
80 repo_model.update(repo_name, form_result)
81 invalidate_cache('cached_repo_list')
81 invalidate_cache('get_repo_cached_%s' % repo_name)
82 h.flash(_('Repository %s updated successfully' % repo_name),
82 h.flash(_('Repository %s updated successfully' % repo_name),
83 category='success')
83 category='success')
84 changed_name = form_result['repo_name']
84 changed_name = form_result['repo_name']
85 action_logger(self.rhodecode_user, 'user_updated_repo',
85 action_logger(self.rhodecode_user, 'user_updated_repo',
86 changed_name, '', self.sa)
86 changed_name, '', self.sa)
87 except formencode.Invalid, errors:
87 except formencode.Invalid, errors:
88 c.repo_info = repo_model.get(repo_name)
88 c.repo_info = repo_model.get(repo_name)
89 c.users_array = repo_model.get_users_js()
89 c.users_array = repo_model.get_users_js()
90 errors.value.update({'user':c.repo_info.user.username})
90 errors.value.update({'user':c.repo_info.user.username})
91 return htmlfill.render(
91 return htmlfill.render(
92 render('settings/repo_settings.html'),
92 render('settings/repo_settings.html'),
93 defaults=errors.value,
93 defaults=errors.value,
94 errors=errors.error_dict or {},
94 errors=errors.error_dict or {},
95 prefix_error=False,
95 prefix_error=False,
96 encoding="UTF-8")
96 encoding="UTF-8")
97 except Exception:
97 except Exception:
98 log.error(traceback.format_exc())
98 log.error(traceback.format_exc())
99 h.flash(_('error occured during update of repository %s') \
99 h.flash(_('error occurred during update of repository %s') \
100 % repo_name, category='error')
100 % repo_name, category='error')
101
101
102 return redirect(url('repo_settings_home', repo_name=changed_name))
102 return redirect(url('repo_settings_home', repo_name=changed_name))
103
103
104
104
105
105
106 def delete(self, repo_name):
106 def delete(self, repo_name):
107 """DELETE /repos/repo_name: Delete an existing item"""
107 """DELETE /repos/repo_name: Delete an existing item"""
108 # Forms posted to this method should contain a hidden field:
108 # Forms posted to this method should contain a hidden field:
109 # <input type="hidden" name="_method" value="DELETE" />
109 # <input type="hidden" name="_method" value="DELETE" />
110 # Or using helpers:
110 # Or using helpers:
111 # h.form(url('repo_settings_delete', repo_name=ID),
111 # h.form(url('repo_settings_delete', repo_name=ID),
112 # method='delete')
112 # method='delete')
113 # url('repo_settings_delete', repo_name=ID)
113 # url('repo_settings_delete', repo_name=ID)
114
114
115 repo_model = RepoModel()
115 repo_model = RepoModel()
116 repo = repo_model.get(repo_name)
116 repo = repo_model.get(repo_name)
117 if not repo:
117 if not repo:
118 h.flash(_('%s repository is not mapped to db perhaps'
118 h.flash(_('%s repository is not mapped to db perhaps'
119 ' it was moved or renamed from the filesystem'
119 ' it was moved or renamed from the filesystem'
120 ' please run the application again'
120 ' please run the application again'
121 ' in order to rescan repositories') % repo_name,
121 ' in order to rescan repositories') % repo_name,
122 category='error')
122 category='error')
123
123
124 return redirect(url('home'))
124 return redirect(url('home'))
125 try:
125 try:
126 action_logger(self.rhodecode_user, 'user_deleted_repo',
126 action_logger(self.rhodecode_user, 'user_deleted_repo',
127 repo_name, '', self.sa)
127 repo_name, '', self.sa)
128 repo_model.delete(repo)
128 repo_model.delete(repo)
129 invalidate_cache('cached_repo_list')
129 invalidate_cache('get_repo_cached_%s' % repo_name)
130 h.flash(_('deleted repository %s') % repo_name, category='success')
130 h.flash(_('deleted repository %s') % repo_name, category='success')
131 except Exception:
131 except Exception:
132 h.flash(_('An error occurred during deletion of %s') % repo_name,
132 h.flash(_('An error occurred during deletion of %s') % repo_name,
133 category='error')
133 category='error')
134
134
135 return redirect(url('home'))
135 return redirect(url('home'))
136
136
137 def fork(self, repo_name):
137 def fork(self, repo_name):
138 repo_model = RepoModel()
138 repo_model = RepoModel()
139 c.repo_info = repo = repo_model.get(repo_name)
139 c.repo_info = repo = repo_model.get(repo_name)
140 if not repo:
140 if not repo:
141 h.flash(_('%s repository is not mapped to db perhaps'
141 h.flash(_('%s repository is not mapped to db perhaps'
142 ' it was created or renamed from the filesystem'
142 ' it was created or renamed from the filesystem'
143 ' please run the application again'
143 ' please run the application again'
144 ' in order to rescan repositories') % repo_name,
144 ' in order to rescan repositories') % repo_name,
145 category='error')
145 category='error')
146
146
147 return redirect(url('home'))
147 return redirect(url('home'))
148
148
149 return render('settings/repo_fork.html')
149 return render('settings/repo_fork.html')
150
150
151
151
152
152
153 def fork_create(self, repo_name):
153 def fork_create(self, repo_name):
154 repo_model = RepoModel()
154 repo_model = RepoModel()
155 c.repo_info = repo_model.get(repo_name)
155 c.repo_info = repo_model.get(repo_name)
156 _form = RepoForkForm(old_data={'repo_type':c.repo_info.repo_type})()
156 _form = RepoForkForm(old_data={'repo_type':c.repo_info.repo_type})()
157 form_result = {}
157 form_result = {}
158 try:
158 try:
159 form_result = _form.to_python(dict(request.POST))
159 form_result = _form.to_python(dict(request.POST))
160 form_result.update({'repo_name':repo_name})
160 form_result.update({'repo_name':repo_name})
161 repo_model.create_fork(form_result, c.rhodecode_user)
161 repo_model.create_fork(form_result, c.rhodecode_user)
162 h.flash(_('fork %s repository as %s task added') \
162 h.flash(_('fork %s repository as %s task added') \
163 % (repo_name, form_result['fork_name']),
163 % (repo_name, form_result['fork_name']),
164 category='success')
164 category='success')
165 action_logger(self.rhodecode_user, 'user_forked_repo',
165 action_logger(self.rhodecode_user, 'user_forked_repo',
166 repo_name, '', self.sa)
166 repo_name, '', self.sa)
167 except formencode.Invalid, errors:
167 except formencode.Invalid, errors:
168 c.new_repo = errors.value['fork_name']
168 c.new_repo = errors.value['fork_name']
169 r = render('settings/repo_fork.html')
169 r = render('settings/repo_fork.html')
170
170
171 return htmlfill.render(
171 return htmlfill.render(
172 r,
172 r,
173 defaults=errors.value,
173 defaults=errors.value,
174 errors=errors.error_dict or {},
174 errors=errors.error_dict or {},
175 prefix_error=False,
175 prefix_error=False,
176 encoding="UTF-8")
176 encoding="UTF-8")
177 return redirect(url('home'))
177 return redirect(url('home'))
@@ -1,46 +1,46 b''
1 """The base Controller API
1 """The base Controller API
2
2
3 Provides the BaseController class for subclassing.
3 Provides the BaseController class for subclassing.
4 """
4 """
5 from pylons import config, tmpl_context as c, request, session
5 from pylons import config, tmpl_context as c, request, session
6 from pylons.controllers import WSGIController
6 from pylons.controllers import WSGIController
7 from pylons.templating import render_mako as render
7 from pylons.templating import render_mako as render
8 from rhodecode import __version__
8 from rhodecode import __version__
9 from rhodecode.lib import auth
9 from rhodecode.lib import auth
10 from rhodecode.lib.utils import get_repo_slug
10 from rhodecode.lib.utils import get_repo_slug
11 from rhodecode.model import meta
11 from rhodecode.model import meta
12 from rhodecode.model.hg import _get_repos_cached, \
12 from rhodecode.model.hg import HgModel
13 _get_repos_switcher_cached
14 from vcs import BACKENDS
13 from vcs import BACKENDS
14
15 class BaseController(WSGIController):
15 class BaseController(WSGIController):
16
16
17 def __before__(self):
17 def __before__(self):
18 c.rhodecode_version = __version__
18 c.rhodecode_version = __version__
19 c.rhodecode_name = config['rhodecode_title']
19 c.rhodecode_name = config['rhodecode_title']
20 c.repo_name = get_repo_slug(request)
20 c.repo_name = get_repo_slug(request)
21 c.cached_repo_list = _get_repos_cached()
21 c.cached_repo_list = HgModel().get_repos()
22 c.repo_switcher_list = _get_repos_switcher_cached(c.cached_repo_list)
23 c.backends = BACKENDS.keys()
22 c.backends = BACKENDS.keys()
23
24 if c.repo_name:
24 if c.repo_name:
25 cached_repo = c.cached_repo_list.get(c.repo_name)
25 cached_repo = HgModel().get(c.repo_name)
26
26
27 if cached_repo:
27 if cached_repo:
28 c.repository_tags = cached_repo.tags
28 c.repository_tags = cached_repo.tags
29 c.repository_branches = cached_repo.branches
29 c.repository_branches = cached_repo.branches
30 else:
30 else:
31 c.repository_tags = {}
31 c.repository_tags = {}
32 c.repository_branches = {}
32 c.repository_branches = {}
33
33
34 self.sa = meta.Session()
34 self.sa = meta.Session()
35
35
36 def __call__(self, environ, start_response):
36 def __call__(self, environ, start_response):
37 """Invoke the Controller"""
37 """Invoke the Controller"""
38 # WSGIController.__call__ dispatches to the Controller method
38 # WSGIController.__call__ dispatches to the Controller method
39 # the request is routed to. This routing information is
39 # the request is routed to. This routing information is
40 # available in environ['pylons.routes_dict']
40 # available in environ['pylons.routes_dict']
41 try:
41 try:
42 #putting this here makes sure that we update permissions every time
42 #putting this here makes sure that we update permissions every time
43 self.rhodecode_user = c.rhodecode_user = auth.get_user(session)
43 self.rhodecode_user = c.rhodecode_user = auth.get_user(session)
44 return WSGIController.__call__(self, environ, start_response)
44 return WSGIController.__call__(self, environ, start_response)
45 finally:
45 finally:
46 meta.Session.remove()
46 meta.Session.remove()
@@ -1,210 +1,209 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # middleware to handle git api calls
3 # middleware to handle git api calls
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on 2010-04-28
21 Created on 2010-04-28
22
22
23 @author: marcink
23 @author: marcink
24 SimpleGit middleware for handling git protocol request (push/clone etc.)
24 SimpleGit middleware for handling git protocol request (push/clone etc.)
25 It's implemented with basic auth function
25 It's implemented with basic auth function
26 """
26 """
27
27
28 from dulwich import server as dulserver
28 from dulwich import server as dulserver
29
29
30 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
30 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
31
31
32 def handle(self):
32 def handle(self):
33 write = lambda x: self.proto.write_sideband(1, x)
33 write = lambda x: self.proto.write_sideband(1, x)
34
34
35 graph_walker = dulserver.ProtocolGraphWalker(self, self.repo.object_store,
35 graph_walker = dulserver.ProtocolGraphWalker(self, self.repo.object_store,
36 self.repo.get_peeled)
36 self.repo.get_peeled)
37 objects_iter = self.repo.fetch_objects(
37 objects_iter = self.repo.fetch_objects(
38 graph_walker.determine_wants, graph_walker, self.progress,
38 graph_walker.determine_wants, graph_walker, self.progress,
39 get_tagged=self.get_tagged)
39 get_tagged=self.get_tagged)
40
40
41 # Do they want any objects?
41 # Do they want any objects?
42 if len(objects_iter) == 0:
42 if len(objects_iter) == 0:
43 return
43 return
44
44
45 self.progress("counting objects: %d, done.\n" % len(objects_iter))
45 self.progress("counting objects: %d, done.\n" % len(objects_iter))
46 dulserver.write_pack_data(dulserver.ProtocolFile(None, write), objects_iter,
46 dulserver.write_pack_data(dulserver.ProtocolFile(None, write), objects_iter,
47 len(objects_iter))
47 len(objects_iter))
48 messages = []
48 messages = []
49 messages.append('thank you for using rhodecode')
49 messages.append('thank you for using rhodecode')
50
50
51 for msg in messages:
51 for msg in messages:
52 self.progress(msg + "\n")
52 self.progress(msg + "\n")
53 # we are done
53 # we are done
54 self.proto.write("0000")
54 self.proto.write("0000")
55
55
56 dulserver.DEFAULT_HANDLERS = {
56 dulserver.DEFAULT_HANDLERS = {
57 'git-upload-pack': SimpleGitUploadPackHandler,
57 'git-upload-pack': SimpleGitUploadPackHandler,
58 'git-receive-pack': dulserver.ReceivePackHandler,
58 'git-receive-pack': dulserver.ReceivePackHandler,
59 }
59 }
60
60
61 from dulwich.repo import Repo
61 from dulwich.repo import Repo
62 from dulwich.web import HTTPGitApplication
62 from dulwich.web import HTTPGitApplication
63 from paste.auth.basic import AuthBasicAuthenticator
63 from paste.auth.basic import AuthBasicAuthenticator
64 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
64 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
65 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
65 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
66 from rhodecode.lib.utils import is_git, invalidate_cache, check_repo_fast
66 from rhodecode.lib.utils import is_git, invalidate_cache, check_repo_fast
67 from rhodecode.model.user import UserModel
67 from rhodecode.model.user import UserModel
68 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
68 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
69 import logging
69 import logging
70 import os
70 import os
71 import traceback
71 import traceback
72
72
73 log = logging.getLogger(__name__)
73 log = logging.getLogger(__name__)
74
74
75 class SimpleGit(object):
75 class SimpleGit(object):
76
76
77 def __init__(self, application, config):
77 def __init__(self, application, config):
78 self.application = application
78 self.application = application
79 self.config = config
79 self.config = config
80 #authenticate this git request using
80 #authenticate this git request using
81 self.authenticate = AuthBasicAuthenticator('', authfunc)
81 self.authenticate = AuthBasicAuthenticator('', authfunc)
82 self.ipaddr = '0.0.0.0'
82 self.ipaddr = '0.0.0.0'
83 self.repository = None
83 self.repository = None
84 self.username = None
84 self.username = None
85 self.action = None
85 self.action = None
86
86
87 def __call__(self, environ, start_response):
87 def __call__(self, environ, start_response):
88 if not is_git(environ):
88 if not is_git(environ):
89 return self.application(environ, start_response)
89 return self.application(environ, start_response)
90
90
91 proxy_key = 'HTTP_X_REAL_IP'
91 proxy_key = 'HTTP_X_REAL_IP'
92 def_key = 'REMOTE_ADDR'
92 def_key = 'REMOTE_ADDR'
93 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
93 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
94
94
95 #===================================================================
95 #===================================================================
96 # AUTHENTICATE THIS GIT REQUEST
96 # AUTHENTICATE THIS GIT REQUEST
97 #===================================================================
97 #===================================================================
98 username = REMOTE_USER(environ)
98 username = REMOTE_USER(environ)
99 if not username:
99 if not username:
100 self.authenticate.realm = self.config['rhodecode_realm']
100 self.authenticate.realm = self.config['rhodecode_realm']
101 result = self.authenticate(environ)
101 result = self.authenticate(environ)
102 if isinstance(result, str):
102 if isinstance(result, str):
103 AUTH_TYPE.update(environ, 'basic')
103 AUTH_TYPE.update(environ, 'basic')
104 REMOTE_USER.update(environ, result)
104 REMOTE_USER.update(environ, result)
105 else:
105 else:
106 return result.wsgi_application(environ, start_response)
106 return result.wsgi_application(environ, start_response)
107
107
108 #=======================================================================
108 #=======================================================================
109 # GET REPOSITORY
109 # GET REPOSITORY
110 #=======================================================================
110 #=======================================================================
111 try:
111 try:
112 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
112 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
113 if repo_name.endswith('/'):
113 if repo_name.endswith('/'):
114 repo_name = repo_name.rstrip('/')
114 repo_name = repo_name.rstrip('/')
115 self.repository = repo_name
115 self.repository = repo_name
116 except:
116 except:
117 log.error(traceback.format_exc())
117 log.error(traceback.format_exc())
118 return HTTPInternalServerError()(environ, start_response)
118 return HTTPInternalServerError()(environ, start_response)
119
119
120 #===================================================================
120 #===================================================================
121 # CHECK PERMISSIONS FOR THIS REQUEST
121 # CHECK PERMISSIONS FOR THIS REQUEST
122 #===================================================================
122 #===================================================================
123 self.action = self.__get_action(environ)
123 self.action = self.__get_action(environ)
124 if self.action:
124 if self.action:
125 username = self.__get_environ_user(environ)
125 username = self.__get_environ_user(environ)
126 try:
126 try:
127 user = self.__get_user(username)
127 user = self.__get_user(username)
128 self.username = user.username
128 self.username = user.username
129 except:
129 except:
130 log.error(traceback.format_exc())
130 log.error(traceback.format_exc())
131 return HTTPInternalServerError()(environ, start_response)
131 return HTTPInternalServerError()(environ, start_response)
132
132
133 #check permissions for this repository
133 #check permissions for this repository
134 if self.action == 'push':
134 if self.action == 'push':
135 if not HasPermissionAnyMiddleware('repository.write',
135 if not HasPermissionAnyMiddleware('repository.write',
136 'repository.admin')\
136 'repository.admin')\
137 (user, repo_name):
137 (user, repo_name):
138 return HTTPForbidden()(environ, start_response)
138 return HTTPForbidden()(environ, start_response)
139
139
140 else:
140 else:
141 #any other action need at least read permission
141 #any other action need at least read permission
142 if not HasPermissionAnyMiddleware('repository.read',
142 if not HasPermissionAnyMiddleware('repository.read',
143 'repository.write',
143 'repository.write',
144 'repository.admin')\
144 'repository.admin')\
145 (user, repo_name):
145 (user, repo_name):
146 return HTTPForbidden()(environ, start_response)
146 return HTTPForbidden()(environ, start_response)
147
147
148 self.extras = {'ip':self.ipaddr,
148 self.extras = {'ip':self.ipaddr,
149 'username':self.username,
149 'username':self.username,
150 'action':self.action,
150 'action':self.action,
151 'repository':self.repository}
151 'repository':self.repository}
152
152
153 #===================================================================
153 #===================================================================
154 # GIT REQUEST HANDLING
154 # GIT REQUEST HANDLING
155 #===================================================================
155 #===================================================================
156 self.basepath = self.config['base_path']
156 self.basepath = self.config['base_path']
157 self.repo_path = os.path.join(self.basepath, self.repo_name)
157 self.repo_path = os.path.join(self.basepath, self.repo_name)
158 #quick check if that dir exists...
158 #quick check if that dir exists...
159 if check_repo_fast(self.repo_name, self.basepath):
159 if check_repo_fast(self.repo_name, self.basepath):
160 return HTTPNotFound()(environ, start_response)
160 return HTTPNotFound()(environ, start_response)
161 try:
161 try:
162 app = self.__make_app()
162 app = self.__make_app()
163 except:
163 except:
164 log.error(traceback.format_exc())
164 log.error(traceback.format_exc())
165 return HTTPInternalServerError()(environ, start_response)
165 return HTTPInternalServerError()(environ, start_response)
166
166
167 #invalidate cache on push
167 #invalidate cache on push
168 if self.action == 'push':
168 if self.action == 'push':
169 self.__invalidate_cache(self.repo_name)
169 self.__invalidate_cache(self.repo_name)
170 messages = []
170 messages = []
171 messages.append('thank you for using rhodecode')
171 messages.append('thank you for using rhodecode')
172 return app(environ, start_response)
172 return app(environ, start_response)
173 else:
173 else:
174 return app(environ, start_response)
174 return app(environ, start_response)
175
175
176
176
177 def __make_app(self):
177 def __make_app(self):
178 backend = dulserver.DictBackend({'/' + self.repo_name: Repo(self.repo_path)})
178 backend = dulserver.DictBackend({'/' + self.repo_name: Repo(self.repo_path)})
179 gitserve = HTTPGitApplication(backend)
179 gitserve = HTTPGitApplication(backend)
180
180
181 return gitserve
181 return gitserve
182
182
183 def __get_environ_user(self, environ):
183 def __get_environ_user(self, environ):
184 return environ.get('REMOTE_USER')
184 return environ.get('REMOTE_USER')
185
185
186 def __get_user(self, username):
186 def __get_user(self, username):
187 return UserModel().get_by_username(username, cache=True)
187 return UserModel().get_by_username(username, cache=True)
188
188
189 def __get_action(self, environ):
189 def __get_action(self, environ):
190 """
190 """
191 Maps git request commands into a pull or push command.
191 Maps git request commands into a pull or push command.
192 :param environ:
192 :param environ:
193 """
193 """
194 service = environ['QUERY_STRING'].split('=')
194 service = environ['QUERY_STRING'].split('=')
195 if len(service) > 1:
195 if len(service) > 1:
196 service_cmd = service[1]
196 service_cmd = service[1]
197 mapping = {'git-receive-pack': 'push',
197 mapping = {'git-receive-pack': 'push',
198 'git-upload-pack': 'pull',
198 'git-upload-pack': 'pull',
199 }
199 }
200
200
201 return mapping.get(service_cmd, service_cmd if service_cmd else 'other')
201 return mapping.get(service_cmd, service_cmd if service_cmd else 'other')
202 else:
202 else:
203 return 'other'
203 return 'other'
204
204
205 def __invalidate_cache(self, repo_name):
205 def __invalidate_cache(self, repo_name):
206 """we know that some change was made to repositories and we should
206 """we know that some change was made to repositories and we should
207 invalidate the cache to see the changes right away but only for
207 invalidate the cache to see the changes right away but only for
208 push requests"""
208 push requests"""
209 invalidate_cache('cached_repo_list')
209 invalidate_cache('get_repo_cached_%s' % repo_name)
210 invalidate_cache('full_changelog', repo_name)
@@ -1,240 +1,239 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # middleware to handle mercurial api calls
3 # middleware to handle mercurial api calls
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on 2010-04-28
21 Created on 2010-04-28
22
22
23 @author: marcink
23 @author: marcink
24 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
24 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
25 It's implemented with basic auth function
25 It's implemented with basic auth function
26 """
26 """
27 from itertools import chain
27 from itertools import chain
28 from mercurial.error import RepoError
28 from mercurial.error import RepoError
29 from mercurial.hgweb import hgweb
29 from mercurial.hgweb import hgweb
30 from mercurial.hgweb.request import wsgiapplication
30 from mercurial.hgweb.request import wsgiapplication
31 from paste.auth.basic import AuthBasicAuthenticator
31 from paste.auth.basic import AuthBasicAuthenticator
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
33 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
33 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
34 from rhodecode.lib.utils import is_mercurial, make_ui, invalidate_cache, \
34 from rhodecode.lib.utils import is_mercurial, make_ui, invalidate_cache, \
35 check_repo_fast, ui_sections
35 check_repo_fast, ui_sections
36 from rhodecode.model.user import UserModel
36 from rhodecode.model.user import UserModel
37 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
37 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
38 import logging
38 import logging
39 import os
39 import os
40 import traceback
40 import traceback
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44 class SimpleHg(object):
44 class SimpleHg(object):
45
45
46 def __init__(self, application, config):
46 def __init__(self, application, config):
47 self.application = application
47 self.application = application
48 self.config = config
48 self.config = config
49 #authenticate this mercurial request using
49 #authenticate this mercurial request using
50 self.authenticate = AuthBasicAuthenticator('', authfunc)
50 self.authenticate = AuthBasicAuthenticator('', authfunc)
51 self.ipaddr = '0.0.0.0'
51 self.ipaddr = '0.0.0.0'
52 self.repository = None
52 self.repository = None
53 self.username = None
53 self.username = None
54 self.action = None
54 self.action = None
55
55
56 def __call__(self, environ, start_response):
56 def __call__(self, environ, start_response):
57 if not is_mercurial(environ):
57 if not is_mercurial(environ):
58 return self.application(environ, start_response)
58 return self.application(environ, start_response)
59
59
60 proxy_key = 'HTTP_X_REAL_IP'
60 proxy_key = 'HTTP_X_REAL_IP'
61 def_key = 'REMOTE_ADDR'
61 def_key = 'REMOTE_ADDR'
62 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
62 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
63
63
64 #===================================================================
64 #===================================================================
65 # AUTHENTICATE THIS MERCURIAL REQUEST
65 # AUTHENTICATE THIS MERCURIAL REQUEST
66 #===================================================================
66 #===================================================================
67 username = REMOTE_USER(environ)
67 username = REMOTE_USER(environ)
68
68
69 if not username:
69 if not username:
70 self.authenticate.realm = self.config['rhodecode_realm']
70 self.authenticate.realm = self.config['rhodecode_realm']
71 result = self.authenticate(environ)
71 result = self.authenticate(environ)
72 if isinstance(result, str):
72 if isinstance(result, str):
73 AUTH_TYPE.update(environ, 'basic')
73 AUTH_TYPE.update(environ, 'basic')
74 REMOTE_USER.update(environ, result)
74 REMOTE_USER.update(environ, result)
75 else:
75 else:
76 return result.wsgi_application(environ, start_response)
76 return result.wsgi_application(environ, start_response)
77
77
78 #=======================================================================
78 #=======================================================================
79 # GET REPOSITORY
79 # GET REPOSITORY
80 #=======================================================================
80 #=======================================================================
81 try:
81 try:
82 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
82 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
83 if repo_name.endswith('/'):
83 if repo_name.endswith('/'):
84 repo_name = repo_name.rstrip('/')
84 repo_name = repo_name.rstrip('/')
85 self.repository = repo_name
85 self.repository = repo_name
86 except:
86 except:
87 log.error(traceback.format_exc())
87 log.error(traceback.format_exc())
88 return HTTPInternalServerError()(environ, start_response)
88 return HTTPInternalServerError()(environ, start_response)
89
89
90 #===================================================================
90 #===================================================================
91 # CHECK PERMISSIONS FOR THIS REQUEST
91 # CHECK PERMISSIONS FOR THIS REQUEST
92 #===================================================================
92 #===================================================================
93 self.action = self.__get_action(environ)
93 self.action = self.__get_action(environ)
94 if self.action:
94 if self.action:
95 username = self.__get_environ_user(environ)
95 username = self.__get_environ_user(environ)
96 try:
96 try:
97 user = self.__get_user(username)
97 user = self.__get_user(username)
98 self.username = user.username
98 self.username = user.username
99 except:
99 except:
100 log.error(traceback.format_exc())
100 log.error(traceback.format_exc())
101 return HTTPInternalServerError()(environ, start_response)
101 return HTTPInternalServerError()(environ, start_response)
102
102
103 #check permissions for this repository
103 #check permissions for this repository
104 if self.action == 'push':
104 if self.action == 'push':
105 if not HasPermissionAnyMiddleware('repository.write',
105 if not HasPermissionAnyMiddleware('repository.write',
106 'repository.admin')\
106 'repository.admin')\
107 (user, repo_name):
107 (user, repo_name):
108 return HTTPForbidden()(environ, start_response)
108 return HTTPForbidden()(environ, start_response)
109
109
110 else:
110 else:
111 #any other action need at least read permission
111 #any other action need at least read permission
112 if not HasPermissionAnyMiddleware('repository.read',
112 if not HasPermissionAnyMiddleware('repository.read',
113 'repository.write',
113 'repository.write',
114 'repository.admin')\
114 'repository.admin')\
115 (user, repo_name):
115 (user, repo_name):
116 return HTTPForbidden()(environ, start_response)
116 return HTTPForbidden()(environ, start_response)
117
117
118 self.extras = {'ip':self.ipaddr,
118 self.extras = {'ip':self.ipaddr,
119 'username':self.username,
119 'username':self.username,
120 'action':self.action,
120 'action':self.action,
121 'repository':self.repository}
121 'repository':self.repository}
122
122
123 #===================================================================
123 #===================================================================
124 # MERCURIAL REQUEST HANDLING
124 # MERCURIAL REQUEST HANDLING
125 #===================================================================
125 #===================================================================
126 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
126 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
127 self.baseui = make_ui('db')
127 self.baseui = make_ui('db')
128 self.basepath = self.config['base_path']
128 self.basepath = self.config['base_path']
129 self.repo_path = os.path.join(self.basepath, repo_name)
129 self.repo_path = os.path.join(self.basepath, repo_name)
130
130
131 #quick check if that dir exists...
131 #quick check if that dir exists...
132 if check_repo_fast(repo_name, self.basepath):
132 if check_repo_fast(repo_name, self.basepath):
133 return HTTPNotFound()(environ, start_response)
133 return HTTPNotFound()(environ, start_response)
134 try:
134 try:
135 app = wsgiapplication(self.__make_app)
135 app = wsgiapplication(self.__make_app)
136 except RepoError, e:
136 except RepoError, e:
137 if str(e).find('not found') != -1:
137 if str(e).find('not found') != -1:
138 return HTTPNotFound()(environ, start_response)
138 return HTTPNotFound()(environ, start_response)
139 except Exception:
139 except Exception:
140 log.error(traceback.format_exc())
140 log.error(traceback.format_exc())
141 return HTTPInternalServerError()(environ, start_response)
141 return HTTPInternalServerError()(environ, start_response)
142
142
143 #invalidate cache on push
143 #invalidate cache on push
144 if self.action == 'push':
144 if self.action == 'push':
145 self.__invalidate_cache(repo_name)
145 self.__invalidate_cache(repo_name)
146 messages = []
146 messages = []
147 messages.append('thank you for using rhodecode')
147 messages.append('thank you for using rhodecode')
148
148
149 return self.msg_wrapper(app, environ, start_response, messages)
149 return self.msg_wrapper(app, environ, start_response, messages)
150 else:
150 else:
151 return app(environ, start_response)
151 return app(environ, start_response)
152
152
153
153
154 def msg_wrapper(self, app, environ, start_response, messages=[]):
154 def msg_wrapper(self, app, environ, start_response, messages=[]):
155 """
155 """
156 Wrapper for custom messages that come out of mercurial respond messages
156 Wrapper for custom messages that come out of mercurial respond messages
157 is a list of messages that the user will see at the end of response
157 is a list of messages that the user will see at the end of response
158 from merurial protocol actions that involves remote answers
158 from merurial protocol actions that involves remote answers
159 :param app:
159 :param app:
160 :param environ:
160 :param environ:
161 :param start_response:
161 :param start_response:
162 """
162 """
163 def custom_messages(msg_list):
163 def custom_messages(msg_list):
164 for msg in msg_list:
164 for msg in msg_list:
165 yield msg + '\n'
165 yield msg + '\n'
166 org_response = app(environ, start_response)
166 org_response = app(environ, start_response)
167 return chain(org_response, custom_messages(messages))
167 return chain(org_response, custom_messages(messages))
168
168
169 def __make_app(self):
169 def __make_app(self):
170 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
170 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
171 return self.__load_web_settings(hgserve, self.extras)
171 return self.__load_web_settings(hgserve, self.extras)
172
172
173 def __get_environ_user(self, environ):
173 def __get_environ_user(self, environ):
174 return environ.get('REMOTE_USER')
174 return environ.get('REMOTE_USER')
175
175
176 def __get_user(self, username):
176 def __get_user(self, username):
177 return UserModel().get_by_username(username, cache=True)
177 return UserModel().get_by_username(username, cache=True)
178
178
179 def __get_action(self, environ):
179 def __get_action(self, environ):
180 """
180 """
181 Maps mercurial request commands into a clone,pull or push command.
181 Maps mercurial request commands into a clone,pull or push command.
182 This should always return a valid command string
182 This should always return a valid command string
183 :param environ:
183 :param environ:
184 """
184 """
185 mapping = {'changegroup': 'pull',
185 mapping = {'changegroup': 'pull',
186 'changegroupsubset': 'pull',
186 'changegroupsubset': 'pull',
187 'stream_out': 'pull',
187 'stream_out': 'pull',
188 'listkeys': 'pull',
188 'listkeys': 'pull',
189 'unbundle': 'push',
189 'unbundle': 'push',
190 'pushkey': 'push', }
190 'pushkey': 'push', }
191 for qry in environ['QUERY_STRING'].split('&'):
191 for qry in environ['QUERY_STRING'].split('&'):
192 if qry.startswith('cmd'):
192 if qry.startswith('cmd'):
193 cmd = qry.split('=')[-1]
193 cmd = qry.split('=')[-1]
194 if mapping.has_key(cmd):
194 if mapping.has_key(cmd):
195 return mapping[cmd]
195 return mapping[cmd]
196 else:
196 else:
197 return cmd
197 return cmd
198
198
199 def __invalidate_cache(self, repo_name):
199 def __invalidate_cache(self, repo_name):
200 """we know that some change was made to repositories and we should
200 """we know that some change was made to repositories and we should
201 invalidate the cache to see the changes right away but only for
201 invalidate the cache to see the changes right away but only for
202 push requests"""
202 push requests"""
203 invalidate_cache('cached_repo_list')
203 invalidate_cache('get_repo_cached_%s' % repo_name)
204 invalidate_cache('full_changelog', repo_name)
205
204
206
205
207 def __load_web_settings(self, hgserve, extras={}):
206 def __load_web_settings(self, hgserve, extras={}):
208 #set the global ui for hgserve instance passed
207 #set the global ui for hgserve instance passed
209 hgserve.repo.ui = self.baseui
208 hgserve.repo.ui = self.baseui
210
209
211 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
210 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
212
211
213 #inject some additional parameters that will be available in ui
212 #inject some additional parameters that will be available in ui
214 #for hooks
213 #for hooks
215 for k, v in extras.items():
214 for k, v in extras.items():
216 hgserve.repo.ui.setconfig('rhodecode_extras', k, v)
215 hgserve.repo.ui.setconfig('rhodecode_extras', k, v)
217
216
218 repoui = make_ui('file', hgrc, False)
217 repoui = make_ui('file', hgrc, False)
219
218
220 if repoui:
219 if repoui:
221 #overwrite our ui instance with the section from hgrc file
220 #overwrite our ui instance with the section from hgrc file
222 for section in ui_sections:
221 for section in ui_sections:
223 for k, v in repoui.configitems(section):
222 for k, v in repoui.configitems(section):
224 hgserve.repo.ui.setconfig(section, k, v)
223 hgserve.repo.ui.setconfig(section, k, v)
225
224
226 return hgserve
225 return hgserve
227
226
228
227
229
228
230
229
231
230
232
231
233
232
234
233
235
234
236
235
237
236
238
237
239
238
240
239
@@ -1,538 +1,519 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Utilities for RhodeCode
3 # Utilities for RhodeCode
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; version 2
7 # as published by the Free Software Foundation; version 2
8 # of the License or (at your opinion) any later version of the license.
8 # of the License or (at your opinion) any later version of the license.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # MA 02110-1301, USA.
18 # MA 02110-1301, USA.
19 """
19 """
20 Created on April 18, 2010
20 Created on April 18, 2010
21 Utilities for RhodeCode
21 Utilities for RhodeCode
22 @author: marcink
22 @author: marcink
23 """
23 """
24
24
25 from UserDict import DictMixin
25 from UserDict import DictMixin
26 from mercurial import ui, config, hg
26 from mercurial import ui, config, hg
27 from mercurial.error import RepoError
27 from mercurial.error import RepoError
28 from rhodecode.model import meta
28 from rhodecode.model import meta
29 from rhodecode.model.caching_query import FromCache
29 from rhodecode.model.caching_query import FromCache
30 from rhodecode.model.db import Repository, User, RhodeCodeUi, RhodeCodeSettings, \
30 from rhodecode.model.db import Repository, User, RhodeCodeUi, RhodeCodeSettings, \
31 UserLog
31 UserLog
32 from rhodecode.model.repo import RepoModel
32 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.user import UserModel
33 from rhodecode.model.user import UserModel
34 from vcs.backends.base import BaseChangeset
34 from vcs.backends.base import BaseChangeset
35 from vcs.backends.git import GitRepository
35 from vcs.backends.git import GitRepository
36 from vcs.backends.hg import MercurialRepository
36 from vcs.backends.hg import MercurialRepository
37 from vcs.utils.lazy import LazyProperty
37 from vcs.utils.lazy import LazyProperty
38 import traceback
38 import traceback
39 import datetime
39 import datetime
40 import logging
40 import logging
41 import os
41 import os
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 def get_repo_slug(request):
46 def get_repo_slug(request):
47 return request.environ['pylons.routes_dict'].get('repo_name')
47 return request.environ['pylons.routes_dict'].get('repo_name')
48
48
49 def is_mercurial(environ):
49 def is_mercurial(environ):
50 """
50 """
51 Returns True if request's target is mercurial server - header
51 Returns True if request's target is mercurial server - header
52 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
52 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
53 """
53 """
54 http_accept = environ.get('HTTP_ACCEPT')
54 http_accept = environ.get('HTTP_ACCEPT')
55 if http_accept and http_accept.startswith('application/mercurial'):
55 if http_accept and http_accept.startswith('application/mercurial'):
56 return True
56 return True
57 return False
57 return False
58
58
59 def is_git(environ):
59 def is_git(environ):
60 """
60 """
61 Returns True if request's target is git server. ``HTTP_USER_AGENT`` would
61 Returns True if request's target is git server. ``HTTP_USER_AGENT`` would
62 then have git client version given.
62 then have git client version given.
63
63
64 :param environ:
64 :param environ:
65 """
65 """
66 http_user_agent = environ.get('HTTP_USER_AGENT')
66 http_user_agent = environ.get('HTTP_USER_AGENT')
67 if http_user_agent.startswith('git'):
67 if http_user_agent.startswith('git'):
68 return True
68 return True
69 return False
69 return False
70
70
71 def action_logger(user, action, repo, ipaddr, sa=None):
71 def action_logger(user, action, repo, ipaddr, sa=None):
72 """
72 """
73 Action logger for various action made by users
73 Action logger for various action made by users
74 """
74 """
75
75
76 if not sa:
76 if not sa:
77 sa = meta.Session()
77 sa = meta.Session()
78
78
79 try:
79 try:
80 if hasattr(user, 'user_id'):
80 if hasattr(user, 'user_id'):
81 user_obj = user
81 user_obj = user
82 elif isinstance(user, basestring):
82 elif isinstance(user, basestring):
83 user_obj = UserModel(sa).get_by_username(user, cache=False)
83 user_obj = UserModel(sa).get_by_username(user, cache=False)
84 else:
84 else:
85 raise Exception('You have to provide user object or username')
85 raise Exception('You have to provide user object or username')
86
86
87 repo_name = repo.lstrip('/')
87 repo_name = repo.lstrip('/')
88 user_log = UserLog()
88 user_log = UserLog()
89 user_log.user_id = user_obj.user_id
89 user_log.user_id = user_obj.user_id
90 user_log.action = action
90 user_log.action = action
91 user_log.repository_name = repo_name
91 user_log.repository_name = repo_name
92 user_log.repository = RepoModel(sa).get(repo_name, cache=False)
92 user_log.repository = RepoModel(sa).get(repo_name, cache=False)
93 user_log.action_date = datetime.datetime.now()
93 user_log.action_date = datetime.datetime.now()
94 user_log.user_ip = ipaddr
94 user_log.user_ip = ipaddr
95 sa.add(user_log)
95 sa.add(user_log)
96 sa.commit()
96 sa.commit()
97
97
98 log.info('Adding user %s, action %s on %s',
98 log.info('Adding user %s, action %s on %s',
99 user_obj.username, action, repo)
99 user_obj.username, action, repo)
100 except:
100 except:
101 log.error(traceback.format_exc())
101 log.error(traceback.format_exc())
102 sa.rollback()
102 sa.rollback()
103
103
104 def get_repos(path, recursive=False, initial=False):
104 def get_repos(path, recursive=False, initial=False):
105 """
105 """
106 Scans given path for repos and return (name,(type,path)) tuple
106 Scans given path for repos and return (name,(type,path)) tuple
107 :param prefix:
107 :param prefix:
108 :param path:
108 :param path:
109 :param recursive:
109 :param recursive:
110 :param initial:
110 :param initial:
111 """
111 """
112 from vcs.utils.helpers import get_scm
112 from vcs.utils.helpers import get_scm
113 from vcs.exceptions import VCSError
113 from vcs.exceptions import VCSError
114
114
115 try:
115 try:
116 scm = get_scm(path)
116 scm = get_scm(path)
117 except:
117 except:
118 pass
118 pass
119 else:
119 else:
120 raise Exception('The given path %s should not be a repository got %s',
120 raise Exception('The given path %s should not be a repository got %s',
121 path, scm)
121 path, scm)
122
122
123 for dirpath in os.listdir(path):
123 for dirpath in os.listdir(path):
124 try:
124 try:
125 yield dirpath, get_scm(os.path.join(path, dirpath))
125 yield dirpath, get_scm(os.path.join(path, dirpath))
126 except VCSError:
126 except VCSError:
127 pass
127 pass
128
128
129 if __name__ == '__main__':
129 if __name__ == '__main__':
130 get_repos('', '/home/marcink/workspace-python')
130 get_repos('', '/home/marcink/workspace-python')
131
131
132
132
133 def check_repo_fast(repo_name, base_path):
133 def check_repo_fast(repo_name, base_path):
134 if os.path.isdir(os.path.join(base_path, repo_name)):return False
134 if os.path.isdir(os.path.join(base_path, repo_name)):return False
135 return True
135 return True
136
136
137 def check_repo(repo_name, base_path, verify=True):
137 def check_repo(repo_name, base_path, verify=True):
138
138
139 repo_path = os.path.join(base_path, repo_name)
139 repo_path = os.path.join(base_path, repo_name)
140
140
141 try:
141 try:
142 if not check_repo_fast(repo_name, base_path):
142 if not check_repo_fast(repo_name, base_path):
143 return False
143 return False
144 r = hg.repository(ui.ui(), repo_path)
144 r = hg.repository(ui.ui(), repo_path)
145 if verify:
145 if verify:
146 hg.verify(r)
146 hg.verify(r)
147 #here we hnow that repo exists it was verified
147 #here we hnow that repo exists it was verified
148 log.info('%s repo is already created', repo_name)
148 log.info('%s repo is already created', repo_name)
149 return False
149 return False
150 except RepoError:
150 except RepoError:
151 #it means that there is no valid repo there...
151 #it means that there is no valid repo there...
152 log.info('%s repo is free for creation', repo_name)
152 log.info('%s repo is free for creation', repo_name)
153 return True
153 return True
154
154
155 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
155 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
156 while True:
156 while True:
157 ok = raw_input(prompt)
157 ok = raw_input(prompt)
158 if ok in ('y', 'ye', 'yes'): return True
158 if ok in ('y', 'ye', 'yes'): return True
159 if ok in ('n', 'no', 'nop', 'nope'): return False
159 if ok in ('n', 'no', 'nop', 'nope'): return False
160 retries = retries - 1
160 retries = retries - 1
161 if retries < 0: raise IOError
161 if retries < 0: raise IOError
162 print complaint
162 print complaint
163
163
164 def get_hg_ui_cached():
164 def get_hg_ui_cached():
165 try:
165 try:
166 sa = meta.Session
166 sa = meta.Session
167 ret = sa.query(RhodeCodeUi)\
167 ret = sa.query(RhodeCodeUi)\
168 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
168 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
169 .all()
169 .all()
170 except:
170 except:
171 pass
171 pass
172 finally:
172 finally:
173 meta.Session.remove()
173 meta.Session.remove()
174 return ret
174 return ret
175
175
176
176
177 def get_hg_settings():
177 def get_hg_settings():
178 try:
178 try:
179 sa = meta.Session()
179 sa = meta.Session()
180 ret = sa.query(RhodeCodeSettings)\
180 ret = sa.query(RhodeCodeSettings)\
181 .options(FromCache("sql_cache_short", "get_hg_settings"))\
181 .options(FromCache("sql_cache_short", "get_hg_settings"))\
182 .all()
182 .all()
183 except:
183 except:
184 pass
184 pass
185 finally:
185 finally:
186 meta.Session.remove()
186 meta.Session.remove()
187
187
188 if not ret:
188 if not ret:
189 raise Exception('Could not get application settings !')
189 raise Exception('Could not get application settings !')
190 settings = {}
190 settings = {}
191 for each in ret:
191 for each in ret:
192 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
192 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
193
193
194 return settings
194 return settings
195
195
196 def get_hg_ui_settings():
196 def get_hg_ui_settings():
197 try:
197 try:
198 sa = meta.Session()
198 sa = meta.Session()
199 ret = sa.query(RhodeCodeUi).all()
199 ret = sa.query(RhodeCodeUi).all()
200 except:
200 except:
201 pass
201 pass
202 finally:
202 finally:
203 meta.Session.remove()
203 meta.Session.remove()
204
204
205 if not ret:
205 if not ret:
206 raise Exception('Could not get application ui settings !')
206 raise Exception('Could not get application ui settings !')
207 settings = {}
207 settings = {}
208 for each in ret:
208 for each in ret:
209 k = each.ui_key
209 k = each.ui_key
210 v = each.ui_value
210 v = each.ui_value
211 if k == '/':
211 if k == '/':
212 k = 'root_path'
212 k = 'root_path'
213
213
214 if k.find('.') != -1:
214 if k.find('.') != -1:
215 k = k.replace('.', '_')
215 k = k.replace('.', '_')
216
216
217 if each.ui_section == 'hooks':
217 if each.ui_section == 'hooks':
218 v = each.ui_active
218 v = each.ui_active
219
219
220 settings[each.ui_section + '_' + k] = v
220 settings[each.ui_section + '_' + k] = v
221
221
222 return settings
222 return settings
223
223
224 #propagated from mercurial documentation
224 #propagated from mercurial documentation
225 ui_sections = ['alias', 'auth',
225 ui_sections = ['alias', 'auth',
226 'decode/encode', 'defaults',
226 'decode/encode', 'defaults',
227 'diff', 'email',
227 'diff', 'email',
228 'extensions', 'format',
228 'extensions', 'format',
229 'merge-patterns', 'merge-tools',
229 'merge-patterns', 'merge-tools',
230 'hooks', 'http_proxy',
230 'hooks', 'http_proxy',
231 'smtp', 'patch',
231 'smtp', 'patch',
232 'paths', 'profiling',
232 'paths', 'profiling',
233 'server', 'trusted',
233 'server', 'trusted',
234 'ui', 'web', ]
234 'ui', 'web', ]
235
235
236 def make_ui(read_from='file', path=None, checkpaths=True):
236 def make_ui(read_from='file', path=None, checkpaths=True):
237 """
237 """
238 A function that will read python rc files or database
238 A function that will read python rc files or database
239 and make an mercurial ui object from read options
239 and make an mercurial ui object from read options
240
240
241 :param path: path to mercurial config file
241 :param path: path to mercurial config file
242 :param checkpaths: check the path
242 :param checkpaths: check the path
243 :param read_from: read from 'file' or 'db'
243 :param read_from: read from 'file' or 'db'
244 """
244 """
245
245
246 baseui = ui.ui()
246 baseui = ui.ui()
247
247
248 if read_from == 'file':
248 if read_from == 'file':
249 if not os.path.isfile(path):
249 if not os.path.isfile(path):
250 log.warning('Unable to read config file %s' % path)
250 log.warning('Unable to read config file %s' % path)
251 return False
251 return False
252 log.debug('reading hgrc from %s', path)
252 log.debug('reading hgrc from %s', path)
253 cfg = config.config()
253 cfg = config.config()
254 cfg.read(path)
254 cfg.read(path)
255 for section in ui_sections:
255 for section in ui_sections:
256 for k, v in cfg.items(section):
256 for k, v in cfg.items(section):
257 baseui.setconfig(section, k, v)
257 baseui.setconfig(section, k, v)
258 log.debug('settings ui from file[%s]%s:%s', section, k, v)
258 log.debug('settings ui from file[%s]%s:%s', section, k, v)
259
259
260 elif read_from == 'db':
260 elif read_from == 'db':
261 hg_ui = get_hg_ui_cached()
261 hg_ui = get_hg_ui_cached()
262 for ui_ in hg_ui:
262 for ui_ in hg_ui:
263 if ui_.ui_active:
263 if ui_.ui_active:
264 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
264 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
265 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
265 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
266
266
267
267
268 return baseui
268 return baseui
269
269
270
270
271 def set_rhodecode_config(config):
271 def set_rhodecode_config(config):
272 hgsettings = get_hg_settings()
272 hgsettings = get_hg_settings()
273
273
274 for k, v in hgsettings.items():
274 for k, v in hgsettings.items():
275 config[k] = v
275 config[k] = v
276
276
277 def invalidate_cache(name, *args):
277 def invalidate_cache(name, *args):
278 """Invalidates given name cache"""
278 """
279
279 Puts cache invalidation task into db for
280 from beaker.cache import region_invalidate
280 further global cache invalidation
281 log.info('INVALIDATING CACHE FOR %s', name)
281 """
282
282 pass
283 """propagate our arguments to make sure invalidation works. First
284 argument has to be the name of cached func name give to cache decorator
285 without that the invalidation would not work"""
286 tmp = [name]
287 tmp.extend(args)
288 args = tuple(tmp)
289
290 if name == 'cached_repo_list':
291 from rhodecode.model.hg import _get_repos_cached
292 region_invalidate(_get_repos_cached, None, *args)
293
294 if name == 'full_changelog':
295 from rhodecode.model.hg import _full_changelog_cached
296 region_invalidate(_full_changelog_cached, None, *args)
297
283
298 class EmptyChangeset(BaseChangeset):
284 class EmptyChangeset(BaseChangeset):
299 """
285 """
300 An dummy empty changeset. It's possible to pass hash when creating
286 An dummy empty changeset. It's possible to pass hash when creating
301 an EmptyChangeset
287 an EmptyChangeset
302 """
288 """
303
289
304 def __init__(self, cs='0' * 40):
290 def __init__(self, cs='0' * 40):
305 self._empty_cs = cs
291 self._empty_cs = cs
306 self.revision = -1
292 self.revision = -1
307 self.message = ''
293 self.message = ''
308 self.author = ''
294 self.author = ''
309 self.date = ''
295 self.date = ''
310
296
311 @LazyProperty
297 @LazyProperty
312 def raw_id(self):
298 def raw_id(self):
313 """
299 """
314 Returns raw string identifying this changeset, useful for web
300 Returns raw string identifying this changeset, useful for web
315 representation.
301 representation.
316 """
302 """
317 return self._empty_cs
303 return self._empty_cs
318
304
319 @LazyProperty
305 @LazyProperty
320 def short_id(self):
306 def short_id(self):
321 return self.raw_id[:12]
307 return self.raw_id[:12]
322
308
323 def get_file_changeset(self, path):
309 def get_file_changeset(self, path):
324 return self
310 return self
325
311
326 def get_file_content(self, path):
312 def get_file_content(self, path):
327 return u''
313 return u''
328
314
329 def get_file_size(self, path):
315 def get_file_size(self, path):
330 return 0
316 return 0
331
317
332 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
318 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
333 """
319 """
334 maps all found repositories into db
320 maps all found repositories into db
335 """
321 """
336
322
337 sa = meta.Session()
323 sa = meta.Session()
338 rm = RepoModel(sa)
324 rm = RepoModel(sa)
339 user = sa.query(User).filter(User.admin == True).first()
325 user = sa.query(User).filter(User.admin == True).first()
340
326
341 for name, repo in initial_repo_list.items():
327 for name, repo in initial_repo_list.items():
342 if not rm.get(name, cache=False):
328 if not rm.get(name, cache=False):
343 log.info('repository %s not found creating default', name)
329 log.info('repository %s not found creating default', name)
344
330
345 form_data = {
331 form_data = {
346 'repo_name':name,
332 'repo_name':name,
347 'repo_type':repo.alias,
333 'repo_type':repo.alias,
348 'description':repo.description \
334 'description':repo.description \
349 if repo.description != 'unknown' else \
335 if repo.description != 'unknown' else \
350 '%s repository' % name,
336 '%s repository' % name,
351 'private':False
337 'private':False
352 }
338 }
353 rm.create(form_data, user, just_db=True)
339 rm.create(form_data, user, just_db=True)
354
340
355
356 if remove_obsolete:
341 if remove_obsolete:
357 #remove from database those repositories that are not in the filesystem
342 #remove from database those repositories that are not in the filesystem
358 for repo in sa.query(Repository).all():
343 for repo in sa.query(Repository).all():
359 if repo.repo_name not in initial_repo_list.keys():
344 if repo.repo_name not in initial_repo_list.keys():
360 sa.delete(repo)
345 sa.delete(repo)
361 sa.commit()
346 sa.commit()
362
347
363
364 meta.Session.remove()
365
366
367 class OrderedDict(dict, DictMixin):
348 class OrderedDict(dict, DictMixin):
368
349
369 def __init__(self, *args, **kwds):
350 def __init__(self, *args, **kwds):
370 if len(args) > 1:
351 if len(args) > 1:
371 raise TypeError('expected at most 1 arguments, got %d' % len(args))
352 raise TypeError('expected at most 1 arguments, got %d' % len(args))
372 try:
353 try:
373 self.__end
354 self.__end
374 except AttributeError:
355 except AttributeError:
375 self.clear()
356 self.clear()
376 self.update(*args, **kwds)
357 self.update(*args, **kwds)
377
358
378 def clear(self):
359 def clear(self):
379 self.__end = end = []
360 self.__end = end = []
380 end += [None, end, end] # sentinel node for doubly linked list
361 end += [None, end, end] # sentinel node for doubly linked list
381 self.__map = {} # key --> [key, prev, next]
362 self.__map = {} # key --> [key, prev, next]
382 dict.clear(self)
363 dict.clear(self)
383
364
384 def __setitem__(self, key, value):
365 def __setitem__(self, key, value):
385 if key not in self:
366 if key not in self:
386 end = self.__end
367 end = self.__end
387 curr = end[1]
368 curr = end[1]
388 curr[2] = end[1] = self.__map[key] = [key, curr, end]
369 curr[2] = end[1] = self.__map[key] = [key, curr, end]
389 dict.__setitem__(self, key, value)
370 dict.__setitem__(self, key, value)
390
371
391 def __delitem__(self, key):
372 def __delitem__(self, key):
392 dict.__delitem__(self, key)
373 dict.__delitem__(self, key)
393 key, prev, next = self.__map.pop(key)
374 key, prev, next = self.__map.pop(key)
394 prev[2] = next
375 prev[2] = next
395 next[1] = prev
376 next[1] = prev
396
377
397 def __iter__(self):
378 def __iter__(self):
398 end = self.__end
379 end = self.__end
399 curr = end[2]
380 curr = end[2]
400 while curr is not end:
381 while curr is not end:
401 yield curr[0]
382 yield curr[0]
402 curr = curr[2]
383 curr = curr[2]
403
384
404 def __reversed__(self):
385 def __reversed__(self):
405 end = self.__end
386 end = self.__end
406 curr = end[1]
387 curr = end[1]
407 while curr is not end:
388 while curr is not end:
408 yield curr[0]
389 yield curr[0]
409 curr = curr[1]
390 curr = curr[1]
410
391
411 def popitem(self, last=True):
392 def popitem(self, last=True):
412 if not self:
393 if not self:
413 raise KeyError('dictionary is empty')
394 raise KeyError('dictionary is empty')
414 if last:
395 if last:
415 key = reversed(self).next()
396 key = reversed(self).next()
416 else:
397 else:
417 key = iter(self).next()
398 key = iter(self).next()
418 value = self.pop(key)
399 value = self.pop(key)
419 return key, value
400 return key, value
420
401
421 def __reduce__(self):
402 def __reduce__(self):
422 items = [[k, self[k]] for k in self]
403 items = [[k, self[k]] for k in self]
423 tmp = self.__map, self.__end
404 tmp = self.__map, self.__end
424 del self.__map, self.__end
405 del self.__map, self.__end
425 inst_dict = vars(self).copy()
406 inst_dict = vars(self).copy()
426 self.__map, self.__end = tmp
407 self.__map, self.__end = tmp
427 if inst_dict:
408 if inst_dict:
428 return (self.__class__, (items,), inst_dict)
409 return (self.__class__, (items,), inst_dict)
429 return self.__class__, (items,)
410 return self.__class__, (items,)
430
411
431 def keys(self):
412 def keys(self):
432 return list(self)
413 return list(self)
433
414
434 setdefault = DictMixin.setdefault
415 setdefault = DictMixin.setdefault
435 update = DictMixin.update
416 update = DictMixin.update
436 pop = DictMixin.pop
417 pop = DictMixin.pop
437 values = DictMixin.values
418 values = DictMixin.values
438 items = DictMixin.items
419 items = DictMixin.items
439 iterkeys = DictMixin.iterkeys
420 iterkeys = DictMixin.iterkeys
440 itervalues = DictMixin.itervalues
421 itervalues = DictMixin.itervalues
441 iteritems = DictMixin.iteritems
422 iteritems = DictMixin.iteritems
442
423
443 def __repr__(self):
424 def __repr__(self):
444 if not self:
425 if not self:
445 return '%s()' % (self.__class__.__name__,)
426 return '%s()' % (self.__class__.__name__,)
446 return '%s(%r)' % (self.__class__.__name__, self.items())
427 return '%s(%r)' % (self.__class__.__name__, self.items())
447
428
448 def copy(self):
429 def copy(self):
449 return self.__class__(self)
430 return self.__class__(self)
450
431
451 @classmethod
432 @classmethod
452 def fromkeys(cls, iterable, value=None):
433 def fromkeys(cls, iterable, value=None):
453 d = cls()
434 d = cls()
454 for key in iterable:
435 for key in iterable:
455 d[key] = value
436 d[key] = value
456 return d
437 return d
457
438
458 def __eq__(self, other):
439 def __eq__(self, other):
459 if isinstance(other, OrderedDict):
440 if isinstance(other, OrderedDict):
460 return len(self) == len(other) and self.items() == other.items()
441 return len(self) == len(other) and self.items() == other.items()
461 return dict.__eq__(self, other)
442 return dict.__eq__(self, other)
462
443
463 def __ne__(self, other):
444 def __ne__(self, other):
464 return not self == other
445 return not self == other
465
446
466
447
467 #===============================================================================
448 #===============================================================================
468 # TEST FUNCTIONS AND CREATORS
449 # TEST FUNCTIONS AND CREATORS
469 #===============================================================================
450 #===============================================================================
470 def create_test_index(repo_location, full_index):
451 def create_test_index(repo_location, full_index):
471 """Makes default test index
452 """Makes default test index
472 :param repo_location:
453 :param repo_location:
473 :param full_index:
454 :param full_index:
474 """
455 """
475 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
456 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
476 from rhodecode.lib.pidlock import DaemonLock, LockHeld
457 from rhodecode.lib.pidlock import DaemonLock, LockHeld
477 from rhodecode.lib.indexers import IDX_LOCATION
458 from rhodecode.lib.indexers import IDX_LOCATION
478 import shutil
459 import shutil
479
460
480 if os.path.exists(IDX_LOCATION):
461 if os.path.exists(IDX_LOCATION):
481 shutil.rmtree(IDX_LOCATION)
462 shutil.rmtree(IDX_LOCATION)
482
463
483 try:
464 try:
484 l = DaemonLock()
465 l = DaemonLock()
485 WhooshIndexingDaemon(repo_location=repo_location)\
466 WhooshIndexingDaemon(repo_location=repo_location)\
486 .run(full_index=full_index)
467 .run(full_index=full_index)
487 l.release()
468 l.release()
488 except LockHeld:
469 except LockHeld:
489 pass
470 pass
490
471
491 def create_test_env(repos_test_path, config):
472 def create_test_env(repos_test_path, config):
492 """Makes a fresh database and
473 """Makes a fresh database and
493 install test repository into tmp dir
474 install test repository into tmp dir
494 """
475 """
495 from rhodecode.lib.db_manage import DbManage
476 from rhodecode.lib.db_manage import DbManage
496 import tarfile
477 import tarfile
497 import shutil
478 import shutil
498 from os.path import dirname as dn, join as jn, abspath
479 from os.path import dirname as dn, join as jn, abspath
499
480
500 log = logging.getLogger('TestEnvCreator')
481 log = logging.getLogger('TestEnvCreator')
501 # create logger
482 # create logger
502 log.setLevel(logging.DEBUG)
483 log.setLevel(logging.DEBUG)
503 log.propagate = True
484 log.propagate = True
504 # create console handler and set level to debug
485 # create console handler and set level to debug
505 ch = logging.StreamHandler()
486 ch = logging.StreamHandler()
506 ch.setLevel(logging.DEBUG)
487 ch.setLevel(logging.DEBUG)
507
488
508 # create formatter
489 # create formatter
509 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
490 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
510
491
511 # add formatter to ch
492 # add formatter to ch
512 ch.setFormatter(formatter)
493 ch.setFormatter(formatter)
513
494
514 # add ch to logger
495 # add ch to logger
515 log.addHandler(ch)
496 log.addHandler(ch)
516
497
517 #PART ONE create db
498 #PART ONE create db
518 dbname = config['sqlalchemy.db1.url'].split('/')[-1]
499 dbname = config['sqlalchemy.db1.url'].split('/')[-1]
519 log.debug('making test db %s', dbname)
500 log.debug('making test db %s', dbname)
520
501
521 dbmanage = DbManage(log_sql=True, dbname=dbname, root=config['here'],
502 dbmanage = DbManage(log_sql=True, dbname=dbname, root=config['here'],
522 tests=True)
503 tests=True)
523 dbmanage.create_tables(override=True)
504 dbmanage.create_tables(override=True)
524 dbmanage.config_prompt(repos_test_path)
505 dbmanage.config_prompt(repos_test_path)
525 dbmanage.create_default_user()
506 dbmanage.create_default_user()
526 dbmanage.admin_prompt()
507 dbmanage.admin_prompt()
527 dbmanage.create_permissions()
508 dbmanage.create_permissions()
528 dbmanage.populate_default_permissions()
509 dbmanage.populate_default_permissions()
529
510
530 #PART TWO make test repo
511 #PART TWO make test repo
531 log.debug('making test vcs repo')
512 log.debug('making test vcs repo')
532 if os.path.isdir('/tmp/vcs_test'):
513 if os.path.isdir('/tmp/vcs_test'):
533 shutil.rmtree('/tmp/vcs_test')
514 shutil.rmtree('/tmp/vcs_test')
534
515
535 cur_dir = dn(dn(abspath(__file__)))
516 cur_dir = dn(dn(abspath(__file__)))
536 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test.tar.gz"))
517 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test.tar.gz"))
537 tar.extractall('/tmp')
518 tar.extractall('/tmp')
538 tar.close()
519 tar.close()
@@ -1,183 +1,168 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Model for RhodeCode
3 # Model for RhodeCode
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 9, 2010
21 Created on April 9, 2010
22 Model for RhodeCode
22 Model for RhodeCode
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from beaker.cache import cache_region
25 from beaker.cache import cache_region, region_invalidate
26 from mercurial import ui
26 from mercurial import ui
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.utils import invalidate_cache
29 from rhodecode.lib.auth import HasRepoPermissionAny
28 from rhodecode.lib.auth import HasRepoPermissionAny
29 from rhodecode.lib.utils import get_repos
30 from rhodecode.model import meta
30 from rhodecode.model import meta
31 from rhodecode.model.db import Repository, User
31 from rhodecode.model.caching_query import FromCache
32 from rhodecode.model.db import Repository, User, RhodeCodeUi
32 from sqlalchemy.orm import joinedload
33 from sqlalchemy.orm import joinedload
34 from vcs import get_repo as vcs_get_repo, get_backend
35 from vcs.backends.hg import MercurialRepository
33 from vcs.exceptions import RepositoryError, VCSError
36 from vcs.exceptions import RepositoryError, VCSError
37 from vcs.utils.lazy import LazyProperty
34 import logging
38 import logging
35 import sys
39 import os
36 import time
40 import time
37
41
38 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
39
43
40 try:
41 from vcs.backends.hg import MercurialRepository
42 from vcs.backends.git import GitRepository
43 except ImportError:
44 sys.stderr.write('You have to import vcs module')
45 raise Exception('Unable to import vcs')
46
47 def _get_repos_cached_initial(app_globals, initial):
48 """return cached dict with repos
49 """
50 g = app_globals
51 return HgModel().repo_scan(g.paths[0][1], g.baseui, initial)
52
53 @cache_region('long_term', 'cached_repo_list')
54 def _get_repos_cached():
55 """return cached dict with repos
56 """
57 log.info('getting all repositories list')
58 from pylons import app_globals as g
59 return HgModel().repo_scan(g.paths[0][1], g.baseui)
60
61 @cache_region('super_short_term', 'cached_repos_switcher_list')
62 def _get_repos_switcher_cached(cached_repo_list):
63 repos_lst = []
64 for repo in [x for x in cached_repo_list.values()]:
65 if HasRepoPermissionAny('repository.write', 'repository.read',
66 'repository.admin')(repo.name, 'main page check'):
67 repos_lst.append((repo.name, repo.dbrepo.private,))
68
69 return sorted(repos_lst, key=lambda k:k[0].lower())
70
71 @cache_region('long_term', 'full_changelog')
72 def _full_changelog_cached(repo_name):
73 log.info('getting full changelog for %s', repo_name)
74 return list(reversed(list(HgModel().get_repo(repo_name))))
75
76 class HgModel(object):
44 class HgModel(object):
77 """
45 """
78 Mercurial Model
46 Mercurial Model
79 """
47 """
80
48
81 def __init__(self, sa=None):
49 def __init__(self, sa=None):
82 if not sa:
50 if not sa:
83 self.sa = meta.Session()
51 self.sa = meta.Session()
84 else:
52 else:
85 self.sa = sa
53 self.sa = sa
86
54
55
56 @LazyProperty
57 def repos_path(self):
58 """
59 Get's the repositories root path from database
60 """
61 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
62
63 return q.ui_value
64
87 def repo_scan(self, repos_path, baseui, initial=False):
65 def repo_scan(self, repos_path, baseui, initial=False):
88 """
66 """
89 Listing of repositories in given path. This path should not be a
67 Listing of repositories in given path. This path should not be a
90 repository itself. Return a dictionary of repository objects
68 repository itself. Return a dictionary of repository objects
91
69
92 :param repos_path: path to directory containing repositories
70 :param repos_path: path to directory containing repositories
93 :param baseui
71 :param baseui
94 :param initial: initial scann
72 :param initial: initial scan
95 """
73 """
96 log.info('scanning for repositories in %s', repos_path)
74 log.info('scanning for repositories in %s', repos_path)
97
75
98 if not isinstance(baseui, ui.ui):
76 if not isinstance(baseui, ui.ui):
99 baseui = ui.ui()
77 baseui = ui.ui()
100
101 from rhodecode.lib.utils import get_repos
102 repos = get_repos(repos_path)
103
104
105 repos_list = {}
78 repos_list = {}
106 for name, path in repos:
79 for name, path in get_repos(repos_path):
107 try:
80 try:
108 #name = name.split('/')[-1]
109 if repos_list.has_key(name):
81 if repos_list.has_key(name):
110 raise RepositoryError('Duplicate repository name %s found in'
82 raise RepositoryError('Duplicate repository name %s '
111 ' %s' % (name, path))
83 'found in %s' % (name, path))
112 else:
84 else:
85
86 klass = get_backend(path[0])
87
113 if path[0] == 'hg':
88 if path[0] == 'hg':
114 repos_list[name] = MercurialRepository(path[1], baseui=baseui)
89 repos_list[name] = klass(path[1], baseui=baseui)
115 repos_list[name].name = name
116
90
117 if path[0] == 'git':
91 if path[0] == 'git':
118 repos_list[name] = GitRepository(path[1])
92 repos_list[name] = klass(path[1])
119 repos_list[name].name = name
120
121 dbrepo = None
122 if not initial:
123 #for initial scann on application first run we don't
124 #have db repos yet.
125 dbrepo = self.sa.query(Repository)\
126 .options(joinedload(Repository.fork))\
127 .filter(Repository.repo_name == name)\
128 .scalar()
129
130 if dbrepo:
131 log.info('Adding db instance to cached list')
132 repos_list[name].dbrepo = dbrepo
133 repos_list[name].description = dbrepo.description
134 if dbrepo.user:
135 repos_list[name].contact = dbrepo.user.full_contact
136 else:
137 repos_list[name].contact = self.sa.query(User)\
138 .filter(User.admin == True).first().full_contact
139 except OSError:
93 except OSError:
140 continue
94 continue
141
95
142 return repos_list
96 return repos_list
143
97
144 def get_repos(self):
98 def get_repos(self, all_repos=None):
145 for name, repo in _get_repos_cached().items():
99 """
100 Get all repos from db and for each such repo make backend and
101 fetch dependent data from db
102 """
103 if not all_repos:
104 all_repos = self.sa.query(Repository).all()
146
105
147 if isinstance(repo, MercurialRepository) and repo._get_hidden():
106 for r in all_repos:
148 #skip hidden web repository
107
149 continue
108 repo = self.get(r.repo_name)
150
109
151 last_change = repo.last_change
110 if repo is not None:
152 tip = h.get_changeset_safe(repo, 'tip')
111 last_change = repo.last_change
112 tip = h.get_changeset_safe(repo, 'tip')
153
113
154 tmp_d = {}
114 tmp_d = {}
155 tmp_d['name'] = repo.name
115 tmp_d['name'] = repo.name
156 tmp_d['name_sort'] = tmp_d['name'].lower()
116 tmp_d['name_sort'] = tmp_d['name'].lower()
157 tmp_d['description'] = repo.description
117 tmp_d['description'] = repo.dbrepo.description
158 tmp_d['description_sort'] = tmp_d['description']
118 tmp_d['description_sort'] = tmp_d['description']
159 tmp_d['last_change'] = last_change
119 tmp_d['last_change'] = last_change
160 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
120 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
161 tmp_d['tip'] = tip.raw_id
121 tmp_d['tip'] = tip.raw_id
162 tmp_d['tip_sort'] = tip.revision
122 tmp_d['tip_sort'] = tip.revision
163 tmp_d['rev'] = tip.revision
123 tmp_d['rev'] = tip.revision
164 tmp_d['contact'] = repo.contact
124 tmp_d['contact'] = repo.dbrepo.user.full_contact
165 tmp_d['contact_sort'] = tmp_d['contact']
125 tmp_d['contact_sort'] = tmp_d['contact']
166 tmp_d['repo_archives'] = list(repo._get_archives())
126 tmp_d['repo_archives'] = list(repo._get_archives())
167 tmp_d['last_msg'] = tip.message
127 tmp_d['last_msg'] = tip.message
168 tmp_d['repo'] = repo
128 tmp_d['repo'] = repo
169 yield tmp_d
129 yield tmp_d
170
130
171 def get_repo(self, repo_name):
131 def get_repo(self, repo_name):
172 try:
132 return self.get(repo_name)
173 repo = _get_repos_cached()[repo_name]
133
174 return repo
134 def get(self, repo_name):
175 except KeyError:
135 """
176 #i we're here and we got key errors let's try to invalidate the
136 Get's repository from given name, creates BackendInstance and
177 #cahce and try again
137 propagates it's data from database with all additional information
178 invalidate_cache('cached_repo_list')
138 :param repo_name:
179 repo = _get_repos_cached()[repo_name]
139 """
140 if not HasRepoPermissionAny('repository.read', 'repository.write',
141 'repository.admin')(repo_name, 'get repo check'):
142 return
143
144 @cache_region('long_term', 'get_repo_cached_%s' % repo_name)
145 def _get_repo(repo_name):
146
147 repo = vcs_get_repo(os.path.join(self.repos_path, repo_name),
148 alias=None, create=False)
149
150 #skip hidden web repository
151 if isinstance(repo, MercurialRepository) and repo._get_hidden():
152 return
153
154 dbrepo = self.sa.query(Repository)\
155 .options(joinedload(Repository.fork))\
156 .options(joinedload(Repository.user))\
157 .filter(Repository.repo_name == repo_name)\
158 .scalar()
159 repo.dbrepo = dbrepo
180 return repo
160 return repo
181
161
162 invalidate = False
163 if invalidate:
164 log.info('INVALIDATING CACHE FOR %s', repo_name)
165 region_invalidate(_get_repo, None, repo_name)
182
166
167 return _get_repo(repo_name)
183
168
@@ -1,188 +1,188 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('My account')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
5 ${_('My account')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${_('My Account')}
9 ${_('My Account')}
10 </%def>
10 </%def>
11
11
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="main()">
16 <%def name="main()">
17
17
18 <div class="box box-left">
18 <div class="box box-left">
19 <!-- box / title -->
19 <!-- box / title -->
20 <div class="title">
20 <div class="title">
21 ${self.breadcrumbs()}
21 ${self.breadcrumbs()}
22 </div>
22 </div>
23 <!-- end box / title -->
23 <!-- end box / title -->
24 <div class="ui-tabs-panel ui-widget-content ui-corner-bottom">
24 <div class="ui-tabs-panel ui-widget-content ui-corner-bottom">
25 ${h.form(url('admin_settings_my_account_update'),method='put')}
25 ${h.form(url('admin_settings_my_account_update'),method='put')}
26 <div class="form">
26 <div class="form">
27 <div class="fields">
27 <div class="fields">
28 <div class="field">
28 <div class="field">
29 <div class="label">
29 <div class="label">
30 <label for="username">${_('Username')}:</label>
30 <label for="username">${_('Username')}:</label>
31 </div>
31 </div>
32 <div class="input">
32 <div class="input">
33 ${h.text('username')}
33 ${h.text('username')}
34 </div>
34 </div>
35 </div>
35 </div>
36
36
37 <div class="field">
37 <div class="field">
38 <div class="label">
38 <div class="label">
39 <label for="new_password">${_('New password')}:</label>
39 <label for="new_password">${_('New password')}:</label>
40 </div>
40 </div>
41 <div class="input">
41 <div class="input">
42 ${h.password('new_password')}
42 ${h.password('new_password')}
43 </div>
43 </div>
44 </div>
44 </div>
45
45
46 <div class="field">
46 <div class="field">
47 <div class="label">
47 <div class="label">
48 <label for="name">${_('Name')}:</label>
48 <label for="name">${_('Name')}:</label>
49 </div>
49 </div>
50 <div class="input">
50 <div class="input">
51 ${h.text('name')}
51 ${h.text('name')}
52 </div>
52 </div>
53 </div>
53 </div>
54
54
55 <div class="field">
55 <div class="field">
56 <div class="label">
56 <div class="label">
57 <label for="lastname">${_('Lastname')}:</label>
57 <label for="lastname">${_('Lastname')}:</label>
58 </div>
58 </div>
59 <div class="input">
59 <div class="input">
60 ${h.text('lastname')}
60 ${h.text('lastname')}
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <div class="field">
64 <div class="field">
65 <div class="label">
65 <div class="label">
66 <label for="email">${_('Email')}:</label>
66 <label for="email">${_('Email')}:</label>
67 </div>
67 </div>
68 <div class="input">
68 <div class="input">
69 ${h.text('email')}
69 ${h.text('email')}
70 </div>
70 </div>
71 </div>
71 </div>
72
72
73 <div class="buttons">
73 <div class="buttons">
74 ${h.submit('save','save',class_="ui-button ui-widget ui-state-default ui-corner-all")}
74 ${h.submit('save','save',class_="ui-button ui-widget ui-state-default ui-corner-all")}
75 </div>
75 </div>
76 </div>
76 </div>
77 </div>
77 </div>
78 ${h.end_form()}
78 ${h.end_form()}
79 </div>
79 </div>
80 </div>
80 </div>
81
81
82 <div class="box box-right">
82 <div class="box box-right">
83 <!-- box / title -->
83 <!-- box / title -->
84 <div class="title">
84 <div class="title">
85 <h5>${_('My repositories')}
85 <h5>${_('My repositories')}
86 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
86 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
87 </h5>
87 </h5>
88 </div>
88 </div>
89 <!-- end box / title -->
89 <!-- end box / title -->
90 <div class="table">
90 <div class="table">
91 <table>
91 <table>
92 <thead>
92 <thead>
93 <tr>
93 <tr>
94 <th class="left">${_('Name')}</th>
94 <th class="left">${_('Name')}</th>
95 <th class="left">${_('revision')}</th>
95 <th class="left">${_('revision')}</th>
96 <th colspan="2" class="left">${_('action')}</th>
96 <th colspan="2" class="left">${_('action')}</th>
97 </thead>
97 </thead>
98 <tbody>
98 <tbody>
99 %if c.user_repos:
99 %if c.user_repos:
100 %for repo in c.user_repos:
100 %for repo in c.user_repos:
101 <tr>
101 <tr>
102 <td>
102 <td>
103 %if repo.dbrepo.repo_type =='hg':
103 %if repo['repo'].dbrepo.repo_type =='hg':
104 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="/images/icons/hgicon.png"/>
104 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="/images/icons/hgicon.png"/>
105 %elif repo.dbrepo.repo_type =='git':
105 %elif repo['repo'].dbrepo.repo_type =='git':
106 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="/images/icons/giticon.png"/>
106 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="/images/icons/giticon.png"/>
107 %else:
107 %else:
108
108
109 %endif
109 %endif
110 %if repo.dbrepo.private:
110 %if repo['repo'].dbrepo.private:
111 <img class="icon" alt="${_('private')}" src="/images/icons/lock.png"/>
111 <img class="icon" alt="${_('private')}" src="/images/icons/lock.png"/>
112 %else:
112 %else:
113 <img class="icon" alt="${_('public')}" src="/images/icons/lock_open.png"/>
113 <img class="icon" alt="${_('public')}" src="/images/icons/lock_open.png"/>
114 %endif
114 %endif
115
115
116 ${h.link_to(repo.name, h.url('summary_home',repo_name=repo.name),class_="repo_name")}
116 ${h.link_to(repo['repo'].name, h.url('summary_home',repo_name=repo['repo'].name),class_="repo_name")}
117 %if repo.dbrepo.fork:
117 %if repo['repo'].dbrepo.fork:
118 <a href="${h.url('summary_home',repo_name=repo.dbrepo.fork.repo_name)}">
118 <a href="${h.url('summary_home',repo_name=repo['repo'].dbrepo.fork.repo_name)}">
119 <img class="icon" alt="${_('public')}"
119 <img class="icon" alt="${_('public')}"
120 title="${_('Fork of')} ${repo.dbrepo.fork.repo_name}"
120 title="${_('Fork of')} ${repo['repo'].dbrepo.fork.repo_name}"
121 src="/images/icons/arrow_divide.png"/></a>
121 src="/images/icons/arrow_divide.png"/></a>
122 %endif
122 %endif
123 </td>
123 </td>
124 <td><span class="tooltip" tooltip_title="${repo.last_change}">${("r%s:%s") % (h.get_changeset_safe(repo,'tip').revision,h.short_id(h.get_changeset_safe(repo,'tip').raw_id))}</span></td>
124 <td><span class="tooltip" tooltip_title="${repo['repo'].last_change}">${("r%s:%s") % (h.get_changeset_safe(repo['repo'],'tip').revision,h.short_id(h.get_changeset_safe(repo['repo'],'tip').raw_id))}</span></td>
125 <td><a href="${h.url('repo_settings_home',repo_name=repo.name)}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="/images/icons/application_form_edit.png"/></a></td>
125 <td><a href="${h.url('repo_settings_home',repo_name=repo['repo'].name)}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="/images/icons/application_form_edit.png"/></a></td>
126 <td>
126 <td>
127 ${h.form(url('repo_settings_delete', repo_name=repo.name),method='delete')}
127 ${h.form(url('repo_settings_delete', repo_name=repo['repo'].name),method='delete')}
128 ${h.submit('remove_%s' % repo.name,'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
128 ${h.submit('remove_%s' % repo['repo'].name,'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
129 ${h.end_form()}
129 ${h.end_form()}
130 </td>
130 </td>
131 </tr>
131 </tr>
132 %endfor
132 %endfor
133 %else:
133 %else:
134 ${_('No repositories yet')}
134 ${_('No repositories yet')}
135 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
135 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
136 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'))}
136 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'))}
137 %endif
137 %endif
138 %endif
138 %endif
139 </tbody>
139 </tbody>
140 </table>
140 </table>
141 </div>
141 </div>
142
142
143 </div>
143 </div>
144 <script type="text/javascript">
144 <script type="text/javascript">
145 var D = YAHOO.util.Dom;
145 var D = YAHOO.util.Dom;
146 var E = YAHOO.util.Event;
146 var E = YAHOO.util.Event;
147 var S = YAHOO.util.Selector;
147 var S = YAHOO.util.Selector;
148
148
149 var q_filter = D.get('q_filter');
149 var q_filter = D.get('q_filter');
150 var F = YAHOO.namespace('q_filter');
150 var F = YAHOO.namespace('q_filter');
151
151
152 E.on(q_filter,'click',function(){
152 E.on(q_filter,'click',function(){
153 q_filter.value = '';
153 q_filter.value = '';
154 });
154 });
155
155
156 F.filterTimeout = null;
156 F.filterTimeout = null;
157
157
158 F.updateFilter = function() {
158 F.updateFilter = function() {
159 // Reset timeout
159 // Reset timeout
160 F.filterTimeout = null;
160 F.filterTimeout = null;
161
161
162 var obsolete = [];
162 var obsolete = [];
163 var nodes = S.query('div.table tr td a.repo_name');
163 var nodes = S.query('div.table tr td a.repo_name');
164 var req = D.get('q_filter').value;
164 var req = D.get('q_filter').value;
165 for (n in nodes){
165 for (n in nodes){
166 D.setStyle(nodes[n].parentNode.parentNode,'display','')
166 D.setStyle(nodes[n].parentNode.parentNode,'display','')
167 }
167 }
168 if (req){
168 if (req){
169 for (n in nodes){
169 for (n in nodes){
170 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
170 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
171 obsolete.push(nodes[n]);
171 obsolete.push(nodes[n]);
172 }
172 }
173 }
173 }
174 if(obsolete){
174 if(obsolete){
175 for (n in obsolete){
175 for (n in obsolete){
176 D.setStyle(obsolete[n].parentNode.parentNode,'display','none');
176 D.setStyle(obsolete[n].parentNode.parentNode,'display','none');
177 }
177 }
178 }
178 }
179 }
179 }
180 }
180 }
181
181
182 E.on(q_filter,'keyup',function(e){
182 E.on(q_filter,'keyup',function(e){
183 clearTimeout(F.filterTimeout);
183 clearTimeout(F.filterTimeout);
184 setTimeout(F.updateFilter,600);
184 setTimeout(F.updateFilter,600);
185 });
185 });
186
186
187 </script>
187 </script>
188 </%def> No newline at end of file
188 </%def>
@@ -1,285 +1,286 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
4 <head>
4 <head>
5 <title>${next.title()}</title>
5 <title>${next.title()}</title>
6 <link rel="icon" href="/images/hgicon.png" type="image/png" />
6 <link rel="icon" href="/images/hgicon.png" type="image/png" />
7 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
8 <meta name="robots" content="index, nofollow"/>
8 <meta name="robots" content="index, nofollow"/>
9 <!-- stylesheets -->
9 <!-- stylesheets -->
10 ${self.css()}
10 ${self.css()}
11 <!-- scripts -->
11 <!-- scripts -->
12 ${self.js()}
12 ${self.js()}
13 </head>
13 </head>
14 <body>
14 <body>
15 <!-- header -->
15 <!-- header -->
16 <div id="header">
16 <div id="header">
17 <!-- user -->
17 <!-- user -->
18 <ul id="logged-user">
18 <ul id="logged-user">
19 <li class="first">
19 <li class="first">
20 <div class="gravatar">
20 <div class="gravatar">
21 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,24)}" />
21 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,24)}" />
22 </div>
22 </div>
23 <div class="account">
23 <div class="account">
24 ${h.link_to('%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname),h.url('admin_settings_my_account'))}<br/>
24 ${h.link_to('%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname),h.url('admin_settings_my_account'))}<br/>
25 ${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'))}
25 ${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'))}
26 </div>
26 </div>
27 </li>
27 </li>
28 <li class="last highlight">${h.link_to(u'Logout',h.url('logout_home'))}</li>
28 <li class="last highlight">${h.link_to(u'Logout',h.url('logout_home'))}</li>
29 </ul>
29 </ul>
30 <!-- end user -->
30 <!-- end user -->
31 <div id="header-inner" class="title top-left-rounded-corner top-right-rounded-corner">
31 <div id="header-inner" class="title top-left-rounded-corner top-right-rounded-corner">
32 <!-- logo -->
32 <!-- logo -->
33 <div id="logo">
33 <div id="logo">
34 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
34 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
35 </div>
35 </div>
36 <!-- end logo -->
36 <!-- end logo -->
37 <!-- menu -->
37 <!-- menu -->
38 ${self.page_nav()}
38 ${self.page_nav()}
39 <!-- quick -->
39 <!-- quick -->
40 </div>
40 </div>
41 </div>
41 </div>
42 <!-- end header -->
42 <!-- end header -->
43
43
44 <!-- CONTENT -->
44 <!-- CONTENT -->
45 <div id="content">
45 <div id="content">
46 <div class="flash_msg">
46 <div class="flash_msg">
47 <% messages = h.flash.pop_messages() %>
47 <% messages = h.flash.pop_messages() %>
48 % if messages:
48 % if messages:
49 <ul id="flash-messages">
49 <ul id="flash-messages">
50 % for message in messages:
50 % for message in messages:
51 <li class="${message.category}_msg">${message}</li>
51 <li class="${message.category}_msg">${message}</li>
52 % endfor
52 % endfor
53 </ul>
53 </ul>
54 % endif
54 % endif
55 </div>
55 </div>
56 <div id="main">
56 <div id="main">
57 ${next.main()}
57 ${next.main()}
58 </div>
58 </div>
59 </div>
59 </div>
60 <!-- END CONTENT -->
60 <!-- END CONTENT -->
61
61
62 <!-- footer -->
62 <!-- footer -->
63 <div id="footer">
63 <div id="footer">
64 <div id="footer-inner" class="title bottom-left-rounded-corner bottom-right-rounded-corner">
64 <div id="footer-inner" class="title bottom-left-rounded-corner bottom-right-rounded-corner">
65 <div>
65 <div>
66 <p class="footer-link">${h.link_to(_('Submit a bug'),h.url('bugtracker'))}</p>
66 <p class="footer-link">${h.link_to(_('Submit a bug'),h.url('bugtracker'))}</p>
67 <p class="footer-link">${h.link_to(_('GPL license'),h.url('gpl_license'))}</p>
67 <p class="footer-link">${h.link_to(_('GPL license'),h.url('gpl_license'))}</p>
68 <p>RhodeCode ${c.rhodecode_version} &copy; 2010 by Marcin Kuzminski</p>
68 <p>RhodeCode ${c.rhodecode_version} &copy; 2010 by Marcin Kuzminski</p>
69 </div>
69 </div>
70 </div>
70 </div>
71 <script type="text/javascript">${h.tooltip.activate()}</script>
71 <script type="text/javascript">${h.tooltip.activate()}</script>
72 </div>
72 </div>
73 <!-- end footer -->
73 <!-- end footer -->
74 </body>
74 </body>
75
75
76 </html>
76 </html>
77
77
78 ### MAKO DEFS ###
78 ### MAKO DEFS ###
79 <%def name="page_nav()">
79 <%def name="page_nav()">
80 ${self.menu()}
80 ${self.menu()}
81 </%def>
81 </%def>
82
82
83 <%def name="menu(current=None)">
83 <%def name="menu(current=None)">
84 <%
84 <%
85 def is_current(selected):
85 def is_current(selected):
86 if selected == current:
86 if selected == current:
87 return h.literal('class="current"')
87 return h.literal('class="current"')
88 %>
88 %>
89 %if current not in ['home','admin']:
89 %if current not in ['home','admin']:
90 ##REGULAR MENU
90 ##REGULAR MENU
91 <ul id="quick">
91 <ul id="quick">
92 <!-- repo switcher -->
92 <!-- repo switcher -->
93 <li>
93 <li>
94 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
94 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
95 <span class="icon">
95 <span class="icon">
96 <img src="/images/icons/database.png" alt="${_('Products')}" />
96 <img src="/images/icons/database.png" alt="${_('Products')}" />
97 </span>
97 </span>
98 <span>&darr;</span>
98 <span>&darr;</span>
99 </a>
99 </a>
100 <ul class="repo_switcher">
100 <ul class="repo_switcher">
101 %for repo,private in c.repo_switcher_list:
101 %for repo in c.cached_repo_list:
102 %if private:
102
103 <li>${h.link_to(repo,h.url('summary_home',repo_name=repo),class_="private_repo")}</li>
103 %if repo['repo'].dbrepo.private:
104 <li>${h.link_to(repo['repo'].name,h.url('summary_home',repo_name=repo['repo'].name),class_="private_repo %s" % repo['repo'].dbrepo.repo_type)}</li>
104 %else:
105 %else:
105 <li>${h.link_to(repo,h.url('summary_home',repo_name=repo),class_="public_repo")}</li>
106 <li>${h.link_to(repo['repo'].name,h.url('summary_home',repo_name=repo['repo'].name),class_="public_repo %s" % repo['repo'].dbrepo.repo_type)}</li>
106 %endif
107 %endif
107 %endfor
108 %endfor
108 </ul>
109 </ul>
109 </li>
110 </li>
110
111
111 <li ${is_current('summary')}>
112 <li ${is_current('summary')}>
112 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
113 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
113 <span class="icon">
114 <span class="icon">
114 <img src="/images/icons/clipboard_16.png" alt="${_('Summary')}" />
115 <img src="/images/icons/clipboard_16.png" alt="${_('Summary')}" />
115 </span>
116 </span>
116 <span>${_('Summary')}</span>
117 <span>${_('Summary')}</span>
117 </a>
118 </a>
118 </li>
119 </li>
119 ##<li ${is_current('shortlog')}>
120 ##<li ${is_current('shortlog')}>
120 ## <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
121 ## <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
121 ## <span class="icon">
122 ## <span class="icon">
122 ## <img src="/images/icons/application_view_list.png" alt="${_('Shortlog')}" />
123 ## <img src="/images/icons/application_view_list.png" alt="${_('Shortlog')}" />
123 ## </span>
124 ## </span>
124 ## <span>${_('Shortlog')}</span>
125 ## <span>${_('Shortlog')}</span>
125 ## </a>
126 ## </a>
126 ##</li>
127 ##</li>
127 <li ${is_current('changelog')}>
128 <li ${is_current('changelog')}>
128 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
129 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
129 <span class="icon">
130 <span class="icon">
130 <img src="/images/icons/time.png" alt="${_('Changelog')}" />
131 <img src="/images/icons/time.png" alt="${_('Changelog')}" />
131 </span>
132 </span>
132 <span>${_('Changelog')}</span>
133 <span>${_('Changelog')}</span>
133 </a>
134 </a>
134 </li>
135 </li>
135
136
136 <li ${is_current('switch_to')}>
137 <li ${is_current('switch_to')}>
137 <a title="${_('Switch to')}" href="#">
138 <a title="${_('Switch to')}" href="#">
138 <span class="icon">
139 <span class="icon">
139 <img src="/images/icons/arrow_switch.png" alt="${_('Switch to')}" />
140 <img src="/images/icons/arrow_switch.png" alt="${_('Switch to')}" />
140 </span>
141 </span>
141 <span>${_('Switch to')}</span>
142 <span>${_('Switch to')}</span>
142 </a>
143 </a>
143 <ul>
144 <ul>
144 <li>
145 <li>
145 ${h.link_to('%s (%s)' % (_('branches'),len(c.repository_branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
146 ${h.link_to('%s (%s)' % (_('branches'),len(c.repository_branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
146 <ul>
147 <ul>
147 %if c.repository_branches.values():
148 %if c.repository_branches.values():
148 %for cnt,branch in enumerate(c.repository_branches.items()):
149 %for cnt,branch in enumerate(c.repository_branches.items()):
149 <li>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
150 <li>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
150 %endfor
151 %endfor
151 %else:
152 %else:
152 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
153 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
153 %endif
154 %endif
154 </ul>
155 </ul>
155 </li>
156 </li>
156 <li>
157 <li>
157 ${h.link_to('%s (%s)' % (_('tags'),len(c.repository_tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
158 ${h.link_to('%s (%s)' % (_('tags'),len(c.repository_tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
158 <ul>
159 <ul>
159 %if c.repository_tags.values():
160 %if c.repository_tags.values():
160 %for cnt,tag in enumerate(c.repository_tags.items()):
161 %for cnt,tag in enumerate(c.repository_tags.items()):
161 <li>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
162 <li>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
162 %endfor
163 %endfor
163 %else:
164 %else:
164 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
165 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
165 %endif
166 %endif
166 </ul>
167 </ul>
167 </li>
168 </li>
168 </ul>
169 </ul>
169 </li>
170 </li>
170 <li ${is_current('files')}>
171 <li ${is_current('files')}>
171 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
172 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
172 <span class="icon">
173 <span class="icon">
173 <img src="/images/icons/file.png" alt="${_('Files')}" />
174 <img src="/images/icons/file.png" alt="${_('Files')}" />
174 </span>
175 </span>
175 <span>${_('Files')}</span>
176 <span>${_('Files')}</span>
176 </a>
177 </a>
177 </li>
178 </li>
178
179
179 <li ${is_current('options')}>
180 <li ${is_current('options')}>
180 <a title="${_('Options')}" href="#">
181 <a title="${_('Options')}" href="#">
181 <span class="icon">
182 <span class="icon">
182 <img src="/images/icons/table_gear.png" alt="${_('Admin')}" />
183 <img src="/images/icons/table_gear.png" alt="${_('Admin')}" />
183 </span>
184 </span>
184 <span>${_('Options')}</span>
185 <span>${_('Options')}</span>
185 </a>
186 </a>
186 <ul>
187 <ul>
187 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
188 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
188 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
189 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
189 %endif
190 %endif
190 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
191 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
191 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
192 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
192
193
193 %if h.HasPermissionAll('hg.admin')('access admin main page'):
194 %if h.HasPermissionAll('hg.admin')('access admin main page'):
194 <li>
195 <li>
195 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
196 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
196 <ul>
197 <ul>
197 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
198 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
198 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
199 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
199 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
200 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
200 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
201 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
201 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
202 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
202 </ul>
203 </ul>
203 </li>
204 </li>
204 %endif
205 %endif
205
206
206
207
207 ## %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
208 ## %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
208 ## <li class="last">
209 ## <li class="last">
209 ## ${h.link_to(_('delete'),'#',class_='delete')}
210 ## ${h.link_to(_('delete'),'#',class_='delete')}
210 ## ${h.form(url('repo_settings_delete', repo_name=c.repo_name),method='delete')}
211 ## ${h.form(url('repo_settings_delete', repo_name=c.repo_name),method='delete')}
211 ## ${h.submit('remove_%s' % c.repo_name,'delete',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
212 ## ${h.submit('remove_%s' % c.repo_name,'delete',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
212 ## ${h.end_form()}
213 ## ${h.end_form()}
213 ## </li>
214 ## </li>
214 ## %endif
215 ## %endif
215 </ul>
216 </ul>
216 </li>
217 </li>
217 </ul>
218 </ul>
218 %else:
219 %else:
219 ##ROOT MENU
220 ##ROOT MENU
220 <ul id="quick">
221 <ul id="quick">
221 <li>
222 <li>
222 <a title="${_('Home')}" href="${h.url('home')}">
223 <a title="${_('Home')}" href="${h.url('home')}">
223 <span class="icon">
224 <span class="icon">
224 <img src="/images/icons/home_16.png" alt="${_('Home')}" />
225 <img src="/images/icons/home_16.png" alt="${_('Home')}" />
225 </span>
226 </span>
226 <span>${_('Home')}</span>
227 <span>${_('Home')}</span>
227 </a>
228 </a>
228 </li>
229 </li>
229
230
230 <li>
231 <li>
231 <a title="${_('Search')}" href="${h.url('search')}">
232 <a title="${_('Search')}" href="${h.url('search')}">
232 <span class="icon">
233 <span class="icon">
233 <img src="/images/icons/search_16.png" alt="${_('Search')}" />
234 <img src="/images/icons/search_16.png" alt="${_('Search')}" />
234 </span>
235 </span>
235 <span>${_('Search')}</span>
236 <span>${_('Search')}</span>
236 </a>
237 </a>
237 </li>
238 </li>
238
239
239 %if h.HasPermissionAll('hg.admin')('access admin main page'):
240 %if h.HasPermissionAll('hg.admin')('access admin main page'):
240 <li ${is_current('admin')}>
241 <li ${is_current('admin')}>
241 <a title="${_('Admin')}" href="${h.url('admin_home')}">
242 <a title="${_('Admin')}" href="${h.url('admin_home')}">
242 <span class="icon">
243 <span class="icon">
243 <img src="/images/icons/cog_edit.png" alt="${_('Admin')}" />
244 <img src="/images/icons/cog_edit.png" alt="${_('Admin')}" />
244 </span>
245 </span>
245 <span>${_('Admin')}</span>
246 <span>${_('Admin')}</span>
246 </a>
247 </a>
247 <ul>
248 <ul>
248 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
249 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
249 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
250 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
250 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
251 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
251 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
252 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
252 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
253 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
253 </ul>
254 </ul>
254 </li>
255 </li>
255 %endif
256 %endif
256
257
257 </ul>
258 </ul>
258 %endif
259 %endif
259 </%def>
260 </%def>
260
261
261
262
262 <%def name="css()">
263 <%def name="css()">
263 <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen" />
264 <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen" />
264 <link rel="stylesheet" type="text/css" href="/css/pygments.css" />
265 <link rel="stylesheet" type="text/css" href="/css/pygments.css" />
265 <link rel="stylesheet" type="text/css" href="/css/diff.css" />
266 <link rel="stylesheet" type="text/css" href="/css/diff.css" />
266 </%def>
267 </%def>
267
268
268 <%def name="js()">
269 <%def name="js()">
269 ##<script type="text/javascript" src="/js/yui/utilities/utilities.js"></script>
270 ##<script type="text/javascript" src="/js/yui/utilities/utilities.js"></script>
270 ##<script type="text/javascript" src="/js/yui/container/container.js"></script>
271 ##<script type="text/javascript" src="/js/yui/container/container.js"></script>
271 ##<script type="text/javascript" src="/js/yui/datasource/datasource.js"></script>
272 ##<script type="text/javascript" src="/js/yui/datasource/datasource.js"></script>
272 ##<script type="text/javascript" src="/js/yui/autocomplete/autocomplete.js"></script>
273 ##<script type="text/javascript" src="/js/yui/autocomplete/autocomplete.js"></script>
273 ##<script type="text/javascript" src="/js/yui/selector/selector-min.js"></script>
274 ##<script type="text/javascript" src="/js/yui/selector/selector-min.js"></script>
274
275
275 <script type="text/javascript" src="/js/yui2.js"></script>
276 <script type="text/javascript" src="/js/yui2.js"></script>
276 <script type="text/javascript" src="/js/yui/selector/selector-min.js"></script>
277 <script type="text/javascript" src="/js/yui/selector/selector-min.js"></script>
277 <!--[if IE]><script language="javascript" type="text/javascript" src="/js/excanvas.min.js"></script><![endif]-->
278 <!--[if IE]><script language="javascript" type="text/javascript" src="/js/excanvas.min.js"></script><![endif]-->
278 <script type="text/javascript" src="/js/yui.flot.js"></script>
279 <script type="text/javascript" src="/js/yui.flot.js"></script>
279 </%def>
280 </%def>
280
281
281 <%def name="breadcrumbs()">
282 <%def name="breadcrumbs()">
282 <div class="breadcrumbs">
283 <div class="breadcrumbs">
283 ${self.breadcrumbs_links()}
284 ${self.breadcrumbs_links()}
284 </div>
285 </div>
285 </%def> No newline at end of file
286 </%def>
General Comments 0
You need to be logged in to leave comments. Login now