##// END OF EJS Templates
project refactoring, cleaned up lib.utils from rarly used functions, and place them...
marcink -
r756:01be209b beta
parent child Browse files
Show More
@@ -1,314 +1,335 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.celerylib import tasks, run_task
34 from rhodecode.lib.celerylib import tasks, run_task
35 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
35 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
36 set_rhodecode_config, get_hg_settings, get_hg_ui_settings
36 set_rhodecode_config
37 from rhodecode.model.db import RhodeCodeUi, Repository
37 from rhodecode.model.db import RhodeCodeUi, Repository
38 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
38 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
39 ApplicationUiSettingsForm
39 ApplicationUiSettingsForm
40 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.settings import SettingsModel
41 from rhodecode.model.settings import SettingsModel
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43 from sqlalchemy import func
43 from sqlalchemy import func
44 import formencode
44 import formencode
45 import logging
45 import logging
46 import traceback
46 import traceback
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class SettingsController(BaseController):
51 class SettingsController(BaseController):
52 """REST Controller styled on the Atom Publishing Protocol"""
52 """REST Controller styled on the Atom Publishing Protocol"""
53 # To properly map this controller, ensure your config/routing.py
53 # To properly map this controller, ensure your config/routing.py
54 # file has a resource setup:
54 # file has a resource setup:
55 # map.resource('setting', 'settings', controller='admin/settings',
55 # map.resource('setting', 'settings', controller='admin/settings',
56 # path_prefix='/admin', name_prefix='admin_')
56 # path_prefix='/admin', name_prefix='admin_')
57
57
58
58
59 @LoginRequired()
59 @LoginRequired()
60 def __before__(self):
60 def __before__(self):
61 c.admin_user = session.get('admin_user')
61 c.admin_user = session.get('admin_user')
62 c.admin_username = session.get('admin_username')
62 c.admin_username = session.get('admin_username')
63 super(SettingsController, self).__before__()
63 super(SettingsController, self).__before__()
64
64
65
65
66 @HasPermissionAllDecorator('hg.admin')
66 @HasPermissionAllDecorator('hg.admin')
67 def index(self, format='html'):
67 def index(self, format='html'):
68 """GET /admin/settings: All items in the collection"""
68 """GET /admin/settings: All items in the collection"""
69 # url('admin_settings')
69 # url('admin_settings')
70
70
71 defaults = get_hg_settings()
71 defaults = SettingsModel().get_app_settings()
72 defaults.update(get_hg_ui_settings())
72 defaults.update(self.get_hg_ui_settings())
73 return htmlfill.render(
73 return htmlfill.render(
74 render('admin/settings/settings.html'),
74 render('admin/settings/settings.html'),
75 defaults=defaults,
75 defaults=defaults,
76 encoding="UTF-8",
76 encoding="UTF-8",
77 force_defaults=False
77 force_defaults=False
78 )
78 )
79
79
80 @HasPermissionAllDecorator('hg.admin')
80 @HasPermissionAllDecorator('hg.admin')
81 def create(self):
81 def create(self):
82 """POST /admin/settings: Create a new item"""
82 """POST /admin/settings: Create a new item"""
83 # url('admin_settings')
83 # url('admin_settings')
84
84
85 @HasPermissionAllDecorator('hg.admin')
85 @HasPermissionAllDecorator('hg.admin')
86 def new(self, format='html'):
86 def new(self, format='html'):
87 """GET /admin/settings/new: Form to create a new item"""
87 """GET /admin/settings/new: Form to create a new item"""
88 # url('admin_new_setting')
88 # url('admin_new_setting')
89
89
90 @HasPermissionAllDecorator('hg.admin')
90 @HasPermissionAllDecorator('hg.admin')
91 def update(self, setting_id):
91 def update(self, setting_id):
92 """PUT /admin/settings/setting_id: Update an existing item"""
92 """PUT /admin/settings/setting_id: Update an existing item"""
93 # Forms posted to this method should contain a hidden field:
93 # Forms posted to this method should contain a hidden field:
94 # <input type="hidden" name="_method" value="PUT" />
94 # <input type="hidden" name="_method" value="PUT" />
95 # Or using helpers:
95 # Or using helpers:
96 # h.form(url('admin_setting', setting_id=ID),
96 # h.form(url('admin_setting', setting_id=ID),
97 # method='put')
97 # method='put')
98 # url('admin_setting', setting_id=ID)
98 # url('admin_setting', setting_id=ID)
99 if setting_id == 'mapping':
99 if setting_id == 'mapping':
100 rm_obsolete = request.POST.get('destroy', False)
100 rm_obsolete = request.POST.get('destroy', False)
101 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
101 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
102
102
103 initial = ScmModel().repo_scan(g.paths[0][1], g.baseui)
103 initial = ScmModel().repo_scan(g.paths[0][1], g.baseui)
104 for repo_name in initial.keys():
104 for repo_name in initial.keys():
105 invalidate_cache('get_repo_cached_%s' % repo_name)
105 invalidate_cache('get_repo_cached_%s' % repo_name)
106
106
107 repo2db_mapper(initial, rm_obsolete)
107 repo2db_mapper(initial, rm_obsolete)
108
108
109 h.flash(_('Repositories successfully rescanned'), category='success')
109 h.flash(_('Repositories successfully rescanned'), category='success')
110
110
111 if setting_id == 'whoosh':
111 if setting_id == 'whoosh':
112 repo_location = get_hg_ui_settings()['paths_root_path']
112 repo_location = self.get_hg_ui_settings()['paths_root_path']
113 full_index = request.POST.get('full_index', False)
113 full_index = request.POST.get('full_index', False)
114 task = run_task(tasks.whoosh_index, repo_location, full_index)
114 task = run_task(tasks.whoosh_index, repo_location, full_index)
115
115
116 h.flash(_('Whoosh reindex task scheduled'), category='success')
116 h.flash(_('Whoosh reindex task scheduled'), category='success')
117 if setting_id == 'global':
117 if setting_id == 'global':
118
118
119 application_form = ApplicationSettingsForm()()
119 application_form = ApplicationSettingsForm()()
120 try:
120 try:
121 form_result = application_form.to_python(dict(request.POST))
121 form_result = application_form.to_python(dict(request.POST))
122 settings_model = SettingsModel()
122 settings_model = SettingsModel()
123 try:
123 try:
124 hgsettings1 = settings_model.get('title')
124 hgsettings1 = settings_model.get('title')
125 hgsettings1.app_settings_value = form_result['rhodecode_title']
125 hgsettings1.app_settings_value = form_result['rhodecode_title']
126
126
127 hgsettings2 = settings_model.get('realm')
127 hgsettings2 = settings_model.get('realm')
128 hgsettings2.app_settings_value = form_result['rhodecode_realm']
128 hgsettings2.app_settings_value = form_result['rhodecode_realm']
129
129
130
130
131 self.sa.add(hgsettings1)
131 self.sa.add(hgsettings1)
132 self.sa.add(hgsettings2)
132 self.sa.add(hgsettings2)
133 self.sa.commit()
133 self.sa.commit()
134 set_rhodecode_config(config)
134 set_rhodecode_config(config)
135 h.flash(_('Updated application settings'),
135 h.flash(_('Updated application settings'),
136 category='success')
136 category='success')
137
137
138 except:
138 except:
139 log.error(traceback.format_exc())
139 log.error(traceback.format_exc())
140 h.flash(_('error occurred during updating application settings'),
140 h.flash(_('error occurred during updating application settings'),
141 category='error')
141 category='error')
142
142
143 self.sa.rollback()
143 self.sa.rollback()
144
144
145
145
146 except formencode.Invalid, errors:
146 except formencode.Invalid, errors:
147 return htmlfill.render(
147 return htmlfill.render(
148 render('admin/settings/settings.html'),
148 render('admin/settings/settings.html'),
149 defaults=errors.value,
149 defaults=errors.value,
150 errors=errors.error_dict or {},
150 errors=errors.error_dict or {},
151 prefix_error=False,
151 prefix_error=False,
152 encoding="UTF-8")
152 encoding="UTF-8")
153
153
154 if setting_id == 'mercurial':
154 if setting_id == 'mercurial':
155 application_form = ApplicationUiSettingsForm()()
155 application_form = ApplicationUiSettingsForm()()
156 try:
156 try:
157 form_result = application_form.to_python(dict(request.POST))
157 form_result = application_form.to_python(dict(request.POST))
158
158
159 try:
159 try:
160
160
161 hgsettings1 = self.sa.query(RhodeCodeUi)\
161 hgsettings1 = self.sa.query(RhodeCodeUi)\
162 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
162 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
163 hgsettings1.ui_value = form_result['web_push_ssl']
163 hgsettings1.ui_value = form_result['web_push_ssl']
164
164
165 hgsettings2 = self.sa.query(RhodeCodeUi)\
165 hgsettings2 = self.sa.query(RhodeCodeUi)\
166 .filter(RhodeCodeUi.ui_key == '/').one()
166 .filter(RhodeCodeUi.ui_key == '/').one()
167 hgsettings2.ui_value = form_result['paths_root_path']
167 hgsettings2.ui_value = form_result['paths_root_path']
168
168
169
169
170 #HOOKS
170 #HOOKS
171 hgsettings3 = self.sa.query(RhodeCodeUi)\
171 hgsettings3 = self.sa.query(RhodeCodeUi)\
172 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
172 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
173 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
173 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
174
174
175 hgsettings4 = self.sa.query(RhodeCodeUi)\
175 hgsettings4 = self.sa.query(RhodeCodeUi)\
176 .filter(RhodeCodeUi.ui_key == 'changegroup.repo_size').one()
176 .filter(RhodeCodeUi.ui_key == 'changegroup.repo_size').one()
177 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
177 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
178
178
179 hgsettings5 = self.sa.query(RhodeCodeUi)\
179 hgsettings5 = self.sa.query(RhodeCodeUi)\
180 .filter(RhodeCodeUi.ui_key == 'pretxnchangegroup.push_logger').one()
180 .filter(RhodeCodeUi.ui_key == 'pretxnchangegroup.push_logger').one()
181 hgsettings5.ui_active = bool(form_result['hooks_pretxnchangegroup_push_logger'])
181 hgsettings5.ui_active = bool(form_result['hooks_pretxnchangegroup_push_logger'])
182
182
183 hgsettings6 = self.sa.query(RhodeCodeUi)\
183 hgsettings6 = self.sa.query(RhodeCodeUi)\
184 .filter(RhodeCodeUi.ui_key == 'preoutgoing.pull_logger').one()
184 .filter(RhodeCodeUi.ui_key == 'preoutgoing.pull_logger').one()
185 hgsettings6.ui_active = bool(form_result['hooks_preoutgoing_pull_logger'])
185 hgsettings6.ui_active = bool(form_result['hooks_preoutgoing_pull_logger'])
186
186
187
187
188 self.sa.add(hgsettings1)
188 self.sa.add(hgsettings1)
189 self.sa.add(hgsettings2)
189 self.sa.add(hgsettings2)
190 self.sa.add(hgsettings3)
190 self.sa.add(hgsettings3)
191 self.sa.add(hgsettings4)
191 self.sa.add(hgsettings4)
192 self.sa.add(hgsettings5)
192 self.sa.add(hgsettings5)
193 self.sa.add(hgsettings6)
193 self.sa.add(hgsettings6)
194 self.sa.commit()
194 self.sa.commit()
195
195
196 h.flash(_('Updated mercurial settings'),
196 h.flash(_('Updated mercurial settings'),
197 category='success')
197 category='success')
198
198
199 except:
199 except:
200 log.error(traceback.format_exc())
200 log.error(traceback.format_exc())
201 h.flash(_('error occurred during updating application settings'),
201 h.flash(_('error occurred during updating application settings'),
202 category='error')
202 category='error')
203
203
204 self.sa.rollback()
204 self.sa.rollback()
205
205
206
206
207 except formencode.Invalid, errors:
207 except formencode.Invalid, errors:
208 return htmlfill.render(
208 return htmlfill.render(
209 render('admin/settings/settings.html'),
209 render('admin/settings/settings.html'),
210 defaults=errors.value,
210 defaults=errors.value,
211 errors=errors.error_dict or {},
211 errors=errors.error_dict or {},
212 prefix_error=False,
212 prefix_error=False,
213 encoding="UTF-8")
213 encoding="UTF-8")
214
214
215
215
216
216
217 return redirect(url('admin_settings'))
217 return redirect(url('admin_settings'))
218
218
219 @HasPermissionAllDecorator('hg.admin')
219 @HasPermissionAllDecorator('hg.admin')
220 def delete(self, setting_id):
220 def delete(self, setting_id):
221 """DELETE /admin/settings/setting_id: Delete an existing item"""
221 """DELETE /admin/settings/setting_id: Delete an existing item"""
222 # Forms posted to this method should contain a hidden field:
222 # Forms posted to this method should contain a hidden field:
223 # <input type="hidden" name="_method" value="DELETE" />
223 # <input type="hidden" name="_method" value="DELETE" />
224 # Or using helpers:
224 # Or using helpers:
225 # h.form(url('admin_setting', setting_id=ID),
225 # h.form(url('admin_setting', setting_id=ID),
226 # method='delete')
226 # method='delete')
227 # url('admin_setting', setting_id=ID)
227 # url('admin_setting', setting_id=ID)
228
228
229 @HasPermissionAllDecorator('hg.admin')
229 @HasPermissionAllDecorator('hg.admin')
230 def show(self, setting_id, format='html'):
230 def show(self, setting_id, format='html'):
231 """GET /admin/settings/setting_id: Show a specific item"""
231 """GET /admin/settings/setting_id: Show a specific item"""
232 # url('admin_setting', setting_id=ID)
232 # url('admin_setting', setting_id=ID)
233
233
234 @HasPermissionAllDecorator('hg.admin')
234 @HasPermissionAllDecorator('hg.admin')
235 def edit(self, setting_id, format='html'):
235 def edit(self, setting_id, format='html'):
236 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
236 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
237 # url('admin_edit_setting', setting_id=ID)
237 # url('admin_edit_setting', setting_id=ID)
238
238
239
239
240 def my_account(self):
240 def my_account(self):
241 """
241 """
242 GET /_admin/my_account Displays info about my account
242 GET /_admin/my_account Displays info about my account
243 """
243 """
244
244
245 # url('admin_settings_my_account')
245 # url('admin_settings_my_account')
246 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
246 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
247 all_repos = self.sa.query(Repository)\
247 all_repos = self.sa.query(Repository)\
248 .filter(Repository.user_id == c.user.user_id)\
248 .filter(Repository.user_id == c.user.user_id)\
249 .order_by(func.lower(Repository.repo_name))\
249 .order_by(func.lower(Repository.repo_name))\
250 .all()
250 .all()
251 c.user_repos = ScmModel().get_repos(all_repos)
251 c.user_repos = ScmModel().get_repos(all_repos)
252
252
253 if c.user.username == 'default':
253 if c.user.username == 'default':
254 h.flash(_("You can't edit this user since it's"
254 h.flash(_("You can't edit this user since it's"
255 " crucial for entire application"), category='warning')
255 " crucial for entire application"), category='warning')
256 return redirect(url('users'))
256 return redirect(url('users'))
257
257
258 defaults = c.user.__dict__
258 defaults = c.user.__dict__
259 return htmlfill.render(
259 return htmlfill.render(
260 render('admin/users/user_edit_my_account.html'),
260 render('admin/users/user_edit_my_account.html'),
261 defaults=defaults,
261 defaults=defaults,
262 encoding="UTF-8",
262 encoding="UTF-8",
263 force_defaults=False
263 force_defaults=False
264 )
264 )
265
265
266 def my_account_update(self):
266 def my_account_update(self):
267 """PUT /_admin/my_account_update: Update an existing item"""
267 """PUT /_admin/my_account_update: Update an existing item"""
268 # Forms posted to this method should contain a hidden field:
268 # Forms posted to this method should contain a hidden field:
269 # <input type="hidden" name="_method" value="PUT" />
269 # <input type="hidden" name="_method" value="PUT" />
270 # Or using helpers:
270 # Or using helpers:
271 # h.form(url('admin_settings_my_account_update'),
271 # h.form(url('admin_settings_my_account_update'),
272 # method='put')
272 # method='put')
273 # url('admin_settings_my_account_update', id=ID)
273 # url('admin_settings_my_account_update', id=ID)
274 user_model = UserModel()
274 user_model = UserModel()
275 uid = c.rhodecode_user.user_id
275 uid = c.rhodecode_user.user_id
276 _form = UserForm(edit=True, old_data={'user_id':uid,
276 _form = UserForm(edit=True, old_data={'user_id':uid,
277 'email':c.rhodecode_user.email})()
277 'email':c.rhodecode_user.email})()
278 form_result = {}
278 form_result = {}
279 try:
279 try:
280 form_result = _form.to_python(dict(request.POST))
280 form_result = _form.to_python(dict(request.POST))
281 user_model.update_my_account(uid, form_result)
281 user_model.update_my_account(uid, form_result)
282 h.flash(_('Your account was updated succesfully'),
282 h.flash(_('Your account was updated succesfully'),
283 category='success')
283 category='success')
284
284
285 except formencode.Invalid, errors:
285 except formencode.Invalid, errors:
286 c.user = user_model.get(c.rhodecode_user.user_id, cache=False)
286 c.user = user_model.get(c.rhodecode_user.user_id, cache=False)
287 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
287 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
288 all_repos = self.sa.query(Repository)\
288 all_repos = self.sa.query(Repository)\
289 .filter(Repository.user_id == c.user.user_id)\
289 .filter(Repository.user_id == c.user.user_id)\
290 .order_by(func.lower(Repository.repo_name))\
290 .order_by(func.lower(Repository.repo_name))\
291 .all()
291 .all()
292 c.user_repos = ScmModel().get_repos(all_repos)
292 c.user_repos = ScmModel().get_repos(all_repos)
293
293
294 return htmlfill.render(
294 return htmlfill.render(
295 render('admin/users/user_edit_my_account.html'),
295 render('admin/users/user_edit_my_account.html'),
296 defaults=errors.value,
296 defaults=errors.value,
297 errors=errors.error_dict or {},
297 errors=errors.error_dict or {},
298 prefix_error=False,
298 prefix_error=False,
299 encoding="UTF-8")
299 encoding="UTF-8")
300 except Exception:
300 except Exception:
301 log.error(traceback.format_exc())
301 log.error(traceback.format_exc())
302 h.flash(_('error occured during update of user %s') \
302 h.flash(_('error occured during update of user %s') \
303 % form_result.get('username'), category='error')
303 % form_result.get('username'), category='error')
304
304
305 return redirect(url('my_account'))
305 return redirect(url('my_account'))
306
306
307 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
307 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
308 def create_repository(self):
308 def create_repository(self):
309 """GET /_admin/create_repository: Form to create a new item"""
309 """GET /_admin/create_repository: Form to create a new item"""
310 new_repo = request.GET.get('repo', '')
310 new_repo = request.GET.get('repo', '')
311 c.new_repo = h.repo_name_slug(new_repo)
311 c.new_repo = h.repo_name_slug(new_repo)
312
312
313 return render('admin/repos/repo_add_create_repository.html')
313 return render('admin/repos/repo_add_create_repository.html')
314
314
315 def get_hg_ui_settings(self):
316 ret = self.sa.query(RhodeCodeUi).all()
317
318 if not ret:
319 raise Exception('Could not get application ui settings !')
320 settings = {}
321 for each in ret:
322 k = each.ui_key
323 v = each.ui_value
324 if k == '/':
325 k = 'root_path'
326
327 if k.find('.') != -1:
328 k = k.replace('.', '_')
329
330 if each.ui_section == 'hooks':
331 v = each.ui_active
332
333 settings[each.ui_section + '_' + k] = v
334
335 return settings
@@ -1,209 +1,221 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 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 def is_git(environ):
76 """
77 Returns True if request's target is git server. ``HTTP_USER_AGENT`` would
78 then have git client version given.
79
80 :param environ:
81 """
82 http_user_agent = environ.get('HTTP_USER_AGENT')
83 if http_user_agent and http_user_agent.startswith('git'):
84 return True
85 return False
86
75 class SimpleGit(object):
87 class SimpleGit(object):
76
88
77 def __init__(self, application, config):
89 def __init__(self, application, config):
78 self.application = application
90 self.application = application
79 self.config = config
91 self.config = config
80 #authenticate this git request using
92 #authenticate this git request using
81 self.authenticate = AuthBasicAuthenticator('', authfunc)
93 self.authenticate = AuthBasicAuthenticator('', authfunc)
82 self.ipaddr = '0.0.0.0'
94 self.ipaddr = '0.0.0.0'
83 self.repository = None
95 self.repository = None
84 self.username = None
96 self.username = None
85 self.action = None
97 self.action = None
86
98
87 def __call__(self, environ, start_response):
99 def __call__(self, environ, start_response):
88 if not is_git(environ):
100 if not is_git(environ):
89 return self.application(environ, start_response)
101 return self.application(environ, start_response)
90
102
91 proxy_key = 'HTTP_X_REAL_IP'
103 proxy_key = 'HTTP_X_REAL_IP'
92 def_key = 'REMOTE_ADDR'
104 def_key = 'REMOTE_ADDR'
93 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
105 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
94
106
95 #===================================================================
107 #===================================================================
96 # AUTHENTICATE THIS GIT REQUEST
108 # AUTHENTICATE THIS GIT REQUEST
97 #===================================================================
109 #===================================================================
98 username = REMOTE_USER(environ)
110 username = REMOTE_USER(environ)
99 if not username:
111 if not username:
100 self.authenticate.realm = self.config['rhodecode_realm']
112 self.authenticate.realm = self.config['rhodecode_realm']
101 result = self.authenticate(environ)
113 result = self.authenticate(environ)
102 if isinstance(result, str):
114 if isinstance(result, str):
103 AUTH_TYPE.update(environ, 'basic')
115 AUTH_TYPE.update(environ, 'basic')
104 REMOTE_USER.update(environ, result)
116 REMOTE_USER.update(environ, result)
105 else:
117 else:
106 return result.wsgi_application(environ, start_response)
118 return result.wsgi_application(environ, start_response)
107
119
108 #=======================================================================
120 #=======================================================================
109 # GET REPOSITORY
121 # GET REPOSITORY
110 #=======================================================================
122 #=======================================================================
111 try:
123 try:
112 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
124 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
113 if repo_name.endswith('/'):
125 if repo_name.endswith('/'):
114 repo_name = repo_name.rstrip('/')
126 repo_name = repo_name.rstrip('/')
115 self.repository = repo_name
127 self.repository = repo_name
116 except:
128 except:
117 log.error(traceback.format_exc())
129 log.error(traceback.format_exc())
118 return HTTPInternalServerError()(environ, start_response)
130 return HTTPInternalServerError()(environ, start_response)
119
131
120 #===================================================================
132 #===================================================================
121 # CHECK PERMISSIONS FOR THIS REQUEST
133 # CHECK PERMISSIONS FOR THIS REQUEST
122 #===================================================================
134 #===================================================================
123 self.action = self.__get_action(environ)
135 self.action = self.__get_action(environ)
124 if self.action:
136 if self.action:
125 username = self.__get_environ_user(environ)
137 username = self.__get_environ_user(environ)
126 try:
138 try:
127 user = self.__get_user(username)
139 user = self.__get_user(username)
128 self.username = user.username
140 self.username = user.username
129 except:
141 except:
130 log.error(traceback.format_exc())
142 log.error(traceback.format_exc())
131 return HTTPInternalServerError()(environ, start_response)
143 return HTTPInternalServerError()(environ, start_response)
132
144
133 #check permissions for this repository
145 #check permissions for this repository
134 if self.action == 'push':
146 if self.action == 'push':
135 if not HasPermissionAnyMiddleware('repository.write',
147 if not HasPermissionAnyMiddleware('repository.write',
136 'repository.admin')\
148 'repository.admin')\
137 (user, repo_name):
149 (user, repo_name):
138 return HTTPForbidden()(environ, start_response)
150 return HTTPForbidden()(environ, start_response)
139
151
140 else:
152 else:
141 #any other action need at least read permission
153 #any other action need at least read permission
142 if not HasPermissionAnyMiddleware('repository.read',
154 if not HasPermissionAnyMiddleware('repository.read',
143 'repository.write',
155 'repository.write',
144 'repository.admin')\
156 'repository.admin')\
145 (user, repo_name):
157 (user, repo_name):
146 return HTTPForbidden()(environ, start_response)
158 return HTTPForbidden()(environ, start_response)
147
159
148 self.extras = {'ip':self.ipaddr,
160 self.extras = {'ip':self.ipaddr,
149 'username':self.username,
161 'username':self.username,
150 'action':self.action,
162 'action':self.action,
151 'repository':self.repository}
163 'repository':self.repository}
152
164
153 #===================================================================
165 #===================================================================
154 # GIT REQUEST HANDLING
166 # GIT REQUEST HANDLING
155 #===================================================================
167 #===================================================================
156 self.basepath = self.config['base_path']
168 self.basepath = self.config['base_path']
157 self.repo_path = os.path.join(self.basepath, self.repo_name)
169 self.repo_path = os.path.join(self.basepath, self.repo_name)
158 #quick check if that dir exists...
170 #quick check if that dir exists...
159 if check_repo_fast(self.repo_name, self.basepath):
171 if check_repo_fast(self.repo_name, self.basepath):
160 return HTTPNotFound()(environ, start_response)
172 return HTTPNotFound()(environ, start_response)
161 try:
173 try:
162 app = self.__make_app()
174 app = self.__make_app()
163 except:
175 except:
164 log.error(traceback.format_exc())
176 log.error(traceback.format_exc())
165 return HTTPInternalServerError()(environ, start_response)
177 return HTTPInternalServerError()(environ, start_response)
166
178
167 #invalidate cache on push
179 #invalidate cache on push
168 if self.action == 'push':
180 if self.action == 'push':
169 self.__invalidate_cache(self.repo_name)
181 self.__invalidate_cache(self.repo_name)
170 messages = []
182 messages = []
171 messages.append('thank you for using rhodecode')
183 messages.append('thank you for using rhodecode')
172 return app(environ, start_response)
184 return app(environ, start_response)
173 else:
185 else:
174 return app(environ, start_response)
186 return app(environ, start_response)
175
187
176
188
177 def __make_app(self):
189 def __make_app(self):
178 backend = dulserver.DictBackend({'/' + self.repo_name: Repo(self.repo_path)})
190 backend = dulserver.DictBackend({'/' + self.repo_name: Repo(self.repo_path)})
179 gitserve = HTTPGitApplication(backend)
191 gitserve = HTTPGitApplication(backend)
180
192
181 return gitserve
193 return gitserve
182
194
183 def __get_environ_user(self, environ):
195 def __get_environ_user(self, environ):
184 return environ.get('REMOTE_USER')
196 return environ.get('REMOTE_USER')
185
197
186 def __get_user(self, username):
198 def __get_user(self, username):
187 return UserModel().get_by_username(username, cache=True)
199 return UserModel().get_by_username(username, cache=True)
188
200
189 def __get_action(self, environ):
201 def __get_action(self, environ):
190 """
202 """
191 Maps git request commands into a pull or push command.
203 Maps git request commands into a pull or push command.
192 :param environ:
204 :param environ:
193 """
205 """
194 service = environ['QUERY_STRING'].split('=')
206 service = environ['QUERY_STRING'].split('=')
195 if len(service) > 1:
207 if len(service) > 1:
196 service_cmd = service[1]
208 service_cmd = service[1]
197 mapping = {'git-receive-pack': 'push',
209 mapping = {'git-receive-pack': 'push',
198 'git-upload-pack': 'pull',
210 'git-upload-pack': 'pull',
199 }
211 }
200
212
201 return mapping.get(service_cmd, service_cmd if service_cmd else 'other')
213 return mapping.get(service_cmd, service_cmd if service_cmd else 'other')
202 else:
214 else:
203 return 'other'
215 return 'other'
204
216
205 def __invalidate_cache(self, repo_name):
217 def __invalidate_cache(self, repo_name):
206 """we know that some change was made to repositories and we should
218 """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
219 invalidate the cache to see the changes right away but only for
208 push requests"""
220 push requests"""
209 invalidate_cache('get_repo_cached_%s' % repo_name)
221 invalidate_cache('get_repo_cached_%s' % repo_name)
@@ -1,220 +1,229 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
28 from mercurial.error import RepoError
27 from mercurial.error import RepoError
29 from mercurial.hgweb import hgweb
28 from mercurial.hgweb import hgweb
30 from mercurial.hgweb.request import wsgiapplication
29 from mercurial.hgweb.request import wsgiapplication
31 from paste.auth.basic import AuthBasicAuthenticator
30 from paste.auth.basic import AuthBasicAuthenticator
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
31 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
33 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
32 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
34 from rhodecode.lib.utils import is_mercurial, make_ui, invalidate_cache, \
33 from rhodecode.lib.utils import make_ui, invalidate_cache, \
35 check_repo_fast, ui_sections
34 check_repo_fast, ui_sections
36 from rhodecode.model.user import UserModel
35 from rhodecode.model.user import UserModel
37 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
36 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
38 import logging
37 import logging
39 import os
38 import os
40 import traceback
39 import traceback
41
40
42 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
43
42
43 def is_mercurial(environ):
44 """
45 Returns True if request's target is mercurial server - header
46 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
47 """
48 http_accept = environ.get('HTTP_ACCEPT')
49 if http_accept and http_accept.startswith('application/mercurial'):
50 return True
51 return False
52
44 class SimpleHg(object):
53 class SimpleHg(object):
45
54
46 def __init__(self, application, config):
55 def __init__(self, application, config):
47 self.application = application
56 self.application = application
48 self.config = config
57 self.config = config
49 #authenticate this mercurial request using authfunc
58 #authenticate this mercurial request using authfunc
50 self.authenticate = AuthBasicAuthenticator('', authfunc)
59 self.authenticate = AuthBasicAuthenticator('', authfunc)
51 self.ipaddr = '0.0.0.0'
60 self.ipaddr = '0.0.0.0'
52 self.repository = None
61 self.repository = None
53 self.username = None
62 self.username = None
54 self.action = None
63 self.action = None
55
64
56 def __call__(self, environ, start_response):
65 def __call__(self, environ, start_response):
57 if not is_mercurial(environ):
66 if not is_mercurial(environ):
58 return self.application(environ, start_response)
67 return self.application(environ, start_response)
59
68
60 proxy_key = 'HTTP_X_REAL_IP'
69 proxy_key = 'HTTP_X_REAL_IP'
61 def_key = 'REMOTE_ADDR'
70 def_key = 'REMOTE_ADDR'
62 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
71 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
63
72
64 #===================================================================
73 #===================================================================
65 # AUTHENTICATE THIS MERCURIAL REQUEST
74 # AUTHENTICATE THIS MERCURIAL REQUEST
66 #===================================================================
75 #===================================================================
67 username = REMOTE_USER(environ)
76 username = REMOTE_USER(environ)
68
77
69 if not username:
78 if not username:
70 self.authenticate.realm = self.config['rhodecode_realm']
79 self.authenticate.realm = self.config['rhodecode_realm']
71 result = self.authenticate(environ)
80 result = self.authenticate(environ)
72 if isinstance(result, str):
81 if isinstance(result, str):
73 AUTH_TYPE.update(environ, 'basic')
82 AUTH_TYPE.update(environ, 'basic')
74 REMOTE_USER.update(environ, result)
83 REMOTE_USER.update(environ, result)
75 else:
84 else:
76 return result.wsgi_application(environ, start_response)
85 return result.wsgi_application(environ, start_response)
77
86
78 #=======================================================================
87 #=======================================================================
79 # GET REPOSITORY
88 # GET REPOSITORY
80 #=======================================================================
89 #=======================================================================
81 try:
90 try:
82 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
91 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
83 if repo_name.endswith('/'):
92 if repo_name.endswith('/'):
84 repo_name = repo_name.rstrip('/')
93 repo_name = repo_name.rstrip('/')
85 self.repository = repo_name
94 self.repository = repo_name
86 except:
95 except:
87 log.error(traceback.format_exc())
96 log.error(traceback.format_exc())
88 return HTTPInternalServerError()(environ, start_response)
97 return HTTPInternalServerError()(environ, start_response)
89
98
90 #===================================================================
99 #===================================================================
91 # CHECK PERMISSIONS FOR THIS REQUEST
100 # CHECK PERMISSIONS FOR THIS REQUEST
92 #===================================================================
101 #===================================================================
93 self.action = self.__get_action(environ)
102 self.action = self.__get_action(environ)
94 if self.action:
103 if self.action:
95 username = self.__get_environ_user(environ)
104 username = self.__get_environ_user(environ)
96 try:
105 try:
97 user = self.__get_user(username)
106 user = self.__get_user(username)
98 self.username = user.username
107 self.username = user.username
99 except:
108 except:
100 log.error(traceback.format_exc())
109 log.error(traceback.format_exc())
101 return HTTPInternalServerError()(environ, start_response)
110 return HTTPInternalServerError()(environ, start_response)
102
111
103 #check permissions for this repository
112 #check permissions for this repository
104 if self.action == 'push':
113 if self.action == 'push':
105 if not HasPermissionAnyMiddleware('repository.write',
114 if not HasPermissionAnyMiddleware('repository.write',
106 'repository.admin')\
115 'repository.admin')\
107 (user, repo_name):
116 (user, repo_name):
108 return HTTPForbidden()(environ, start_response)
117 return HTTPForbidden()(environ, start_response)
109
118
110 else:
119 else:
111 #any other action need at least read permission
120 #any other action need at least read permission
112 if not HasPermissionAnyMiddleware('repository.read',
121 if not HasPermissionAnyMiddleware('repository.read',
113 'repository.write',
122 'repository.write',
114 'repository.admin')\
123 'repository.admin')\
115 (user, repo_name):
124 (user, repo_name):
116 return HTTPForbidden()(environ, start_response)
125 return HTTPForbidden()(environ, start_response)
117
126
118 self.extras = {'ip':self.ipaddr,
127 self.extras = {'ip':self.ipaddr,
119 'username':self.username,
128 'username':self.username,
120 'action':self.action,
129 'action':self.action,
121 'repository':self.repository}
130 'repository':self.repository}
122
131
123 #===================================================================
132 #===================================================================
124 # MERCURIAL REQUEST HANDLING
133 # MERCURIAL REQUEST HANDLING
125 #===================================================================
134 #===================================================================
126 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
135 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
127 self.baseui = make_ui('db')
136 self.baseui = make_ui('db')
128 self.basepath = self.config['base_path']
137 self.basepath = self.config['base_path']
129 self.repo_path = os.path.join(self.basepath, repo_name)
138 self.repo_path = os.path.join(self.basepath, repo_name)
130
139
131 #quick check if that dir exists...
140 #quick check if that dir exists...
132 if check_repo_fast(repo_name, self.basepath):
141 if check_repo_fast(repo_name, self.basepath):
133 return HTTPNotFound()(environ, start_response)
142 return HTTPNotFound()(environ, start_response)
134 try:
143 try:
135 app = wsgiapplication(self.__make_app)
144 app = wsgiapplication(self.__make_app)
136 except RepoError, e:
145 except RepoError, e:
137 if str(e).find('not found') != -1:
146 if str(e).find('not found') != -1:
138 return HTTPNotFound()(environ, start_response)
147 return HTTPNotFound()(environ, start_response)
139 except Exception:
148 except Exception:
140 log.error(traceback.format_exc())
149 log.error(traceback.format_exc())
141 return HTTPInternalServerError()(environ, start_response)
150 return HTTPInternalServerError()(environ, start_response)
142
151
143 #invalidate cache on push
152 #invalidate cache on push
144 if self.action == 'push':
153 if self.action == 'push':
145 self.__invalidate_cache(repo_name)
154 self.__invalidate_cache(repo_name)
146
155
147 return app(environ, start_response)
156 return app(environ, start_response)
148
157
149
158
150 def __make_app(self):
159 def __make_app(self):
151 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
160 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
152 return self.__load_web_settings(hgserve, self.extras)
161 return self.__load_web_settings(hgserve, self.extras)
153
162
154 def __get_environ_user(self, environ):
163 def __get_environ_user(self, environ):
155 return environ.get('REMOTE_USER')
164 return environ.get('REMOTE_USER')
156
165
157 def __get_user(self, username):
166 def __get_user(self, username):
158 return UserModel().get_by_username(username, cache=True)
167 return UserModel().get_by_username(username, cache=True)
159
168
160 def __get_action(self, environ):
169 def __get_action(self, environ):
161 """
170 """
162 Maps mercurial request commands into a clone,pull or push command.
171 Maps mercurial request commands into a clone,pull or push command.
163 This should always return a valid command string
172 This should always return a valid command string
164 :param environ:
173 :param environ:
165 """
174 """
166 mapping = {'changegroup': 'pull',
175 mapping = {'changegroup': 'pull',
167 'changegroupsubset': 'pull',
176 'changegroupsubset': 'pull',
168 'stream_out': 'pull',
177 'stream_out': 'pull',
169 'listkeys': 'pull',
178 'listkeys': 'pull',
170 'unbundle': 'push',
179 'unbundle': 'push',
171 'pushkey': 'push', }
180 'pushkey': 'push', }
172 for qry in environ['QUERY_STRING'].split('&'):
181 for qry in environ['QUERY_STRING'].split('&'):
173 if qry.startswith('cmd'):
182 if qry.startswith('cmd'):
174 cmd = qry.split('=')[-1]
183 cmd = qry.split('=')[-1]
175 if mapping.has_key(cmd):
184 if mapping.has_key(cmd):
176 return mapping[cmd]
185 return mapping[cmd]
177 else:
186 else:
178 return cmd
187 return cmd
179
188
180 def __invalidate_cache(self, repo_name):
189 def __invalidate_cache(self, repo_name):
181 """we know that some change was made to repositories and we should
190 """we know that some change was made to repositories and we should
182 invalidate the cache to see the changes right away but only for
191 invalidate the cache to see the changes right away but only for
183 push requests"""
192 push requests"""
184 invalidate_cache('get_repo_cached_%s' % repo_name)
193 invalidate_cache('get_repo_cached_%s' % repo_name)
185
194
186
195
187 def __load_web_settings(self, hgserve, extras={}):
196 def __load_web_settings(self, hgserve, extras={}):
188 #set the global ui for hgserve instance passed
197 #set the global ui for hgserve instance passed
189 hgserve.repo.ui = self.baseui
198 hgserve.repo.ui = self.baseui
190
199
191 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
200 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
192
201
193 #inject some additional parameters that will be available in ui
202 #inject some additional parameters that will be available in ui
194 #for hooks
203 #for hooks
195 for k, v in extras.items():
204 for k, v in extras.items():
196 hgserve.repo.ui.setconfig('rhodecode_extras', k, v)
205 hgserve.repo.ui.setconfig('rhodecode_extras', k, v)
197
206
198 repoui = make_ui('file', hgrc, False)
207 repoui = make_ui('file', hgrc, False)
199
208
200 if repoui:
209 if repoui:
201 #overwrite our ui instance with the section from hgrc file
210 #overwrite our ui instance with the section from hgrc file
202 for section in ui_sections:
211 for section in ui_sections:
203 for k, v in repoui.configitems(section):
212 for k, v in repoui.configitems(section):
204 hgserve.repo.ui.setconfig(section, k, v)
213 hgserve.repo.ui.setconfig(section, k, v)
205
214
206 return hgserve
215 return hgserve
207
216
208
217
209
218
210
219
211
220
212
221
213
222
214
223
215
224
216
225
217
226
218
227
219
228
220
229
@@ -1,585 +1,508 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, UserLog
31 UserLog
32 from rhodecode.model.repo import RepoModel
31 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
33
34 from vcs.backends.base import BaseChangeset
34 from vcs.backends.base import BaseChangeset
35 from paste.script import command
35 from paste.script import command
36 import ConfigParser
36 import ConfigParser
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):
50 """
51 Returns True if request's target is mercurial server - header
52 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
53 """
54 http_accept = environ.get('HTTP_ACCEPT')
55 if http_accept and http_accept.startswith('application/mercurial'):
56 return True
57 return False
58
59 def is_git(environ):
60 """
61 Returns True if request's target is git server. ``HTTP_USER_AGENT`` would
62 then have git client version given.
63
64 :param environ:
65 """
66 http_user_agent = environ.get('HTTP_USER_AGENT')
67 if http_user_agent and http_user_agent.startswith('git'):
68 return True
69 return False
70
71 def action_logger(user, action, repo, ipaddr='', sa=None):
49 def action_logger(user, action, repo, ipaddr='', sa=None):
72 """
50 """
73 Action logger for various actions made by users
51 Action logger for various actions made by users
74
52
75 :param user: user that made this action, can be a unique username string or
53 :param user: user that made this action, can be a unique username string or
76 object containing user_id attribute
54 object containing user_id attribute
77 :param action: action to log, should be on of predefined unique actions for
55 :param action: action to log, should be on of predefined unique actions for
78 easy translations
56 easy translations
79 :param repo: string name of repository or object containing repo_id,
57 :param repo: string name of repository or object containing repo_id,
80 that action was made on
58 that action was made on
81 :param ipaddr: optional ip address from what the action was made
59 :param ipaddr: optional ip address from what the action was made
82 :param sa: optional sqlalchemy session
60 :param sa: optional sqlalchemy session
83
61
84 """
62 """
85
63
86 if not sa:
64 if not sa:
87 sa = meta.Session()
65 sa = meta.Session()
88
66
89 try:
67 try:
90 um = UserModel()
68 um = UserModel()
91 if hasattr(user, 'user_id'):
69 if hasattr(user, 'user_id'):
92 user_obj = user
70 user_obj = user
93 elif isinstance(user, basestring):
71 elif isinstance(user, basestring):
94 user_obj = um.get_by_username(user, cache=False)
72 user_obj = um.get_by_username(user, cache=False)
95 else:
73 else:
96 raise Exception('You have to provide user object or username')
74 raise Exception('You have to provide user object or username')
97
75
98
76
99 rm = RepoModel()
77 rm = RepoModel()
100 if hasattr(repo, 'repo_id'):
78 if hasattr(repo, 'repo_id'):
101 repo_obj = rm.get(repo.repo_id, cache=False)
79 repo_obj = rm.get(repo.repo_id, cache=False)
102 repo_name = repo_obj.repo_name
80 repo_name = repo_obj.repo_name
103 elif isinstance(repo, basestring):
81 elif isinstance(repo, basestring):
104 repo_name = repo.lstrip('/')
82 repo_name = repo.lstrip('/')
105 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
83 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
106 else:
84 else:
107 raise Exception('You have to provide repository to action logger')
85 raise Exception('You have to provide repository to action logger')
108
86
109
87
110 user_log = UserLog()
88 user_log = UserLog()
111 user_log.user_id = user_obj.user_id
89 user_log.user_id = user_obj.user_id
112 user_log.action = action
90 user_log.action = action
113
91
114 user_log.repository_id = repo_obj.repo_id
92 user_log.repository_id = repo_obj.repo_id
115 user_log.repository_name = repo_name
93 user_log.repository_name = repo_name
116
94
117 user_log.action_date = datetime.datetime.now()
95 user_log.action_date = datetime.datetime.now()
118 user_log.user_ip = ipaddr
96 user_log.user_ip = ipaddr
119 sa.add(user_log)
97 sa.add(user_log)
120 sa.commit()
98 sa.commit()
121
99
122 log.info('Adding user %s, action %s on %s',
100 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
123 user_obj.username, action, repo)
124 except:
101 except:
125 log.error(traceback.format_exc())
102 log.error(traceback.format_exc())
126 sa.rollback()
103 sa.rollback()
127
104
128 def get_repos(path, recursive=False, initial=False):
105 def get_repos(path, recursive=False, initial=False):
129 """
106 """
130 Scans given path for repos and return (name,(type,path)) tuple
107 Scans given path for repos and return (name,(type,path)) tuple
131 :param prefix:
108 :param prefix:
132 :param path:
109 :param path:
133 :param recursive:
110 :param recursive:
134 :param initial:
111 :param initial:
135 """
112 """
136 from vcs.utils.helpers import get_scm
113 from vcs.utils.helpers import get_scm
137 from vcs.exceptions import VCSError
114 from vcs.exceptions import VCSError
138
115
139 try:
116 try:
140 scm = get_scm(path)
117 scm = get_scm(path)
141 except:
118 except:
142 pass
119 pass
143 else:
120 else:
144 raise Exception('The given path %s should not be a repository got %s',
121 raise Exception('The given path %s should not be a repository got %s',
145 path, scm)
122 path, scm)
146
123
147 for dirpath in os.listdir(path):
124 for dirpath in os.listdir(path):
148 try:
125 try:
149 yield dirpath, get_scm(os.path.join(path, dirpath))
126 yield dirpath, get_scm(os.path.join(path, dirpath))
150 except VCSError:
127 except VCSError:
151 pass
128 pass
152
129
153 if __name__ == '__main__':
154 get_repos('', '/home/marcink/workspace-python')
155
156
157 def check_repo_fast(repo_name, base_path):
130 def check_repo_fast(repo_name, base_path):
158 if os.path.isdir(os.path.join(base_path, repo_name)):return False
131 if os.path.isdir(os.path.join(base_path, repo_name)):return False
159 return True
132 return True
160
133
161 def check_repo(repo_name, base_path, verify=True):
134 def check_repo(repo_name, base_path, verify=True):
162
135
163 repo_path = os.path.join(base_path, repo_name)
136 repo_path = os.path.join(base_path, repo_name)
164
137
165 try:
138 try:
166 if not check_repo_fast(repo_name, base_path):
139 if not check_repo_fast(repo_name, base_path):
167 return False
140 return False
168 r = hg.repository(ui.ui(), repo_path)
141 r = hg.repository(ui.ui(), repo_path)
169 if verify:
142 if verify:
170 hg.verify(r)
143 hg.verify(r)
171 #here we hnow that repo exists it was verified
144 #here we hnow that repo exists it was verified
172 log.info('%s repo is already created', repo_name)
145 log.info('%s repo is already created', repo_name)
173 return False
146 return False
174 except RepoError:
147 except RepoError:
175 #it means that there is no valid repo there...
148 #it means that there is no valid repo there...
176 log.info('%s repo is free for creation', repo_name)
149 log.info('%s repo is free for creation', repo_name)
177 return True
150 return True
178
151
179 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
152 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
180 while True:
153 while True:
181 ok = raw_input(prompt)
154 ok = raw_input(prompt)
182 if ok in ('y', 'ye', 'yes'): return True
155 if ok in ('y', 'ye', 'yes'): return True
183 if ok in ('n', 'no', 'nop', 'nope'): return False
156 if ok in ('n', 'no', 'nop', 'nope'): return False
184 retries = retries - 1
157 retries = retries - 1
185 if retries < 0: raise IOError
158 if retries < 0: raise IOError
186 print complaint
159 print complaint
187
160
188 def get_hg_ui_cached():
189 try:
190 sa = meta.Session
191 ret = sa.query(RhodeCodeUi)\
192 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
193 .all()
194 except:
195 pass
196 finally:
197 meta.Session.remove()
198 return ret
199
200
201 def get_hg_settings():
202 try:
203 sa = meta.Session()
204 ret = sa.query(RhodeCodeSettings)\
205 .options(FromCache("sql_cache_short", "get_hg_settings"))\
206 .all()
207 except:
208 pass
209 finally:
210 meta.Session.remove()
211
212 if not ret:
213 raise Exception('Could not get application settings !')
214 settings = {}
215 for each in ret:
216 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
217
218 return settings
219
220 def get_hg_ui_settings():
221 try:
222 sa = meta.Session()
223 ret = sa.query(RhodeCodeUi).all()
224 except:
225 pass
226 finally:
227 meta.Session.remove()
228
229 if not ret:
230 raise Exception('Could not get application ui settings !')
231 settings = {}
232 for each in ret:
233 k = each.ui_key
234 v = each.ui_value
235 if k == '/':
236 k = 'root_path'
237
238 if k.find('.') != -1:
239 k = k.replace('.', '_')
240
241 if each.ui_section == 'hooks':
242 v = each.ui_active
243
244 settings[each.ui_section + '_' + k] = v
245
246 return settings
247
248 #propagated from mercurial documentation
161 #propagated from mercurial documentation
249 ui_sections = ['alias', 'auth',
162 ui_sections = ['alias', 'auth',
250 'decode/encode', 'defaults',
163 'decode/encode', 'defaults',
251 'diff', 'email',
164 'diff', 'email',
252 'extensions', 'format',
165 'extensions', 'format',
253 'merge-patterns', 'merge-tools',
166 'merge-patterns', 'merge-tools',
254 'hooks', 'http_proxy',
167 'hooks', 'http_proxy',
255 'smtp', 'patch',
168 'smtp', 'patch',
256 'paths', 'profiling',
169 'paths', 'profiling',
257 'server', 'trusted',
170 'server', 'trusted',
258 'ui', 'web', ]
171 'ui', 'web', ]
259
172
260 def make_ui(read_from='file', path=None, checkpaths=True):
173 def make_ui(read_from='file', path=None, checkpaths=True):
261 """
174 """
262 A function that will read python rc files or database
175 A function that will read python rc files or database
263 and make an mercurial ui object from read options
176 and make an mercurial ui object from read options
264
177
265 :param path: path to mercurial config file
178 :param path: path to mercurial config file
266 :param checkpaths: check the path
179 :param checkpaths: check the path
267 :param read_from: read from 'file' or 'db'
180 :param read_from: read from 'file' or 'db'
268 """
181 """
269
182
270 baseui = ui.ui()
183 baseui = ui.ui()
271
184
272 #clean the baseui object
185 #clean the baseui object
273 baseui._ocfg = config.config()
186 baseui._ocfg = config.config()
274 baseui._ucfg = config.config()
187 baseui._ucfg = config.config()
275 baseui._tcfg = config.config()
188 baseui._tcfg = config.config()
276
189
277 if read_from == 'file':
190 if read_from == 'file':
278 if not os.path.isfile(path):
191 if not os.path.isfile(path):
279 log.warning('Unable to read config file %s' % path)
192 log.warning('Unable to read config file %s' % path)
280 return False
193 return False
281 log.debug('reading hgrc from %s', path)
194 log.debug('reading hgrc from %s', path)
282 cfg = config.config()
195 cfg = config.config()
283 cfg.read(path)
196 cfg.read(path)
284 for section in ui_sections:
197 for section in ui_sections:
285 for k, v in cfg.items(section):
198 for k, v in cfg.items(section):
286 log.debug('settings ui from file[%s]%s:%s', section, k, v)
199 log.debug('settings ui from file[%s]%s:%s', section, k, v)
287 baseui.setconfig(section, k, v)
200 baseui.setconfig(section, k, v)
288
201
289
202
290 elif read_from == 'db':
203 elif read_from == 'db':
291 hg_ui = get_hg_ui_cached()
204 sa = meta.Session()
205 ret = sa.query(RhodeCodeUi)\
206 .options(FromCache("sql_cache_short",
207 "get_hg_ui_settings")).all()
208 meta.Session.remove()
209 hg_ui = ret
292 for ui_ in hg_ui:
210 for ui_ in hg_ui:
293 if ui_.ui_active:
211 if ui_.ui_active:
294 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
212 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
295 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
213 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
296 return baseui
214 return baseui
297
215
298
216
299 def set_rhodecode_config(config):
217 def set_rhodecode_config(config):
300 hgsettings = get_hg_settings()
218 """
219 Updates pylons config with new settings from database
220 :param config:
221 """
222 from rhodecode.model.settings import SettingsModel
223 hgsettings = SettingsModel().get_app_settings()
301
224
302 for k, v in hgsettings.items():
225 for k, v in hgsettings.items():
303 config[k] = v
226 config[k] = v
304
227
305 def invalidate_cache(cache_key, *args):
228 def invalidate_cache(cache_key, *args):
306 """
229 """
307 Puts cache invalidation task into db for
230 Puts cache invalidation task into db for
308 further global cache invalidation
231 further global cache invalidation
309 """
232 """
310 from rhodecode.model.scm import ScmModel
233 from rhodecode.model.scm import ScmModel
311
234
312 if cache_key.startswith('get_repo_cached_'):
235 if cache_key.startswith('get_repo_cached_'):
313 name = cache_key.split('get_repo_cached_')[-1]
236 name = cache_key.split('get_repo_cached_')[-1]
314 ScmModel().mark_for_invalidation(name)
237 ScmModel().mark_for_invalidation(name)
315
238
316 class EmptyChangeset(BaseChangeset):
239 class EmptyChangeset(BaseChangeset):
317 """
240 """
318 An dummy empty changeset. It's possible to pass hash when creating
241 An dummy empty changeset. It's possible to pass hash when creating
319 an EmptyChangeset
242 an EmptyChangeset
320 """
243 """
321
244
322 def __init__(self, cs='0' * 40):
245 def __init__(self, cs='0' * 40):
323 self._empty_cs = cs
246 self._empty_cs = cs
324 self.revision = -1
247 self.revision = -1
325 self.message = ''
248 self.message = ''
326 self.author = ''
249 self.author = ''
327 self.date = ''
250 self.date = ''
328
251
329 @LazyProperty
252 @LazyProperty
330 def raw_id(self):
253 def raw_id(self):
331 """
254 """
332 Returns raw string identifying this changeset, useful for web
255 Returns raw string identifying this changeset, useful for web
333 representation.
256 representation.
334 """
257 """
335 return self._empty_cs
258 return self._empty_cs
336
259
337 @LazyProperty
260 @LazyProperty
338 def short_id(self):
261 def short_id(self):
339 return self.raw_id[:12]
262 return self.raw_id[:12]
340
263
341 def get_file_changeset(self, path):
264 def get_file_changeset(self, path):
342 return self
265 return self
343
266
344 def get_file_content(self, path):
267 def get_file_content(self, path):
345 return u''
268 return u''
346
269
347 def get_file_size(self, path):
270 def get_file_size(self, path):
348 return 0
271 return 0
349
272
350 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
273 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
351 """
274 """
352 maps all found repositories into db
275 maps all found repositories into db
353 """
276 """
354
277
355 sa = meta.Session()
278 sa = meta.Session()
356 rm = RepoModel()
279 rm = RepoModel()
357 user = sa.query(User).filter(User.admin == True).first()
280 user = sa.query(User).filter(User.admin == True).first()
358
281
359 for name, repo in initial_repo_list.items():
282 for name, repo in initial_repo_list.items():
360 if not rm.get_by_repo_name(name, cache=False):
283 if not rm.get_by_repo_name(name, cache=False):
361 log.info('repository %s not found creating default', name)
284 log.info('repository %s not found creating default', name)
362
285
363 form_data = {
286 form_data = {
364 'repo_name':name,
287 'repo_name':name,
365 'repo_type':repo.alias,
288 'repo_type':repo.alias,
366 'description':repo.description \
289 'description':repo.description \
367 if repo.description != 'unknown' else \
290 if repo.description != 'unknown' else \
368 '%s repository' % name,
291 '%s repository' % name,
369 'private':False
292 'private':False
370 }
293 }
371 rm.create(form_data, user, just_db=True)
294 rm.create(form_data, user, just_db=True)
372
295
373 if remove_obsolete:
296 if remove_obsolete:
374 #remove from database those repositories that are not in the filesystem
297 #remove from database those repositories that are not in the filesystem
375 for repo in sa.query(Repository).all():
298 for repo in sa.query(Repository).all():
376 if repo.repo_name not in initial_repo_list.keys():
299 if repo.repo_name not in initial_repo_list.keys():
377 sa.delete(repo)
300 sa.delete(repo)
378 sa.commit()
301 sa.commit()
379
302
380 class OrderedDict(dict, DictMixin):
303 class OrderedDict(dict, DictMixin):
381
304
382 def __init__(self, *args, **kwds):
305 def __init__(self, *args, **kwds):
383 if len(args) > 1:
306 if len(args) > 1:
384 raise TypeError('expected at most 1 arguments, got %d' % len(args))
307 raise TypeError('expected at most 1 arguments, got %d' % len(args))
385 try:
308 try:
386 self.__end
309 self.__end
387 except AttributeError:
310 except AttributeError:
388 self.clear()
311 self.clear()
389 self.update(*args, **kwds)
312 self.update(*args, **kwds)
390
313
391 def clear(self):
314 def clear(self):
392 self.__end = end = []
315 self.__end = end = []
393 end += [None, end, end] # sentinel node for doubly linked list
316 end += [None, end, end] # sentinel node for doubly linked list
394 self.__map = {} # key --> [key, prev, next]
317 self.__map = {} # key --> [key, prev, next]
395 dict.clear(self)
318 dict.clear(self)
396
319
397 def __setitem__(self, key, value):
320 def __setitem__(self, key, value):
398 if key not in self:
321 if key not in self:
399 end = self.__end
322 end = self.__end
400 curr = end[1]
323 curr = end[1]
401 curr[2] = end[1] = self.__map[key] = [key, curr, end]
324 curr[2] = end[1] = self.__map[key] = [key, curr, end]
402 dict.__setitem__(self, key, value)
325 dict.__setitem__(self, key, value)
403
326
404 def __delitem__(self, key):
327 def __delitem__(self, key):
405 dict.__delitem__(self, key)
328 dict.__delitem__(self, key)
406 key, prev, next = self.__map.pop(key)
329 key, prev, next = self.__map.pop(key)
407 prev[2] = next
330 prev[2] = next
408 next[1] = prev
331 next[1] = prev
409
332
410 def __iter__(self):
333 def __iter__(self):
411 end = self.__end
334 end = self.__end
412 curr = end[2]
335 curr = end[2]
413 while curr is not end:
336 while curr is not end:
414 yield curr[0]
337 yield curr[0]
415 curr = curr[2]
338 curr = curr[2]
416
339
417 def __reversed__(self):
340 def __reversed__(self):
418 end = self.__end
341 end = self.__end
419 curr = end[1]
342 curr = end[1]
420 while curr is not end:
343 while curr is not end:
421 yield curr[0]
344 yield curr[0]
422 curr = curr[1]
345 curr = curr[1]
423
346
424 def popitem(self, last=True):
347 def popitem(self, last=True):
425 if not self:
348 if not self:
426 raise KeyError('dictionary is empty')
349 raise KeyError('dictionary is empty')
427 if last:
350 if last:
428 key = reversed(self).next()
351 key = reversed(self).next()
429 else:
352 else:
430 key = iter(self).next()
353 key = iter(self).next()
431 value = self.pop(key)
354 value = self.pop(key)
432 return key, value
355 return key, value
433
356
434 def __reduce__(self):
357 def __reduce__(self):
435 items = [[k, self[k]] for k in self]
358 items = [[k, self[k]] for k in self]
436 tmp = self.__map, self.__end
359 tmp = self.__map, self.__end
437 del self.__map, self.__end
360 del self.__map, self.__end
438 inst_dict = vars(self).copy()
361 inst_dict = vars(self).copy()
439 self.__map, self.__end = tmp
362 self.__map, self.__end = tmp
440 if inst_dict:
363 if inst_dict:
441 return (self.__class__, (items,), inst_dict)
364 return (self.__class__, (items,), inst_dict)
442 return self.__class__, (items,)
365 return self.__class__, (items,)
443
366
444 def keys(self):
367 def keys(self):
445 return list(self)
368 return list(self)
446
369
447 setdefault = DictMixin.setdefault
370 setdefault = DictMixin.setdefault
448 update = DictMixin.update
371 update = DictMixin.update
449 pop = DictMixin.pop
372 pop = DictMixin.pop
450 values = DictMixin.values
373 values = DictMixin.values
451 items = DictMixin.items
374 items = DictMixin.items
452 iterkeys = DictMixin.iterkeys
375 iterkeys = DictMixin.iterkeys
453 itervalues = DictMixin.itervalues
376 itervalues = DictMixin.itervalues
454 iteritems = DictMixin.iteritems
377 iteritems = DictMixin.iteritems
455
378
456 def __repr__(self):
379 def __repr__(self):
457 if not self:
380 if not self:
458 return '%s()' % (self.__class__.__name__,)
381 return '%s()' % (self.__class__.__name__,)
459 return '%s(%r)' % (self.__class__.__name__, self.items())
382 return '%s(%r)' % (self.__class__.__name__, self.items())
460
383
461 def copy(self):
384 def copy(self):
462 return self.__class__(self)
385 return self.__class__(self)
463
386
464 @classmethod
387 @classmethod
465 def fromkeys(cls, iterable, value=None):
388 def fromkeys(cls, iterable, value=None):
466 d = cls()
389 d = cls()
467 for key in iterable:
390 for key in iterable:
468 d[key] = value
391 d[key] = value
469 return d
392 return d
470
393
471 def __eq__(self, other):
394 def __eq__(self, other):
472 if isinstance(other, OrderedDict):
395 if isinstance(other, OrderedDict):
473 return len(self) == len(other) and self.items() == other.items()
396 return len(self) == len(other) and self.items() == other.items()
474 return dict.__eq__(self, other)
397 return dict.__eq__(self, other)
475
398
476 def __ne__(self, other):
399 def __ne__(self, other):
477 return not self == other
400 return not self == other
478
401
479
402
480 #===============================================================================
403 #===============================================================================
481 # TEST FUNCTIONS AND CREATORS
404 # TEST FUNCTIONS AND CREATORS
482 #===============================================================================
405 #===============================================================================
483 def create_test_index(repo_location, full_index):
406 def create_test_index(repo_location, full_index):
484 """Makes default test index
407 """Makes default test index
485 :param repo_location:
408 :param repo_location:
486 :param full_index:
409 :param full_index:
487 """
410 """
488 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
411 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
489 from rhodecode.lib.pidlock import DaemonLock, LockHeld
412 from rhodecode.lib.pidlock import DaemonLock, LockHeld
490 import shutil
413 import shutil
491
414
492 index_location = os.path.join(repo_location, 'index')
415 index_location = os.path.join(repo_location, 'index')
493 if os.path.exists(index_location):
416 if os.path.exists(index_location):
494 shutil.rmtree(index_location)
417 shutil.rmtree(index_location)
495
418
496 try:
419 try:
497 l = DaemonLock()
420 l = DaemonLock()
498 WhooshIndexingDaemon(index_location=index_location,
421 WhooshIndexingDaemon(index_location=index_location,
499 repo_location=repo_location)\
422 repo_location=repo_location)\
500 .run(full_index=full_index)
423 .run(full_index=full_index)
501 l.release()
424 l.release()
502 except LockHeld:
425 except LockHeld:
503 pass
426 pass
504
427
505 def create_test_env(repos_test_path, config):
428 def create_test_env(repos_test_path, config):
506 """Makes a fresh database and
429 """Makes a fresh database and
507 install test repository into tmp dir
430 install test repository into tmp dir
508 """
431 """
509 from rhodecode.lib.db_manage import DbManage
432 from rhodecode.lib.db_manage import DbManage
510 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
433 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
511 HG_FORK, GIT_FORK, TESTS_TMP_PATH
434 HG_FORK, GIT_FORK, TESTS_TMP_PATH
512 import tarfile
435 import tarfile
513 import shutil
436 import shutil
514 from os.path import dirname as dn, join as jn, abspath
437 from os.path import dirname as dn, join as jn, abspath
515
438
516 log = logging.getLogger('TestEnvCreator')
439 log = logging.getLogger('TestEnvCreator')
517 # create logger
440 # create logger
518 log.setLevel(logging.DEBUG)
441 log.setLevel(logging.DEBUG)
519 log.propagate = True
442 log.propagate = True
520 # create console handler and set level to debug
443 # create console handler and set level to debug
521 ch = logging.StreamHandler()
444 ch = logging.StreamHandler()
522 ch.setLevel(logging.DEBUG)
445 ch.setLevel(logging.DEBUG)
523
446
524 # create formatter
447 # create formatter
525 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
448 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
526
449
527 # add formatter to ch
450 # add formatter to ch
528 ch.setFormatter(formatter)
451 ch.setFormatter(formatter)
529
452
530 # add ch to logger
453 # add ch to logger
531 log.addHandler(ch)
454 log.addHandler(ch)
532
455
533 #PART ONE create db
456 #PART ONE create db
534 dbname = config['sqlalchemy.db1.url'].split('/')[-1]
457 dbname = config['sqlalchemy.db1.url'].split('/')[-1]
535 log.debug('making test db %s', dbname)
458 log.debug('making test db %s', dbname)
536
459
537 dbmanage = DbManage(log_sql=True, dbname=dbname, root=config['here'],
460 dbmanage = DbManage(log_sql=True, dbname=dbname, root=config['here'],
538 tests=True)
461 tests=True)
539 dbmanage.create_tables(override=True)
462 dbmanage.create_tables(override=True)
540 dbmanage.config_prompt(repos_test_path)
463 dbmanage.config_prompt(repos_test_path)
541 dbmanage.create_default_user()
464 dbmanage.create_default_user()
542 dbmanage.admin_prompt()
465 dbmanage.admin_prompt()
543 dbmanage.create_permissions()
466 dbmanage.create_permissions()
544 dbmanage.populate_default_permissions()
467 dbmanage.populate_default_permissions()
545
468
546 #PART TWO make test repo
469 #PART TWO make test repo
547 log.debug('making test vcs repositories')
470 log.debug('making test vcs repositories')
548
471
549 #remove old one from previos tests
472 #remove old one from previos tests
550 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
473 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
551
474
552 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
475 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
553 log.debug('removing %s', r)
476 log.debug('removing %s', r)
554 shutil.rmtree(jn(TESTS_TMP_PATH, r))
477 shutil.rmtree(jn(TESTS_TMP_PATH, r))
555
478
556 #CREATE DEFAULT HG REPOSITORY
479 #CREATE DEFAULT HG REPOSITORY
557 cur_dir = dn(dn(abspath(__file__)))
480 cur_dir = dn(dn(abspath(__file__)))
558 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
481 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
559 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
482 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
560 tar.close()
483 tar.close()
561
484
562 class UpgradeDb(command.Command):
485 class UpgradeDb(command.Command):
563 """Command used for paster to upgrade our database to newer version
486 """Command used for paster to upgrade our database to newer version
564 """
487 """
565
488
566 max_args = 1
489 max_args = 1
567 min_args = 1
490 min_args = 1
568
491
569 usage = "CONFIG_FILE"
492 usage = "CONFIG_FILE"
570 summary = "Upgrades current db to newer version given configuration file"
493 summary = "Upgrades current db to newer version given configuration file"
571 group_name = "RhodeCode"
494 group_name = "RhodeCode"
572
495
573 parser = command.Command.standard_parser(verbose=True)
496 parser = command.Command.standard_parser(verbose=True)
574
497
575 parser.add_option('--sql',
498 parser.add_option('--sql',
576 action='store_true',
499 action='store_true',
577 dest='just_sql',
500 dest='just_sql',
578 help="Prints upgrade sql for further investigation",
501 help="Prints upgrade sql for further investigation",
579 default=False)
502 default=False)
580 def command(self):
503 def command(self):
581 config_name = self.args[0]
504 config_name = self.args[0]
582 p = config_name.split('/')
505 p = config_name.split('/')
583 root = '.' if len(p) == 1 else '/'.join(p[:-1])
506 root = '.' if len(p) == 1 else '/'.join(p[:-1])
584 config = ConfigParser.ConfigParser({'here':root})
507 config = ConfigParser.ConfigParser({'here':root})
585 config.read(config_name)
508 config.read(config_name)
@@ -1,76 +1,87 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Model for RhodeCode settings
3 # Model for RhodeCode settings
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 Nov 17, 2010
21 Created on Nov 17, 2010
22 Model for RhodeCode
22 Model for RhodeCode
23 :author: marcink
23 :author: marcink
24 """
24 """
25
25
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.model import BaseModel
27 from rhodecode.model import BaseModel
28 from rhodecode.model.caching_query import FromCache
28 from rhodecode.model.caching_query import FromCache
29 from rhodecode.model.db import RhodeCodeSettings
29 from rhodecode.model.db import RhodeCodeSettings
30 from sqlalchemy.orm import joinedload
30 from sqlalchemy.orm import joinedload
31 from sqlalchemy.orm.session import make_transient
32 import logging
31 import logging
33
32
34 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
35
34
36 class SettingsModel(BaseModel):
35 class SettingsModel(BaseModel):
37 """
36 """
38 Settings model
37 Settings model
39 """
38 """
40
39
41 def get(self, settings_key, cache=False):
40 def get(self, settings_key, cache=False):
42 r = self.sa.query(RhodeCodeSettings)\
41 r = self.sa.query(RhodeCodeSettings)\
43 .filter(RhodeCodeSettings.app_settings_name == settings_key).scalar()
42 .filter(RhodeCodeSettings.app_settings_name == settings_key).scalar()
44 if cache:
43 if cache:
45 r = r.options(FromCache("sql_cache_short",
44 r = r.options(FromCache("sql_cache_short",
46 "get_setting_%s" % settings_key))
45 "get_setting_%s" % settings_key))
47 return r
46 return r
48
47
48 def get_app_settings(self):
49 ret = self.sa.query(RhodeCodeSettings)\
50 .options(FromCache("sql_cache_short",
51 "get_hg_settings")).all()
52
53 if not ret:
54 raise Exception('Could not get application settings !')
55 settings = {}
56 for each in ret:
57 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
58
59 return settings
49
60
50 def get_ldap_settings(self):
61 def get_ldap_settings(self):
51 """
62 """
52 Returns ldap settings from database
63 Returns ldap settings from database
53 :returns:
64 :returns:
54 ldap_active
65 ldap_active
55 ldap_host
66 ldap_host
56 ldap_port
67 ldap_port
57 ldap_ldaps
68 ldap_ldaps
58 ldap_dn_user
69 ldap_dn_user
59 ldap_dn_pass
70 ldap_dn_pass
60 ldap_base_dn
71 ldap_base_dn
61 """
72 """
62
73
63 r = self.sa.query(RhodeCodeSettings)\
74 r = self.sa.query(RhodeCodeSettings)\
64 .filter(RhodeCodeSettings.app_settings_name\
75 .filter(RhodeCodeSettings.app_settings_name\
65 .startswith('ldap_'))\
76 .startswith('ldap_'))\
66 .all()
77 .all()
67
78
68 fd = {}
79 fd = {}
69
80
70 for row in r:
81 for row in r:
71 v = row.app_settings_value
82 v = row.app_settings_value
72 if v in ['0', '1']:
83 if v in ['0', '1']:
73 v = v == '1'
84 v = v == '1'
74 fd.update({row.app_settings_name:v})
85 fd.update({row.app_settings_name:v})
75
86
76 return fd
87 return fd
General Comments 0
You need to be logged in to leave comments. Login now