##// END OF EJS Templates
Use lightweight revisions in journal by default
marcink -
r2958:c0a6a2e6 beta
parent child Browse files
Show More
@@ -1,513 +1,508
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.settings
3 rhodecode.controllers.admin.settings
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 settings controller for rhodecode admin
6 settings controller for rhodecode admin
7
7
8 :created_on: Jul 14, 2010
8 :created_on: Jul 14, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29 import pkg_resources
29 import pkg_resources
30 import platform
30 import platform
31
31
32 from sqlalchemy import func
32 from sqlalchemy import func
33 from formencode import htmlfill
33 from formencode import htmlfill
34 from pylons import request, session, tmpl_context as c, url, config
34 from pylons import request, session, tmpl_context as c, url, config
35 from pylons.controllers.util import abort, redirect
35 from pylons.controllers.util import abort, redirect
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 HasPermissionAnyDecorator, NotAnonymous
40 HasPermissionAnyDecorator, NotAnonymous
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.celerylib import tasks, run_task
42 from rhodecode.lib.celerylib import tasks, run_task
43 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
43 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
44 set_rhodecode_config, repo_name_slug, check_git_version
44 set_rhodecode_config, repo_name_slug, check_git_version
45 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
45 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
46 RhodeCodeSetting, PullRequest, PullRequestReviewers
46 RhodeCodeSetting, PullRequest, PullRequestReviewers
47 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
47 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
48 ApplicationUiSettingsForm, ApplicationVisualisationForm
48 ApplicationUiSettingsForm, ApplicationVisualisationForm
49 from rhodecode.model.scm import ScmModel
49 from rhodecode.model.scm import ScmModel
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.db import User
51 from rhodecode.model.db import User
52 from rhodecode.model.notification import EmailNotificationModel
52 from rhodecode.model.notification import EmailNotificationModel
53 from rhodecode.model.meta import Session
53 from rhodecode.model.meta import Session
54 from rhodecode.lib.utils2 import str2bool
54 from rhodecode.lib.utils2 import str2bool
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class SettingsController(BaseController):
59 class SettingsController(BaseController):
60 """REST Controller styled on the Atom Publishing Protocol"""
60 """REST Controller styled on the Atom Publishing Protocol"""
61 # To properly map this controller, ensure your config/routing.py
61 # To properly map this controller, ensure your config/routing.py
62 # file has a resource setup:
62 # file has a resource setup:
63 # map.resource('setting', 'settings', controller='admin/settings',
63 # map.resource('setting', 'settings', controller='admin/settings',
64 # path_prefix='/admin', name_prefix='admin_')
64 # path_prefix='/admin', name_prefix='admin_')
65
65
66 @LoginRequired()
66 @LoginRequired()
67 def __before__(self):
67 def __before__(self):
68 c.admin_user = session.get('admin_user')
68 c.admin_user = session.get('admin_user')
69 c.admin_username = session.get('admin_username')
69 c.admin_username = session.get('admin_username')
70 c.modules = sorted([(p.project_name, p.version)
70 c.modules = sorted([(p.project_name, p.version)
71 for p in pkg_resources.working_set]
71 for p in pkg_resources.working_set]
72 + [('git', check_git_version())],
72 + [('git', check_git_version())],
73 key=lambda k: k[0].lower())
73 key=lambda k: k[0].lower())
74 c.py_version = platform.python_version()
74 c.py_version = platform.python_version()
75 c.platform = platform.platform()
75 c.platform = platform.platform()
76 super(SettingsController, self).__before__()
76 super(SettingsController, self).__before__()
77
77
78 @HasPermissionAllDecorator('hg.admin')
78 @HasPermissionAllDecorator('hg.admin')
79 def index(self, format='html'):
79 def index(self, format='html'):
80 """GET /admin/settings: All items in the collection"""
80 """GET /admin/settings: All items in the collection"""
81 # url('admin_settings')
81 # url('admin_settings')
82
82
83 defaults = RhodeCodeSetting.get_app_settings()
83 defaults = RhodeCodeSetting.get_app_settings()
84 defaults.update(self._get_hg_ui_settings())
84 defaults.update(self._get_hg_ui_settings())
85
85
86 return htmlfill.render(
86 return htmlfill.render(
87 render('admin/settings/settings.html'),
87 render('admin/settings/settings.html'),
88 defaults=defaults,
88 defaults=defaults,
89 encoding="UTF-8",
89 encoding="UTF-8",
90 force_defaults=False
90 force_defaults=False
91 )
91 )
92
92
93 @HasPermissionAllDecorator('hg.admin')
93 @HasPermissionAllDecorator('hg.admin')
94 def create(self):
94 def create(self):
95 """POST /admin/settings: Create a new item"""
95 """POST /admin/settings: Create a new item"""
96 # url('admin_settings')
96 # url('admin_settings')
97
97
98 @HasPermissionAllDecorator('hg.admin')
98 @HasPermissionAllDecorator('hg.admin')
99 def new(self, format='html'):
99 def new(self, format='html'):
100 """GET /admin/settings/new: Form to create a new item"""
100 """GET /admin/settings/new: Form to create a new item"""
101 # url('admin_new_setting')
101 # url('admin_new_setting')
102
102
103 @HasPermissionAllDecorator('hg.admin')
103 @HasPermissionAllDecorator('hg.admin')
104 def update(self, setting_id):
104 def update(self, setting_id):
105 """PUT /admin/settings/setting_id: Update an existing item"""
105 """PUT /admin/settings/setting_id: Update an existing item"""
106 # Forms posted to this method should contain a hidden field:
106 # Forms posted to this method should contain a hidden field:
107 # <input type="hidden" name="_method" value="PUT" />
107 # <input type="hidden" name="_method" value="PUT" />
108 # Or using helpers:
108 # Or using helpers:
109 # h.form(url('admin_setting', setting_id=ID),
109 # h.form(url('admin_setting', setting_id=ID),
110 # method='put')
110 # method='put')
111 # url('admin_setting', setting_id=ID)
111 # url('admin_setting', setting_id=ID)
112
112
113 if setting_id == 'mapping':
113 if setting_id == 'mapping':
114 rm_obsolete = request.POST.get('destroy', False)
114 rm_obsolete = request.POST.get('destroy', False)
115 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
115 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
116 initial = ScmModel().repo_scan()
116 initial = ScmModel().repo_scan()
117 log.debug('invalidating all repositories')
117 log.debug('invalidating all repositories')
118 for repo_name in initial.keys():
118 for repo_name in initial.keys():
119 invalidate_cache('get_repo_cached_%s' % repo_name)
119 invalidate_cache('get_repo_cached_%s' % repo_name)
120
120
121 added, removed = repo2db_mapper(initial, rm_obsolete)
121 added, removed = repo2db_mapper(initial, rm_obsolete)
122
122
123 h.flash(_('Repositories successfully'
123 h.flash(_('Repositories successfully'
124 ' rescanned added: %s,removed: %s') % (added, removed),
124 ' rescanned added: %s,removed: %s') % (added, removed),
125 category='success')
125 category='success')
126
126
127 if setting_id == 'whoosh':
127 if setting_id == 'whoosh':
128 repo_location = self._get_hg_ui_settings()['paths_root_path']
128 repo_location = self._get_hg_ui_settings()['paths_root_path']
129 full_index = request.POST.get('full_index', False)
129 full_index = request.POST.get('full_index', False)
130 run_task(tasks.whoosh_index, repo_location, full_index)
130 run_task(tasks.whoosh_index, repo_location, full_index)
131 h.flash(_('Whoosh reindex task scheduled'), category='success')
131 h.flash(_('Whoosh reindex task scheduled'), category='success')
132
132
133 if setting_id == 'global':
133 if setting_id == 'global':
134
134
135 application_form = ApplicationSettingsForm()()
135 application_form = ApplicationSettingsForm()()
136 try:
136 try:
137 form_result = application_form.to_python(dict(request.POST))
137 form_result = application_form.to_python(dict(request.POST))
138 except formencode.Invalid, errors:
138 except formencode.Invalid, errors:
139 return htmlfill.render(
139 return htmlfill.render(
140 render('admin/settings/settings.html'),
140 render('admin/settings/settings.html'),
141 defaults=errors.value,
141 defaults=errors.value,
142 errors=errors.error_dict or {},
142 errors=errors.error_dict or {},
143 prefix_error=False,
143 prefix_error=False,
144 encoding="UTF-8"
144 encoding="UTF-8"
145 )
145 )
146
146
147 try:
147 try:
148 sett1 = RhodeCodeSetting.get_by_name_or_create('title')
148 sett1 = RhodeCodeSetting.get_by_name_or_create('title')
149 sett1.app_settings_value = form_result['rhodecode_title']
149 sett1.app_settings_value = form_result['rhodecode_title']
150 Session().add(sett1)
150 Session().add(sett1)
151
151
152 sett2 = RhodeCodeSetting.get_by_name_or_create('realm')
152 sett2 = RhodeCodeSetting.get_by_name_or_create('realm')
153 sett2.app_settings_value = form_result['rhodecode_realm']
153 sett2.app_settings_value = form_result['rhodecode_realm']
154 Session().add(sett2)
154 Session().add(sett2)
155
155
156 sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code')
156 sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code')
157 sett3.app_settings_value = form_result['rhodecode_ga_code']
157 sett3.app_settings_value = form_result['rhodecode_ga_code']
158 Session().add(sett3)
158 Session().add(sett3)
159
159
160 Session().commit()
160 Session().commit()
161 set_rhodecode_config(config)
161 set_rhodecode_config(config)
162 h.flash(_('Updated application settings'), category='success')
162 h.flash(_('Updated application settings'), category='success')
163
163
164 except Exception:
164 except Exception:
165 log.error(traceback.format_exc())
165 log.error(traceback.format_exc())
166 h.flash(_('error occurred during updating '
166 h.flash(_('error occurred during updating '
167 'application settings'),
167 'application settings'),
168 category='error')
168 category='error')
169
169
170 if setting_id == 'visual':
170 if setting_id == 'visual':
171
171
172 application_form = ApplicationVisualisationForm()()
172 application_form = ApplicationVisualisationForm()()
173 try:
173 try:
174 form_result = application_form.to_python(dict(request.POST))
174 form_result = application_form.to_python(dict(request.POST))
175 except formencode.Invalid, errors:
175 except formencode.Invalid, errors:
176 return htmlfill.render(
176 return htmlfill.render(
177 render('admin/settings/settings.html'),
177 render('admin/settings/settings.html'),
178 defaults=errors.value,
178 defaults=errors.value,
179 errors=errors.error_dict or {},
179 errors=errors.error_dict or {},
180 prefix_error=False,
180 prefix_error=False,
181 encoding="UTF-8"
181 encoding="UTF-8"
182 )
182 )
183
183
184 try:
184 try:
185 sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon')
185 sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon')
186 sett1.app_settings_value = \
186 sett1.app_settings_value = \
187 form_result['rhodecode_show_public_icon']
187 form_result['rhodecode_show_public_icon']
188 Session().add(sett1)
188 Session().add(sett1)
189
189
190 sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon')
190 sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon')
191 sett2.app_settings_value = \
191 sett2.app_settings_value = \
192 form_result['rhodecode_show_private_icon']
192 form_result['rhodecode_show_private_icon']
193 Session().add(sett2)
193 Session().add(sett2)
194
194
195 sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags')
195 sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags')
196 sett3.app_settings_value = \
196 sett3.app_settings_value = \
197 form_result['rhodecode_stylify_metatags']
197 form_result['rhodecode_stylify_metatags']
198 Session().add(sett3)
198 Session().add(sett3)
199
199
200 sett4 = RhodeCodeSetting.get_by_name_or_create('lightweight_dashboard')
200 sett4 = RhodeCodeSetting.get_by_name_or_create('lightweight_dashboard')
201 sett4.app_settings_value = \
201 sett4.app_settings_value = \
202 form_result['rhodecode_lightweight_dashboard']
202 form_result['rhodecode_lightweight_dashboard']
203 Session().add(sett4)
203 Session().add(sett4)
204
204
205 sett5 = RhodeCodeSetting.get_by_name_or_create('lightweight_journal')
206 sett5.app_settings_value = \
207 form_result['rhodecode_lightweight_journal']
208 Session().add(sett5)
209
210 Session().commit()
205 Session().commit()
211 set_rhodecode_config(config)
206 set_rhodecode_config(config)
212 h.flash(_('Updated visualisation settings'),
207 h.flash(_('Updated visualisation settings'),
213 category='success')
208 category='success')
214
209
215 except Exception:
210 except Exception:
216 log.error(traceback.format_exc())
211 log.error(traceback.format_exc())
217 h.flash(_('error occurred during updating '
212 h.flash(_('error occurred during updating '
218 'visualisation settings'),
213 'visualisation settings'),
219 category='error')
214 category='error')
220
215
221 if setting_id == 'vcs':
216 if setting_id == 'vcs':
222 application_form = ApplicationUiSettingsForm()()
217 application_form = ApplicationUiSettingsForm()()
223 try:
218 try:
224 form_result = application_form.to_python(dict(request.POST))
219 form_result = application_form.to_python(dict(request.POST))
225 except formencode.Invalid, errors:
220 except formencode.Invalid, errors:
226 return htmlfill.render(
221 return htmlfill.render(
227 render('admin/settings/settings.html'),
222 render('admin/settings/settings.html'),
228 defaults=errors.value,
223 defaults=errors.value,
229 errors=errors.error_dict or {},
224 errors=errors.error_dict or {},
230 prefix_error=False,
225 prefix_error=False,
231 encoding="UTF-8"
226 encoding="UTF-8"
232 )
227 )
233
228
234 try:
229 try:
235 # fix namespaces for hooks and extensions
230 # fix namespaces for hooks and extensions
236 _f = lambda s: s.replace('.', '_')
231 _f = lambda s: s.replace('.', '_')
237
232
238 sett = RhodeCodeUi.get_by_key('push_ssl')
233 sett = RhodeCodeUi.get_by_key('push_ssl')
239 sett.ui_value = form_result['web_push_ssl']
234 sett.ui_value = form_result['web_push_ssl']
240 Session().add(sett)
235 Session().add(sett)
241
236
242 sett = RhodeCodeUi.get_by_key('/')
237 sett = RhodeCodeUi.get_by_key('/')
243 sett.ui_value = form_result['paths_root_path']
238 sett.ui_value = form_result['paths_root_path']
244 Session().add(sett)
239 Session().add(sett)
245
240
246 #HOOKS
241 #HOOKS
247 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE)
242 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE)
248 sett.ui_active = form_result[_f('hooks_%s' %
243 sett.ui_active = form_result[_f('hooks_%s' %
249 RhodeCodeUi.HOOK_UPDATE)]
244 RhodeCodeUi.HOOK_UPDATE)]
250 Session().add(sett)
245 Session().add(sett)
251
246
252 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_REPO_SIZE)
247 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_REPO_SIZE)
253 sett.ui_active = form_result[_f('hooks_%s' %
248 sett.ui_active = form_result[_f('hooks_%s' %
254 RhodeCodeUi.HOOK_REPO_SIZE)]
249 RhodeCodeUi.HOOK_REPO_SIZE)]
255 Session().add(sett)
250 Session().add(sett)
256
251
257 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PUSH)
252 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PUSH)
258 sett.ui_active = form_result[_f('hooks_%s' %
253 sett.ui_active = form_result[_f('hooks_%s' %
259 RhodeCodeUi.HOOK_PUSH)]
254 RhodeCodeUi.HOOK_PUSH)]
260 Session().add(sett)
255 Session().add(sett)
261
256
262 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PULL)
257 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PULL)
263 sett.ui_active = form_result[_f('hooks_%s' %
258 sett.ui_active = form_result[_f('hooks_%s' %
264 RhodeCodeUi.HOOK_PULL)]
259 RhodeCodeUi.HOOK_PULL)]
265
260
266 Session().add(sett)
261 Session().add(sett)
267
262
268 ## EXTENSIONS
263 ## EXTENSIONS
269 sett = RhodeCodeUi.get_by_key('largefiles')
264 sett = RhodeCodeUi.get_by_key('largefiles')
270 if not sett:
265 if not sett:
271 #make one if it's not there !
266 #make one if it's not there !
272 sett = RhodeCodeUi()
267 sett = RhodeCodeUi()
273 sett.ui_key = 'largefiles'
268 sett.ui_key = 'largefiles'
274 sett.ui_section = 'extensions'
269 sett.ui_section = 'extensions'
275 sett.ui_active = form_result[_f('extensions_largefiles')]
270 sett.ui_active = form_result[_f('extensions_largefiles')]
276 Session().add(sett)
271 Session().add(sett)
277
272
278 sett = RhodeCodeUi.get_by_key('hgsubversion')
273 sett = RhodeCodeUi.get_by_key('hgsubversion')
279 if not sett:
274 if not sett:
280 #make one if it's not there !
275 #make one if it's not there !
281 sett = RhodeCodeUi()
276 sett = RhodeCodeUi()
282 sett.ui_key = 'hgsubversion'
277 sett.ui_key = 'hgsubversion'
283 sett.ui_section = 'extensions'
278 sett.ui_section = 'extensions'
284
279
285 sett.ui_active = form_result[_f('extensions_hgsubversion')]
280 sett.ui_active = form_result[_f('extensions_hgsubversion')]
286 Session().add(sett)
281 Session().add(sett)
287
282
288 # sett = RhodeCodeUi.get_by_key('hggit')
283 # sett = RhodeCodeUi.get_by_key('hggit')
289 # if not sett:
284 # if not sett:
290 # #make one if it's not there !
285 # #make one if it's not there !
291 # sett = RhodeCodeUi()
286 # sett = RhodeCodeUi()
292 # sett.ui_key = 'hggit'
287 # sett.ui_key = 'hggit'
293 # sett.ui_section = 'extensions'
288 # sett.ui_section = 'extensions'
294 #
289 #
295 # sett.ui_active = form_result[_f('extensions_hggit')]
290 # sett.ui_active = form_result[_f('extensions_hggit')]
296 # Session().add(sett)
291 # Session().add(sett)
297
292
298 Session().commit()
293 Session().commit()
299
294
300 h.flash(_('Updated VCS settings'), category='success')
295 h.flash(_('Updated VCS settings'), category='success')
301
296
302 except Exception:
297 except Exception:
303 log.error(traceback.format_exc())
298 log.error(traceback.format_exc())
304 h.flash(_('error occurred during updating '
299 h.flash(_('error occurred during updating '
305 'application settings'), category='error')
300 'application settings'), category='error')
306
301
307 if setting_id == 'hooks':
302 if setting_id == 'hooks':
308 ui_key = request.POST.get('new_hook_ui_key')
303 ui_key = request.POST.get('new_hook_ui_key')
309 ui_value = request.POST.get('new_hook_ui_value')
304 ui_value = request.POST.get('new_hook_ui_value')
310 try:
305 try:
311
306
312 if ui_value and ui_key:
307 if ui_value and ui_key:
313 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
308 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
314 h.flash(_('Added new hook'),
309 h.flash(_('Added new hook'),
315 category='success')
310 category='success')
316
311
317 # check for edits
312 # check for edits
318 update = False
313 update = False
319 _d = request.POST.dict_of_lists()
314 _d = request.POST.dict_of_lists()
320 for k, v in zip(_d.get('hook_ui_key', []),
315 for k, v in zip(_d.get('hook_ui_key', []),
321 _d.get('hook_ui_value_new', [])):
316 _d.get('hook_ui_value_new', [])):
322 RhodeCodeUi.create_or_update_hook(k, v)
317 RhodeCodeUi.create_or_update_hook(k, v)
323 update = True
318 update = True
324
319
325 if update:
320 if update:
326 h.flash(_('Updated hooks'), category='success')
321 h.flash(_('Updated hooks'), category='success')
327 Session().commit()
322 Session().commit()
328 except Exception:
323 except Exception:
329 log.error(traceback.format_exc())
324 log.error(traceback.format_exc())
330 h.flash(_('error occurred during hook creation'),
325 h.flash(_('error occurred during hook creation'),
331 category='error')
326 category='error')
332
327
333 return redirect(url('admin_edit_setting', setting_id='hooks'))
328 return redirect(url('admin_edit_setting', setting_id='hooks'))
334
329
335 if setting_id == 'email':
330 if setting_id == 'email':
336 test_email = request.POST.get('test_email')
331 test_email = request.POST.get('test_email')
337 test_email_subj = 'RhodeCode TestEmail'
332 test_email_subj = 'RhodeCode TestEmail'
338 test_email_body = 'RhodeCode Email test'
333 test_email_body = 'RhodeCode Email test'
339
334
340 test_email_html_body = EmailNotificationModel()\
335 test_email_html_body = EmailNotificationModel()\
341 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
336 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
342 body=test_email_body)
337 body=test_email_body)
343
338
344 recipients = [test_email] if [test_email] else None
339 recipients = [test_email] if [test_email] else None
345
340
346 run_task(tasks.send_email, recipients, test_email_subj,
341 run_task(tasks.send_email, recipients, test_email_subj,
347 test_email_body, test_email_html_body)
342 test_email_body, test_email_html_body)
348
343
349 h.flash(_('Email task created'), category='success')
344 h.flash(_('Email task created'), category='success')
350 return redirect(url('admin_settings'))
345 return redirect(url('admin_settings'))
351
346
352 @HasPermissionAllDecorator('hg.admin')
347 @HasPermissionAllDecorator('hg.admin')
353 def delete(self, setting_id):
348 def delete(self, setting_id):
354 """DELETE /admin/settings/setting_id: Delete an existing item"""
349 """DELETE /admin/settings/setting_id: Delete an existing item"""
355 # Forms posted to this method should contain a hidden field:
350 # Forms posted to this method should contain a hidden field:
356 # <input type="hidden" name="_method" value="DELETE" />
351 # <input type="hidden" name="_method" value="DELETE" />
357 # Or using helpers:
352 # Or using helpers:
358 # h.form(url('admin_setting', setting_id=ID),
353 # h.form(url('admin_setting', setting_id=ID),
359 # method='delete')
354 # method='delete')
360 # url('admin_setting', setting_id=ID)
355 # url('admin_setting', setting_id=ID)
361 if setting_id == 'hooks':
356 if setting_id == 'hooks':
362 hook_id = request.POST.get('hook_id')
357 hook_id = request.POST.get('hook_id')
363 RhodeCodeUi.delete(hook_id)
358 RhodeCodeUi.delete(hook_id)
364 Session().commit()
359 Session().commit()
365
360
366 @HasPermissionAllDecorator('hg.admin')
361 @HasPermissionAllDecorator('hg.admin')
367 def show(self, setting_id, format='html'):
362 def show(self, setting_id, format='html'):
368 """
363 """
369 GET /admin/settings/setting_id: Show a specific item"""
364 GET /admin/settings/setting_id: Show a specific item"""
370 # url('admin_setting', setting_id=ID)
365 # url('admin_setting', setting_id=ID)
371
366
372 @HasPermissionAllDecorator('hg.admin')
367 @HasPermissionAllDecorator('hg.admin')
373 def edit(self, setting_id, format='html'):
368 def edit(self, setting_id, format='html'):
374 """
369 """
375 GET /admin/settings/setting_id/edit: Form to
370 GET /admin/settings/setting_id/edit: Form to
376 edit an existing item"""
371 edit an existing item"""
377 # url('admin_edit_setting', setting_id=ID)
372 # url('admin_edit_setting', setting_id=ID)
378
373
379 c.hooks = RhodeCodeUi.get_builtin_hooks()
374 c.hooks = RhodeCodeUi.get_builtin_hooks()
380 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
375 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
381
376
382 return htmlfill.render(
377 return htmlfill.render(
383 render('admin/settings/hooks.html'),
378 render('admin/settings/hooks.html'),
384 defaults={},
379 defaults={},
385 encoding="UTF-8",
380 encoding="UTF-8",
386 force_defaults=False
381 force_defaults=False
387 )
382 )
388
383
389 @NotAnonymous()
384 @NotAnonymous()
390 def my_account(self):
385 def my_account(self):
391 """
386 """
392 GET /_admin/my_account Displays info about my account
387 GET /_admin/my_account Displays info about my account
393 """
388 """
394 # url('admin_settings_my_account')
389 # url('admin_settings_my_account')
395
390
396 c.user = User.get(self.rhodecode_user.user_id)
391 c.user = User.get(self.rhodecode_user.user_id)
397 all_repos = Session().query(Repository)\
392 all_repos = Session().query(Repository)\
398 .filter(Repository.user_id == c.user.user_id)\
393 .filter(Repository.user_id == c.user.user_id)\
399 .order_by(func.lower(Repository.repo_name)).all()
394 .order_by(func.lower(Repository.repo_name)).all()
400
395
401 c.user_repos = ScmModel().get_repos(all_repos)
396 c.user_repos = ScmModel().get_repos(all_repos)
402
397
403 if c.user.username == 'default':
398 if c.user.username == 'default':
404 h.flash(_("You can't edit this user since it's"
399 h.flash(_("You can't edit this user since it's"
405 " crucial for entire application"), category='warning')
400 " crucial for entire application"), category='warning')
406 return redirect(url('users'))
401 return redirect(url('users'))
407
402
408 defaults = c.user.get_dict()
403 defaults = c.user.get_dict()
409
404
410 c.form = htmlfill.render(
405 c.form = htmlfill.render(
411 render('admin/users/user_edit_my_account_form.html'),
406 render('admin/users/user_edit_my_account_form.html'),
412 defaults=defaults,
407 defaults=defaults,
413 encoding="UTF-8",
408 encoding="UTF-8",
414 force_defaults=False
409 force_defaults=False
415 )
410 )
416 return render('admin/users/user_edit_my_account.html')
411 return render('admin/users/user_edit_my_account.html')
417
412
418 @NotAnonymous()
413 @NotAnonymous()
419 def my_account_update(self):
414 def my_account_update(self):
420 """PUT /_admin/my_account_update: Update an existing item"""
415 """PUT /_admin/my_account_update: Update an existing item"""
421 # Forms posted to this method should contain a hidden field:
416 # Forms posted to this method should contain a hidden field:
422 # <input type="hidden" name="_method" value="PUT" />
417 # <input type="hidden" name="_method" value="PUT" />
423 # Or using helpers:
418 # Or using helpers:
424 # h.form(url('admin_settings_my_account_update'),
419 # h.form(url('admin_settings_my_account_update'),
425 # method='put')
420 # method='put')
426 # url('admin_settings_my_account_update', id=ID)
421 # url('admin_settings_my_account_update', id=ID)
427 uid = self.rhodecode_user.user_id
422 uid = self.rhodecode_user.user_id
428 email = self.rhodecode_user.email
423 email = self.rhodecode_user.email
429 _form = UserForm(edit=True,
424 _form = UserForm(edit=True,
430 old_data={'user_id': uid, 'email': email})()
425 old_data={'user_id': uid, 'email': email})()
431 form_result = {}
426 form_result = {}
432 try:
427 try:
433 form_result = _form.to_python(dict(request.POST))
428 form_result = _form.to_python(dict(request.POST))
434 UserModel().update_my_account(uid, form_result)
429 UserModel().update_my_account(uid, form_result)
435 h.flash(_('Your account was updated successfully'),
430 h.flash(_('Your account was updated successfully'),
436 category='success')
431 category='success')
437 Session().commit()
432 Session().commit()
438 except formencode.Invalid, errors:
433 except formencode.Invalid, errors:
439 c.user = User.get(self.rhodecode_user.user_id)
434 c.user = User.get(self.rhodecode_user.user_id)
440
435
441 c.form = htmlfill.render(
436 c.form = htmlfill.render(
442 render('admin/users/user_edit_my_account_form.html'),
437 render('admin/users/user_edit_my_account_form.html'),
443 defaults=errors.value,
438 defaults=errors.value,
444 errors=errors.error_dict or {},
439 errors=errors.error_dict or {},
445 prefix_error=False,
440 prefix_error=False,
446 encoding="UTF-8")
441 encoding="UTF-8")
447 return render('admin/users/user_edit_my_account.html')
442 return render('admin/users/user_edit_my_account.html')
448 except Exception:
443 except Exception:
449 log.error(traceback.format_exc())
444 log.error(traceback.format_exc())
450 h.flash(_('error occurred during update of user %s') \
445 h.flash(_('error occurred during update of user %s') \
451 % form_result.get('username'), category='error')
446 % form_result.get('username'), category='error')
452
447
453 return redirect(url('my_account'))
448 return redirect(url('my_account'))
454
449
455 @NotAnonymous()
450 @NotAnonymous()
456 def my_account_my_repos(self):
451 def my_account_my_repos(self):
457 all_repos = Session().query(Repository)\
452 all_repos = Session().query(Repository)\
458 .filter(Repository.user_id == self.rhodecode_user.user_id)\
453 .filter(Repository.user_id == self.rhodecode_user.user_id)\
459 .order_by(func.lower(Repository.repo_name))\
454 .order_by(func.lower(Repository.repo_name))\
460 .all()
455 .all()
461 c.user_repos = ScmModel().get_repos(all_repos)
456 c.user_repos = ScmModel().get_repos(all_repos)
462 return render('admin/users/user_edit_my_account_repos.html')
457 return render('admin/users/user_edit_my_account_repos.html')
463
458
464 @NotAnonymous()
459 @NotAnonymous()
465 def my_account_my_pullrequests(self):
460 def my_account_my_pullrequests(self):
466 c.my_pull_requests = PullRequest.query()\
461 c.my_pull_requests = PullRequest.query()\
467 .filter(PullRequest.user_id==
462 .filter(PullRequest.user_id==
468 self.rhodecode_user.user_id)\
463 self.rhodecode_user.user_id)\
469 .all()
464 .all()
470 c.participate_in_pull_requests = \
465 c.participate_in_pull_requests = \
471 [x.pull_request for x in PullRequestReviewers.query()\
466 [x.pull_request for x in PullRequestReviewers.query()\
472 .filter(PullRequestReviewers.user_id==
467 .filter(PullRequestReviewers.user_id==
473 self.rhodecode_user.user_id)\
468 self.rhodecode_user.user_id)\
474 .all()]
469 .all()]
475 return render('admin/users/user_edit_my_account_pullrequests.html')
470 return render('admin/users/user_edit_my_account_pullrequests.html')
476
471
477 @NotAnonymous()
472 @NotAnonymous()
478 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
473 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
479 def create_repository(self):
474 def create_repository(self):
480 """GET /_admin/create_repository: Form to create a new item"""
475 """GET /_admin/create_repository: Form to create a new item"""
481
476
482 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
477 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
483 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
478 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
484 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
479 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
485
480
486 new_repo = request.GET.get('repo', '')
481 new_repo = request.GET.get('repo', '')
487 c.new_repo = repo_name_slug(new_repo)
482 c.new_repo = repo_name_slug(new_repo)
488
483
489 return render('admin/repos/repo_add_create_repository.html')
484 return render('admin/repos/repo_add_create_repository.html')
490
485
491 def _get_hg_ui_settings(self):
486 def _get_hg_ui_settings(self):
492 ret = RhodeCodeUi.query().all()
487 ret = RhodeCodeUi.query().all()
493
488
494 if not ret:
489 if not ret:
495 raise Exception('Could not get application ui settings !')
490 raise Exception('Could not get application ui settings !')
496 settings = {}
491 settings = {}
497 for each in ret:
492 for each in ret:
498 k = each.ui_key
493 k = each.ui_key
499 v = each.ui_value
494 v = each.ui_value
500 if k == '/':
495 if k == '/':
501 k = 'root_path'
496 k = 'root_path'
502
497
503 if k == 'push_ssl':
498 if k == 'push_ssl':
504 v = str2bool(v)
499 v = str2bool(v)
505
500
506 if k.find('.') != -1:
501 if k.find('.') != -1:
507 k = k.replace('.', '_')
502 k = k.replace('.', '_')
508
503
509 if each.ui_section in ['hooks', 'extensions']:
504 if each.ui_section in ['hooks', 'extensions']:
510 v = each.ui_active
505 v = each.ui_active
511
506
512 settings[each.ui_section + '_' + k] = v
507 settings[each.ui_section + '_' + k] = v
513 return settings
508 return settings
@@ -1,324 +1,323
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 import logging
5 import logging
6 import time
6 import time
7 import traceback
7 import traceback
8
8
9 from paste.auth.basic import AuthBasicAuthenticator
9 from paste.auth.basic import AuthBasicAuthenticator
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
11 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
12
12
13 from pylons import config, tmpl_context as c, request, session, url
13 from pylons import config, tmpl_context as c, request, session, url
14 from pylons.controllers import WSGIController
14 from pylons.controllers import WSGIController
15 from pylons.controllers.util import redirect
15 from pylons.controllers.util import redirect
16 from pylons.templating import render_mako as render
16 from pylons.templating import render_mako as render
17
17
18 from rhodecode import __version__, BACKENDS
18 from rhodecode import __version__, BACKENDS
19
19
20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
21 safe_str
21 safe_str
22 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
22 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
23 HasPermissionAnyMiddleware, CookieStoreWrapper
23 HasPermissionAnyMiddleware, CookieStoreWrapper
24 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
24 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
25 from rhodecode.model import meta
25 from rhodecode.model import meta
26
26
27 from rhodecode.model.db import Repository, RhodeCodeUi, User, RhodeCodeSetting
27 from rhodecode.model.db import Repository, RhodeCodeUi, User, RhodeCodeSetting
28 from rhodecode.model.notification import NotificationModel
28 from rhodecode.model.notification import NotificationModel
29 from rhodecode.model.scm import ScmModel
29 from rhodecode.model.scm import ScmModel
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 def _get_ip_addr(environ):
35 def _get_ip_addr(environ):
36 proxy_key = 'HTTP_X_REAL_IP'
36 proxy_key = 'HTTP_X_REAL_IP'
37 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
37 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
38 def_key = 'REMOTE_ADDR'
38 def_key = 'REMOTE_ADDR'
39
39
40 ip = environ.get(proxy_key2)
40 ip = environ.get(proxy_key2)
41 if ip:
41 if ip:
42 return ip
42 return ip
43
43
44 ip = environ.get(proxy_key)
44 ip = environ.get(proxy_key)
45
45
46 if ip:
46 if ip:
47 return ip
47 return ip
48
48
49 ip = environ.get(def_key, '0.0.0.0')
49 ip = environ.get(def_key, '0.0.0.0')
50 return ip
50 return ip
51
51
52
52
53 def _get_access_path(environ):
53 def _get_access_path(environ):
54 path = environ.get('PATH_INFO')
54 path = environ.get('PATH_INFO')
55 org_req = environ.get('pylons.original_request')
55 org_req = environ.get('pylons.original_request')
56 if org_req:
56 if org_req:
57 path = org_req.environ.get('PATH_INFO')
57 path = org_req.environ.get('PATH_INFO')
58 return path
58 return path
59
59
60
60
61 class BasicAuth(AuthBasicAuthenticator):
61 class BasicAuth(AuthBasicAuthenticator):
62
62
63 def __init__(self, realm, authfunc, auth_http_code=None):
63 def __init__(self, realm, authfunc, auth_http_code=None):
64 self.realm = realm
64 self.realm = realm
65 self.authfunc = authfunc
65 self.authfunc = authfunc
66 self._rc_auth_http_code = auth_http_code
66 self._rc_auth_http_code = auth_http_code
67
67
68 def build_authentication(self):
68 def build_authentication(self):
69 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
69 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
70 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
70 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
71 # return 403 if alternative http return code is specified in
71 # return 403 if alternative http return code is specified in
72 # RhodeCode config
72 # RhodeCode config
73 return HTTPForbidden(headers=head)
73 return HTTPForbidden(headers=head)
74 return HTTPUnauthorized(headers=head)
74 return HTTPUnauthorized(headers=head)
75
75
76 def authenticate(self, environ):
76 def authenticate(self, environ):
77 authorization = AUTHORIZATION(environ)
77 authorization = AUTHORIZATION(environ)
78 if not authorization:
78 if not authorization:
79 return self.build_authentication()
79 return self.build_authentication()
80 (authmeth, auth) = authorization.split(' ', 1)
80 (authmeth, auth) = authorization.split(' ', 1)
81 if 'basic' != authmeth.lower():
81 if 'basic' != authmeth.lower():
82 return self.build_authentication()
82 return self.build_authentication()
83 auth = auth.strip().decode('base64')
83 auth = auth.strip().decode('base64')
84 _parts = auth.split(':', 1)
84 _parts = auth.split(':', 1)
85 if len(_parts) == 2:
85 if len(_parts) == 2:
86 username, password = _parts
86 username, password = _parts
87 if self.authfunc(environ, username, password):
87 if self.authfunc(environ, username, password):
88 return username
88 return username
89 return self.build_authentication()
89 return self.build_authentication()
90
90
91 __call__ = authenticate
91 __call__ = authenticate
92
92
93
93
94 class BaseVCSController(object):
94 class BaseVCSController(object):
95
95
96 def __init__(self, application, config):
96 def __init__(self, application, config):
97 self.application = application
97 self.application = application
98 self.config = config
98 self.config = config
99 # base path of repo locations
99 # base path of repo locations
100 self.basepath = self.config['base_path']
100 self.basepath = self.config['base_path']
101 #authenticate this mercurial request using authfunc
101 #authenticate this mercurial request using authfunc
102 self.authenticate = BasicAuth('', authfunc,
102 self.authenticate = BasicAuth('', authfunc,
103 config.get('auth_ret_code'))
103 config.get('auth_ret_code'))
104 self.ipaddr = '0.0.0.0'
104 self.ipaddr = '0.0.0.0'
105
105
106 def _handle_request(self, environ, start_response):
106 def _handle_request(self, environ, start_response):
107 raise NotImplementedError()
107 raise NotImplementedError()
108
108
109 def _get_by_id(self, repo_name):
109 def _get_by_id(self, repo_name):
110 """
110 """
111 Get's a special pattern _<ID> from clone url and tries to replace it
111 Get's a special pattern _<ID> from clone url and tries to replace it
112 with a repository_name for support of _<ID> non changable urls
112 with a repository_name for support of _<ID> non changable urls
113
113
114 :param repo_name:
114 :param repo_name:
115 """
115 """
116 try:
116 try:
117 data = repo_name.split('/')
117 data = repo_name.split('/')
118 if len(data) >= 2:
118 if len(data) >= 2:
119 by_id = data[1].split('_')
119 by_id = data[1].split('_')
120 if len(by_id) == 2 and by_id[1].isdigit():
120 if len(by_id) == 2 and by_id[1].isdigit():
121 _repo_name = Repository.get(by_id[1]).repo_name
121 _repo_name = Repository.get(by_id[1]).repo_name
122 data[1] = _repo_name
122 data[1] = _repo_name
123 except:
123 except:
124 log.debug('Failed to extract repo_name from id %s' % (
124 log.debug('Failed to extract repo_name from id %s' % (
125 traceback.format_exc()
125 traceback.format_exc()
126 )
126 )
127 )
127 )
128
128
129 return '/'.join(data)
129 return '/'.join(data)
130
130
131 def _invalidate_cache(self, repo_name):
131 def _invalidate_cache(self, repo_name):
132 """
132 """
133 Set's cache for this repository for invalidation on next access
133 Set's cache for this repository for invalidation on next access
134
134
135 :param repo_name: full repo name, also a cache key
135 :param repo_name: full repo name, also a cache key
136 """
136 """
137 invalidate_cache('get_repo_cached_%s' % repo_name)
137 invalidate_cache('get_repo_cached_%s' % repo_name)
138
138
139 def _check_permission(self, action, user, repo_name):
139 def _check_permission(self, action, user, repo_name):
140 """
140 """
141 Checks permissions using action (push/pull) user and repository
141 Checks permissions using action (push/pull) user and repository
142 name
142 name
143
143
144 :param action: push or pull action
144 :param action: push or pull action
145 :param user: user instance
145 :param user: user instance
146 :param repo_name: repository name
146 :param repo_name: repository name
147 """
147 """
148 if action == 'push':
148 if action == 'push':
149 if not HasPermissionAnyMiddleware('repository.write',
149 if not HasPermissionAnyMiddleware('repository.write',
150 'repository.admin')(user,
150 'repository.admin')(user,
151 repo_name):
151 repo_name):
152 return False
152 return False
153
153
154 else:
154 else:
155 #any other action need at least read permission
155 #any other action need at least read permission
156 if not HasPermissionAnyMiddleware('repository.read',
156 if not HasPermissionAnyMiddleware('repository.read',
157 'repository.write',
157 'repository.write',
158 'repository.admin')(user,
158 'repository.admin')(user,
159 repo_name):
159 repo_name):
160 return False
160 return False
161
161
162 return True
162 return True
163
163
164 def _get_ip_addr(self, environ):
164 def _get_ip_addr(self, environ):
165 return _get_ip_addr(environ)
165 return _get_ip_addr(environ)
166
166
167 def _check_ssl(self, environ, start_response):
167 def _check_ssl(self, environ, start_response):
168 """
168 """
169 Checks the SSL check flag and returns False if SSL is not present
169 Checks the SSL check flag and returns False if SSL is not present
170 and required True otherwise
170 and required True otherwise
171 """
171 """
172 org_proto = environ['wsgi._org_proto']
172 org_proto = environ['wsgi._org_proto']
173 #check if we have SSL required ! if not it's a bad request !
173 #check if we have SSL required ! if not it's a bad request !
174 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
174 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
175 if require_ssl and org_proto == 'http':
175 if require_ssl and org_proto == 'http':
176 log.debug('proto is %s and SSL is required BAD REQUEST !'
176 log.debug('proto is %s and SSL is required BAD REQUEST !'
177 % org_proto)
177 % org_proto)
178 return False
178 return False
179 return True
179 return True
180
180
181 def _check_locking_state(self, environ, action, repo, user_id):
181 def _check_locking_state(self, environ, action, repo, user_id):
182 """
182 """
183 Checks locking on this repository, if locking is enabled and lock is
183 Checks locking on this repository, if locking is enabled and lock is
184 present returns a tuple of make_lock, locked, locked_by.
184 present returns a tuple of make_lock, locked, locked_by.
185 make_lock can have 3 states None (do nothing) True, make lock
185 make_lock can have 3 states None (do nothing) True, make lock
186 False release lock, This value is later propagated to hooks, which
186 False release lock, This value is later propagated to hooks, which
187 do the locking. Think about this as signals passed to hooks what to do.
187 do the locking. Think about this as signals passed to hooks what to do.
188
188
189 """
189 """
190 locked = False # defines that locked error should be thrown to user
190 locked = False # defines that locked error should be thrown to user
191 make_lock = None
191 make_lock = None
192 repo = Repository.get_by_repo_name(repo)
192 repo = Repository.get_by_repo_name(repo)
193 user = User.get(user_id)
193 user = User.get(user_id)
194
194
195 # this is kind of hacky, but due to how mercurial handles client-server
195 # this is kind of hacky, but due to how mercurial handles client-server
196 # server see all operation on changeset; bookmarks, phases and
196 # server see all operation on changeset; bookmarks, phases and
197 # obsolescence marker in different transaction, we don't want to check
197 # obsolescence marker in different transaction, we don't want to check
198 # locking on those
198 # locking on those
199 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
199 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
200 locked_by = repo.locked
200 locked_by = repo.locked
201 if repo and repo.enable_locking and not obsolete_call:
201 if repo and repo.enable_locking and not obsolete_call:
202 if action == 'push':
202 if action == 'push':
203 #check if it's already locked !, if it is compare users
203 #check if it's already locked !, if it is compare users
204 user_id, _date = repo.locked
204 user_id, _date = repo.locked
205 if user.user_id == user_id:
205 if user.user_id == user_id:
206 log.debug('Got push from user %s, now unlocking' % (user))
206 log.debug('Got push from user %s, now unlocking' % (user))
207 # unlock if we have push from user who locked
207 # unlock if we have push from user who locked
208 make_lock = False
208 make_lock = False
209 else:
209 else:
210 # we're not the same user who locked, ban with 423 !
210 # we're not the same user who locked, ban with 423 !
211 locked = True
211 locked = True
212 if action == 'pull':
212 if action == 'pull':
213 if repo.locked[0] and repo.locked[1]:
213 if repo.locked[0] and repo.locked[1]:
214 locked = True
214 locked = True
215 else:
215 else:
216 log.debug('Setting lock on repo %s by %s' % (repo, user))
216 log.debug('Setting lock on repo %s by %s' % (repo, user))
217 make_lock = True
217 make_lock = True
218
218
219 else:
219 else:
220 log.debug('Repository %s do not have locking enabled' % (repo))
220 log.debug('Repository %s do not have locking enabled' % (repo))
221 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
221 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
222 % (make_lock, locked, locked_by))
222 % (make_lock, locked, locked_by))
223 return make_lock, locked, locked_by
223 return make_lock, locked, locked_by
224
224
225 def __call__(self, environ, start_response):
225 def __call__(self, environ, start_response):
226 start = time.time()
226 start = time.time()
227 try:
227 try:
228 return self._handle_request(environ, start_response)
228 return self._handle_request(environ, start_response)
229 finally:
229 finally:
230 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
230 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
231 log.debug('Request time: %.3fs' % (time.time() - start))
231 log.debug('Request time: %.3fs' % (time.time() - start))
232 meta.Session.remove()
232 meta.Session.remove()
233
233
234
234
235 class BaseController(WSGIController):
235 class BaseController(WSGIController):
236
236
237 def __before__(self):
237 def __before__(self):
238 c.rhodecode_version = __version__
238 c.rhodecode_version = __version__
239 c.rhodecode_instanceid = config.get('instance_id')
239 c.rhodecode_instanceid = config.get('instance_id')
240 c.rhodecode_name = config.get('rhodecode_title')
240 c.rhodecode_name = config.get('rhodecode_title')
241 c.use_gravatar = str2bool(config.get('use_gravatar'))
241 c.use_gravatar = str2bool(config.get('use_gravatar'))
242 c.ga_code = config.get('rhodecode_ga_code')
242 c.ga_code = config.get('rhodecode_ga_code')
243 # Visual options
243 # Visual options
244 c.visual = AttributeDict({})
244 c.visual = AttributeDict({})
245 rc_config = RhodeCodeSetting.get_app_settings()
245 rc_config = RhodeCodeSetting.get_app_settings()
246
246
247 c.visual.show_public_icon = str2bool(rc_config.get('rhodecode_show_public_icon'))
247 c.visual.show_public_icon = str2bool(rc_config.get('rhodecode_show_public_icon'))
248 c.visual.show_private_icon = str2bool(rc_config.get('rhodecode_show_private_icon'))
248 c.visual.show_private_icon = str2bool(rc_config.get('rhodecode_show_private_icon'))
249 c.visual.stylify_metatags = str2bool(rc_config.get('rhodecode_stylify_metatags'))
249 c.visual.stylify_metatags = str2bool(rc_config.get('rhodecode_stylify_metatags'))
250 c.visual.lightweight_dashboard = str2bool(rc_config.get('rhodecode_lightweight_dashboard'))
250 c.visual.lightweight_dashboard = str2bool(rc_config.get('rhodecode_lightweight_dashboard'))
251 c.visual.lightweight_journal = str2bool(rc_config.get('rhodecode_lightweight_dashboard'))
252
251
253 c.repo_name = get_repo_slug(request)
252 c.repo_name = get_repo_slug(request)
254 c.backends = BACKENDS.keys()
253 c.backends = BACKENDS.keys()
255 c.unread_notifications = NotificationModel()\
254 c.unread_notifications = NotificationModel()\
256 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
255 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
257 self.cut_off_limit = int(config.get('cut_off_limit'))
256 self.cut_off_limit = int(config.get('cut_off_limit'))
258
257
259 self.sa = meta.Session
258 self.sa = meta.Session
260 self.scm_model = ScmModel(self.sa)
259 self.scm_model = ScmModel(self.sa)
261 self.ip_addr = ''
260 self.ip_addr = ''
262
261
263 def __call__(self, environ, start_response):
262 def __call__(self, environ, start_response):
264 """Invoke the Controller"""
263 """Invoke the Controller"""
265 # WSGIController.__call__ dispatches to the Controller method
264 # WSGIController.__call__ dispatches to the Controller method
266 # the request is routed to. This routing information is
265 # the request is routed to. This routing information is
267 # available in environ['pylons.routes_dict']
266 # available in environ['pylons.routes_dict']
268 start = time.time()
267 start = time.time()
269 try:
268 try:
270 self.ip_addr = _get_ip_addr(environ)
269 self.ip_addr = _get_ip_addr(environ)
271 # make sure that we update permissions each time we call controller
270 # make sure that we update permissions each time we call controller
272 api_key = request.GET.get('api_key')
271 api_key = request.GET.get('api_key')
273 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
272 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
274 user_id = cookie_store.get('user_id', None)
273 user_id = cookie_store.get('user_id', None)
275 username = get_container_username(environ, config)
274 username = get_container_username(environ, config)
276 auth_user = AuthUser(user_id, api_key, username)
275 auth_user = AuthUser(user_id, api_key, username)
277 request.user = auth_user
276 request.user = auth_user
278 self.rhodecode_user = c.rhodecode_user = auth_user
277 self.rhodecode_user = c.rhodecode_user = auth_user
279 if not self.rhodecode_user.is_authenticated and \
278 if not self.rhodecode_user.is_authenticated and \
280 self.rhodecode_user.user_id is not None:
279 self.rhodecode_user.user_id is not None:
281 self.rhodecode_user.set_authenticated(
280 self.rhodecode_user.set_authenticated(
282 cookie_store.get('is_authenticated')
281 cookie_store.get('is_authenticated')
283 )
282 )
284 log.info('IP: %s User: %s accessed %s' % (
283 log.info('IP: %s User: %s accessed %s' % (
285 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
284 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
286 )
285 )
287 return WSGIController.__call__(self, environ, start_response)
286 return WSGIController.__call__(self, environ, start_response)
288 finally:
287 finally:
289 log.info('IP: %s Request to %s time: %.3fs' % (
288 log.info('IP: %s Request to %s time: %.3fs' % (
290 _get_ip_addr(environ),
289 _get_ip_addr(environ),
291 safe_unicode(_get_access_path(environ)), time.time() - start)
290 safe_unicode(_get_access_path(environ)), time.time() - start)
292 )
291 )
293 meta.Session.remove()
292 meta.Session.remove()
294
293
295
294
296 class BaseRepoController(BaseController):
295 class BaseRepoController(BaseController):
297 """
296 """
298 Base class for controllers responsible for loading all needed data for
297 Base class for controllers responsible for loading all needed data for
299 repository loaded items are
298 repository loaded items are
300
299
301 c.rhodecode_repo: instance of scm repository
300 c.rhodecode_repo: instance of scm repository
302 c.rhodecode_db_repo: instance of db
301 c.rhodecode_db_repo: instance of db
303 c.repository_followers: number of followers
302 c.repository_followers: number of followers
304 c.repository_forks: number of forks
303 c.repository_forks: number of forks
305 """
304 """
306
305
307 def __before__(self):
306 def __before__(self):
308 super(BaseRepoController, self).__before__()
307 super(BaseRepoController, self).__before__()
309 if c.repo_name:
308 if c.repo_name:
310
309
311 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
310 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
312 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
311 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
313 # update last change according to VCS data
312 # update last change according to VCS data
314 dbr.update_last_change(c.rhodecode_repo.last_change)
313 dbr.update_last_change(c.rhodecode_repo.last_change)
315 if c.rhodecode_repo is None:
314 if c.rhodecode_repo is None:
316 log.error('%s this repository is present in database but it '
315 log.error('%s this repository is present in database but it '
317 'cannot be created as an scm instance', c.repo_name)
316 'cannot be created as an scm instance', c.repo_name)
318
317
319 redirect(url('home'))
318 redirect(url('home'))
320
319
321 # some globals counter for menu
320 # some globals counter for menu
322 c.repository_followers = self.scm_model.get_followers(dbr)
321 c.repository_followers = self.scm_model.get_followers(dbr)
323 c.repository_forks = self.scm_model.get_forks(dbr)
322 c.repository_forks = self.scm_model.get_forks(dbr)
324 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
323 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
@@ -1,1109 +1,1107
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13 import urlparse
14
14
15 from datetime import datetime
15 from datetime import datetime
16 from pygments.formatters.html import HtmlFormatter
16 from pygments.formatters.html import HtmlFormatter
17 from pygments import highlight as code_highlight
17 from pygments import highlight as code_highlight
18 from pylons import url, request, config
18 from pylons import url, request, config
19 from pylons.i18n.translation import _, ungettext
19 from pylons.i18n.translation import _, ungettext
20 from hashlib import md5
20 from hashlib import md5
21
21
22 from webhelpers.html import literal, HTML, escape
22 from webhelpers.html import literal, HTML, escape
23 from webhelpers.html.tools import *
23 from webhelpers.html.tools import *
24 from webhelpers.html.builder import make_tag
24 from webhelpers.html.builder import make_tag
25 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
25 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 end_form, file, form, hidden, image, javascript_link, link_to, \
26 end_form, file, form, hidden, image, javascript_link, link_to, \
27 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
27 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 submit, text, password, textarea, title, ul, xml_declaration, radio
28 submit, text, password, textarea, title, ul, xml_declaration, radio
29 from webhelpers.html.tools import auto_link, button_to, highlight, \
29 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
30 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 from webhelpers.number import format_byte_size, format_bit_size
31 from webhelpers.number import format_byte_size, format_bit_size
32 from webhelpers.pylonslib import Flash as _Flash
32 from webhelpers.pylonslib import Flash as _Flash
33 from webhelpers.pylonslib.secure_form import secure_form
33 from webhelpers.pylonslib.secure_form import secure_form
34 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
34 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
35 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 replace_whitespace, urlify, truncate, wrap_paragraphs
36 replace_whitespace, urlify, truncate, wrap_paragraphs
37 from webhelpers.date import time_ago_in_words
37 from webhelpers.date import time_ago_in_words
38 from webhelpers.paginate import Page
38 from webhelpers.paginate import Page
39 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
39 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 convert_boolean_attrs, NotGiven, _make_safe_id_component
40 convert_boolean_attrs, NotGiven, _make_safe_id_component
41
41
42 from rhodecode.lib.annotate import annotate_highlight
42 from rhodecode.lib.annotate import annotate_highlight
43 from rhodecode.lib.utils import repo_name_slug
43 from rhodecode.lib.utils import repo_name_slug
44 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
44 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
45 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
46 from rhodecode.lib.markup_renderer import MarkupRenderer
46 from rhodecode.lib.markup_renderer import MarkupRenderer
47 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
47 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
48 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.lib.vcs.backends.base import BaseChangeset
49 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
49 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
50 from rhodecode.model.changeset_status import ChangesetStatusModel
50 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.db import URL_SEP, Permission
51 from rhodecode.model.db import URL_SEP, Permission
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 html_escape_table = {
56 html_escape_table = {
57 "&": "&amp;",
57 "&": "&amp;",
58 '"': "&quot;",
58 '"': "&quot;",
59 "'": "&apos;",
59 "'": "&apos;",
60 ">": "&gt;",
60 ">": "&gt;",
61 "<": "&lt;",
61 "<": "&lt;",
62 }
62 }
63
63
64
64
65 def html_escape(text):
65 def html_escape(text):
66 """Produce entities within text."""
66 """Produce entities within text."""
67 return "".join(html_escape_table.get(c,c) for c in text)
67 return "".join(html_escape_table.get(c, c) for c in text)
68
68
69
69
70 def shorter(text, size=20):
70 def shorter(text, size=20):
71 postfix = '...'
71 postfix = '...'
72 if len(text) > size:
72 if len(text) > size:
73 return text[:size - len(postfix)] + postfix
73 return text[:size - len(postfix)] + postfix
74 return text
74 return text
75
75
76
76
77 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
77 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
78 """
78 """
79 Reset button
79 Reset button
80 """
80 """
81 _set_input_attrs(attrs, type, name, value)
81 _set_input_attrs(attrs, type, name, value)
82 _set_id_attr(attrs, id, name)
82 _set_id_attr(attrs, id, name)
83 convert_boolean_attrs(attrs, ["disabled"])
83 convert_boolean_attrs(attrs, ["disabled"])
84 return HTML.input(**attrs)
84 return HTML.input(**attrs)
85
85
86 reset = _reset
86 reset = _reset
87 safeid = _make_safe_id_component
87 safeid = _make_safe_id_component
88
88
89
89
90 def FID(raw_id, path):
90 def FID(raw_id, path):
91 """
91 """
92 Creates a uniqe ID for filenode based on it's hash of path and revision
92 Creates a uniqe ID for filenode based on it's hash of path and revision
93 it's safe to use in urls
93 it's safe to use in urls
94
94
95 :param raw_id:
95 :param raw_id:
96 :param path:
96 :param path:
97 """
97 """
98
98
99 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
99 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
100
100
101
101
102 def get_token():
102 def get_token():
103 """Return the current authentication token, creating one if one doesn't
103 """Return the current authentication token, creating one if one doesn't
104 already exist.
104 already exist.
105 """
105 """
106 token_key = "_authentication_token"
106 token_key = "_authentication_token"
107 from pylons import session
107 from pylons import session
108 if not token_key in session:
108 if not token_key in session:
109 try:
109 try:
110 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
110 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
111 except AttributeError: # Python < 2.4
111 except AttributeError: # Python < 2.4
112 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
112 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
113 session[token_key] = token
113 session[token_key] = token
114 if hasattr(session, 'save'):
114 if hasattr(session, 'save'):
115 session.save()
115 session.save()
116 return session[token_key]
116 return session[token_key]
117
117
118
118
119 class _GetError(object):
119 class _GetError(object):
120 """Get error from form_errors, and represent it as span wrapped error
120 """Get error from form_errors, and represent it as span wrapped error
121 message
121 message
122
122
123 :param field_name: field to fetch errors for
123 :param field_name: field to fetch errors for
124 :param form_errors: form errors dict
124 :param form_errors: form errors dict
125 """
125 """
126
126
127 def __call__(self, field_name, form_errors):
127 def __call__(self, field_name, form_errors):
128 tmpl = """<span class="error_msg">%s</span>"""
128 tmpl = """<span class="error_msg">%s</span>"""
129 if form_errors and field_name in form_errors:
129 if form_errors and field_name in form_errors:
130 return literal(tmpl % form_errors.get(field_name))
130 return literal(tmpl % form_errors.get(field_name))
131
131
132 get_error = _GetError()
132 get_error = _GetError()
133
133
134
134
135 class _ToolTip(object):
135 class _ToolTip(object):
136
136
137 def __call__(self, tooltip_title, trim_at=50):
137 def __call__(self, tooltip_title, trim_at=50):
138 """
138 """
139 Special function just to wrap our text into nice formatted
139 Special function just to wrap our text into nice formatted
140 autowrapped text
140 autowrapped text
141
141
142 :param tooltip_title:
142 :param tooltip_title:
143 """
143 """
144 tooltip_title = escape(tooltip_title)
144 tooltip_title = escape(tooltip_title)
145 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
145 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
146 return tooltip_title
146 return tooltip_title
147 tooltip = _ToolTip()
147 tooltip = _ToolTip()
148
148
149
149
150 class _FilesBreadCrumbs(object):
150 class _FilesBreadCrumbs(object):
151
151
152 def __call__(self, repo_name, rev, paths):
152 def __call__(self, repo_name, rev, paths):
153 if isinstance(paths, str):
153 if isinstance(paths, str):
154 paths = safe_unicode(paths)
154 paths = safe_unicode(paths)
155 url_l = [link_to(repo_name, url('files_home',
155 url_l = [link_to(repo_name, url('files_home',
156 repo_name=repo_name,
156 repo_name=repo_name,
157 revision=rev, f_path=''),
157 revision=rev, f_path=''),
158 class_='ypjax-link')]
158 class_='ypjax-link')]
159 paths_l = paths.split('/')
159 paths_l = paths.split('/')
160 for cnt, p in enumerate(paths_l):
160 for cnt, p in enumerate(paths_l):
161 if p != '':
161 if p != '':
162 url_l.append(link_to(p,
162 url_l.append(link_to(p,
163 url('files_home',
163 url('files_home',
164 repo_name=repo_name,
164 repo_name=repo_name,
165 revision=rev,
165 revision=rev,
166 f_path='/'.join(paths_l[:cnt + 1])
166 f_path='/'.join(paths_l[:cnt + 1])
167 ),
167 ),
168 class_='ypjax-link'
168 class_='ypjax-link'
169 )
169 )
170 )
170 )
171
171
172 return literal('/'.join(url_l))
172 return literal('/'.join(url_l))
173
173
174 files_breadcrumbs = _FilesBreadCrumbs()
174 files_breadcrumbs = _FilesBreadCrumbs()
175
175
176
176
177 class CodeHtmlFormatter(HtmlFormatter):
177 class CodeHtmlFormatter(HtmlFormatter):
178 """
178 """
179 My code Html Formatter for source codes
179 My code Html Formatter for source codes
180 """
180 """
181
181
182 def wrap(self, source, outfile):
182 def wrap(self, source, outfile):
183 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
183 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
184
184
185 def _wrap_code(self, source):
185 def _wrap_code(self, source):
186 for cnt, it in enumerate(source):
186 for cnt, it in enumerate(source):
187 i, t = it
187 i, t = it
188 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
188 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
189 yield i, t
189 yield i, t
190
190
191 def _wrap_tablelinenos(self, inner):
191 def _wrap_tablelinenos(self, inner):
192 dummyoutfile = StringIO.StringIO()
192 dummyoutfile = StringIO.StringIO()
193 lncount = 0
193 lncount = 0
194 for t, line in inner:
194 for t, line in inner:
195 if t:
195 if t:
196 lncount += 1
196 lncount += 1
197 dummyoutfile.write(line)
197 dummyoutfile.write(line)
198
198
199 fl = self.linenostart
199 fl = self.linenostart
200 mw = len(str(lncount + fl - 1))
200 mw = len(str(lncount + fl - 1))
201 sp = self.linenospecial
201 sp = self.linenospecial
202 st = self.linenostep
202 st = self.linenostep
203 la = self.lineanchors
203 la = self.lineanchors
204 aln = self.anchorlinenos
204 aln = self.anchorlinenos
205 nocls = self.noclasses
205 nocls = self.noclasses
206 if sp:
206 if sp:
207 lines = []
207 lines = []
208
208
209 for i in range(fl, fl + lncount):
209 for i in range(fl, fl + lncount):
210 if i % st == 0:
210 if i % st == 0:
211 if i % sp == 0:
211 if i % sp == 0:
212 if aln:
212 if aln:
213 lines.append('<a href="#%s%d" class="special">%*d</a>' %
213 lines.append('<a href="#%s%d" class="special">%*d</a>' %
214 (la, i, mw, i))
214 (la, i, mw, i))
215 else:
215 else:
216 lines.append('<span class="special">%*d</span>' % (mw, i))
216 lines.append('<span class="special">%*d</span>' % (mw, i))
217 else:
217 else:
218 if aln:
218 if aln:
219 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
219 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
220 else:
220 else:
221 lines.append('%*d' % (mw, i))
221 lines.append('%*d' % (mw, i))
222 else:
222 else:
223 lines.append('')
223 lines.append('')
224 ls = '\n'.join(lines)
224 ls = '\n'.join(lines)
225 else:
225 else:
226 lines = []
226 lines = []
227 for i in range(fl, fl + lncount):
227 for i in range(fl, fl + lncount):
228 if i % st == 0:
228 if i % st == 0:
229 if aln:
229 if aln:
230 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
230 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
231 else:
231 else:
232 lines.append('%*d' % (mw, i))
232 lines.append('%*d' % (mw, i))
233 else:
233 else:
234 lines.append('')
234 lines.append('')
235 ls = '\n'.join(lines)
235 ls = '\n'.join(lines)
236
236
237 # in case you wonder about the seemingly redundant <div> here: since the
237 # in case you wonder about the seemingly redundant <div> here: since the
238 # content in the other cell also is wrapped in a div, some browsers in
238 # content in the other cell also is wrapped in a div, some browsers in
239 # some configurations seem to mess up the formatting...
239 # some configurations seem to mess up the formatting...
240 if nocls:
240 if nocls:
241 yield 0, ('<table class="%stable">' % self.cssclass +
241 yield 0, ('<table class="%stable">' % self.cssclass +
242 '<tr><td><div class="linenodiv" '
242 '<tr><td><div class="linenodiv" '
243 'style="background-color: #f0f0f0; padding-right: 10px">'
243 'style="background-color: #f0f0f0; padding-right: 10px">'
244 '<pre style="line-height: 125%">' +
244 '<pre style="line-height: 125%">' +
245 ls + '</pre></div></td><td id="hlcode" class="code">')
245 ls + '</pre></div></td><td id="hlcode" class="code">')
246 else:
246 else:
247 yield 0, ('<table class="%stable">' % self.cssclass +
247 yield 0, ('<table class="%stable">' % self.cssclass +
248 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
248 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
249 ls + '</pre></div></td><td id="hlcode" class="code">')
249 ls + '</pre></div></td><td id="hlcode" class="code">')
250 yield 0, dummyoutfile.getvalue()
250 yield 0, dummyoutfile.getvalue()
251 yield 0, '</td></tr></table>'
251 yield 0, '</td></tr></table>'
252
252
253
253
254 def pygmentize(filenode, **kwargs):
254 def pygmentize(filenode, **kwargs):
255 """pygmentize function using pygments
255 """pygmentize function using pygments
256
256
257 :param filenode:
257 :param filenode:
258 """
258 """
259
259
260 return literal(code_highlight(filenode.content,
260 return literal(code_highlight(filenode.content,
261 filenode.lexer, CodeHtmlFormatter(**kwargs)))
261 filenode.lexer, CodeHtmlFormatter(**kwargs)))
262
262
263
263
264 def pygmentize_annotation(repo_name, filenode, **kwargs):
264 def pygmentize_annotation(repo_name, filenode, **kwargs):
265 """
265 """
266 pygmentize function for annotation
266 pygmentize function for annotation
267
267
268 :param filenode:
268 :param filenode:
269 """
269 """
270
270
271 color_dict = {}
271 color_dict = {}
272
272
273 def gen_color(n=10000):
273 def gen_color(n=10000):
274 """generator for getting n of evenly distributed colors using
274 """generator for getting n of evenly distributed colors using
275 hsv color and golden ratio. It always return same order of colors
275 hsv color and golden ratio. It always return same order of colors
276
276
277 :returns: RGB tuple
277 :returns: RGB tuple
278 """
278 """
279
279
280 def hsv_to_rgb(h, s, v):
280 def hsv_to_rgb(h, s, v):
281 if s == 0.0:
281 if s == 0.0:
282 return v, v, v
282 return v, v, v
283 i = int(h * 6.0) # XXX assume int() truncates!
283 i = int(h * 6.0) # XXX assume int() truncates!
284 f = (h * 6.0) - i
284 f = (h * 6.0) - i
285 p = v * (1.0 - s)
285 p = v * (1.0 - s)
286 q = v * (1.0 - s * f)
286 q = v * (1.0 - s * f)
287 t = v * (1.0 - s * (1.0 - f))
287 t = v * (1.0 - s * (1.0 - f))
288 i = i % 6
288 i = i % 6
289 if i == 0:
289 if i == 0:
290 return v, t, p
290 return v, t, p
291 if i == 1:
291 if i == 1:
292 return q, v, p
292 return q, v, p
293 if i == 2:
293 if i == 2:
294 return p, v, t
294 return p, v, t
295 if i == 3:
295 if i == 3:
296 return p, q, v
296 return p, q, v
297 if i == 4:
297 if i == 4:
298 return t, p, v
298 return t, p, v
299 if i == 5:
299 if i == 5:
300 return v, p, q
300 return v, p, q
301
301
302 golden_ratio = 0.618033988749895
302 golden_ratio = 0.618033988749895
303 h = 0.22717784590367374
303 h = 0.22717784590367374
304
304
305 for _ in xrange(n):
305 for _ in xrange(n):
306 h += golden_ratio
306 h += golden_ratio
307 h %= 1
307 h %= 1
308 HSV_tuple = [h, 0.95, 0.95]
308 HSV_tuple = [h, 0.95, 0.95]
309 RGB_tuple = hsv_to_rgb(*HSV_tuple)
309 RGB_tuple = hsv_to_rgb(*HSV_tuple)
310 yield map(lambda x: str(int(x * 256)), RGB_tuple)
310 yield map(lambda x: str(int(x * 256)), RGB_tuple)
311
311
312 cgenerator = gen_color()
312 cgenerator = gen_color()
313
313
314 def get_color_string(cs):
314 def get_color_string(cs):
315 if cs in color_dict:
315 if cs in color_dict:
316 col = color_dict[cs]
316 col = color_dict[cs]
317 else:
317 else:
318 col = color_dict[cs] = cgenerator.next()
318 col = color_dict[cs] = cgenerator.next()
319 return "color: rgb(%s)! important;" % (', '.join(col))
319 return "color: rgb(%s)! important;" % (', '.join(col))
320
320
321 def url_func(repo_name):
321 def url_func(repo_name):
322
322
323 def _url_func(changeset):
323 def _url_func(changeset):
324 author = changeset.author
324 author = changeset.author
325 date = changeset.date
325 date = changeset.date
326 message = tooltip(changeset.message)
326 message = tooltip(changeset.message)
327
327
328 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
328 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
329 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
329 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
330 "</b> %s<br/></div>")
330 "</b> %s<br/></div>")
331
331
332 tooltip_html = tooltip_html % (author, date, message)
332 tooltip_html = tooltip_html % (author, date, message)
333 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
333 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
334 short_id(changeset.raw_id))
334 short_id(changeset.raw_id))
335 uri = link_to(
335 uri = link_to(
336 lnk_format,
336 lnk_format,
337 url('changeset_home', repo_name=repo_name,
337 url('changeset_home', repo_name=repo_name,
338 revision=changeset.raw_id),
338 revision=changeset.raw_id),
339 style=get_color_string(changeset.raw_id),
339 style=get_color_string(changeset.raw_id),
340 class_='tooltip',
340 class_='tooltip',
341 title=tooltip_html
341 title=tooltip_html
342 )
342 )
343
343
344 uri += '\n'
344 uri += '\n'
345 return uri
345 return uri
346 return _url_func
346 return _url_func
347
347
348 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
348 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
349
349
350
350
351 def is_following_repo(repo_name, user_id):
351 def is_following_repo(repo_name, user_id):
352 from rhodecode.model.scm import ScmModel
352 from rhodecode.model.scm import ScmModel
353 return ScmModel().is_following_repo(repo_name, user_id)
353 return ScmModel().is_following_repo(repo_name, user_id)
354
354
355 flash = _Flash()
355 flash = _Flash()
356
356
357 #==============================================================================
357 #==============================================================================
358 # SCM FILTERS available via h.
358 # SCM FILTERS available via h.
359 #==============================================================================
359 #==============================================================================
360 from rhodecode.lib.vcs.utils import author_name, author_email
360 from rhodecode.lib.vcs.utils import author_name, author_email
361 from rhodecode.lib.utils2 import credentials_filter, age as _age
361 from rhodecode.lib.utils2 import credentials_filter, age as _age
362 from rhodecode.model.db import User, ChangesetStatus
362 from rhodecode.model.db import User, ChangesetStatus
363
363
364 age = lambda x: _age(x)
364 age = lambda x: _age(x)
365 capitalize = lambda x: x.capitalize()
365 capitalize = lambda x: x.capitalize()
366 email = author_email
366 email = author_email
367 short_id = lambda x: x[:12]
367 short_id = lambda x: x[:12]
368 hide_credentials = lambda x: ''.join(credentials_filter(x))
368 hide_credentials = lambda x: ''.join(credentials_filter(x))
369
369
370
370
371 def fmt_date(date):
371 def fmt_date(date):
372 if date:
372 if date:
373 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
373 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
374 return date.strftime(_fmt).decode('utf8')
374 return date.strftime(_fmt).decode('utf8')
375
375
376 return ""
376 return ""
377
377
378
378
379 def is_git(repository):
379 def is_git(repository):
380 if hasattr(repository, 'alias'):
380 if hasattr(repository, 'alias'):
381 _type = repository.alias
381 _type = repository.alias
382 elif hasattr(repository, 'repo_type'):
382 elif hasattr(repository, 'repo_type'):
383 _type = repository.repo_type
383 _type = repository.repo_type
384 else:
384 else:
385 _type = repository
385 _type = repository
386 return _type == 'git'
386 return _type == 'git'
387
387
388
388
389 def is_hg(repository):
389 def is_hg(repository):
390 if hasattr(repository, 'alias'):
390 if hasattr(repository, 'alias'):
391 _type = repository.alias
391 _type = repository.alias
392 elif hasattr(repository, 'repo_type'):
392 elif hasattr(repository, 'repo_type'):
393 _type = repository.repo_type
393 _type = repository.repo_type
394 else:
394 else:
395 _type = repository
395 _type = repository
396 return _type == 'hg'
396 return _type == 'hg'
397
397
398
398
399 def email_or_none(author):
399 def email_or_none(author):
400 # extract email from the commit string
400 # extract email from the commit string
401 _email = email(author)
401 _email = email(author)
402 if _email != '':
402 if _email != '':
403 # check it against RhodeCode database, and use the MAIN email for this
403 # check it against RhodeCode database, and use the MAIN email for this
404 # user
404 # user
405 user = User.get_by_email(_email, case_insensitive=True, cache=True)
405 user = User.get_by_email(_email, case_insensitive=True, cache=True)
406 if user is not None:
406 if user is not None:
407 return user.email
407 return user.email
408 return _email
408 return _email
409
409
410 # See if it contains a username we can get an email from
410 # See if it contains a username we can get an email from
411 user = User.get_by_username(author_name(author), case_insensitive=True,
411 user = User.get_by_username(author_name(author), case_insensitive=True,
412 cache=True)
412 cache=True)
413 if user is not None:
413 if user is not None:
414 return user.email
414 return user.email
415
415
416 # No valid email, not a valid user in the system, none!
416 # No valid email, not a valid user in the system, none!
417 return None
417 return None
418
418
419
419
420 def person(author, show_attr="username_and_name"):
420 def person(author, show_attr="username_and_name"):
421 # attr to return from fetched user
421 # attr to return from fetched user
422 person_getter = lambda usr: getattr(usr, show_attr)
422 person_getter = lambda usr: getattr(usr, show_attr)
423
423
424 # Valid email in the attribute passed, see if they're in the system
424 # Valid email in the attribute passed, see if they're in the system
425 _email = email(author)
425 _email = email(author)
426 if _email != '':
426 if _email != '':
427 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 user = User.get_by_email(_email, case_insensitive=True, cache=True)
428 if user is not None:
428 if user is not None:
429 return person_getter(user)
429 return person_getter(user)
430 return _email
430 return _email
431
431
432 # Maybe it's a username?
432 # Maybe it's a username?
433 _author = author_name(author)
433 _author = author_name(author)
434 user = User.get_by_username(_author, case_insensitive=True,
434 user = User.get_by_username(_author, case_insensitive=True,
435 cache=True)
435 cache=True)
436 if user is not None:
436 if user is not None:
437 return person_getter(user)
437 return person_getter(user)
438
438
439 # Still nothing? Just pass back the author name then
439 # Still nothing? Just pass back the author name then
440 return _author
440 return _author
441
441
442
442
443 def person_by_id(id_, show_attr="username_and_name"):
443 def person_by_id(id_, show_attr="username_and_name"):
444 # attr to return from fetched user
444 # attr to return from fetched user
445 person_getter = lambda usr: getattr(usr, show_attr)
445 person_getter = lambda usr: getattr(usr, show_attr)
446
446
447 #maybe it's an ID ?
447 #maybe it's an ID ?
448 if str(id_).isdigit() or isinstance(id_, int):
448 if str(id_).isdigit() or isinstance(id_, int):
449 id_ = int(id_)
449 id_ = int(id_)
450 user = User.get(id_)
450 user = User.get(id_)
451 if user is not None:
451 if user is not None:
452 return person_getter(user)
452 return person_getter(user)
453 return id_
453 return id_
454
454
455
455
456 def desc_stylize(value):
456 def desc_stylize(value):
457 """
457 """
458 converts tags from value into html equivalent
458 converts tags from value into html equivalent
459
459
460 :param value:
460 :param value:
461 """
461 """
462 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
462 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
463 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
463 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
464 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
464 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
465 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
465 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
466 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
466 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
467 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
467 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
468 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
468 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
469 '<div class="metatag" tag="lang">\\2</div>', value)
469 '<div class="metatag" tag="lang">\\2</div>', value)
470 value = re.sub(r'\[([a-z]+)\]',
470 value = re.sub(r'\[([a-z]+)\]',
471 '<div class="metatag" tag="\\1">\\1</div>', value)
471 '<div class="metatag" tag="\\1">\\1</div>', value)
472
472
473 return value
473 return value
474
474
475
475
476 def bool2icon(value):
476 def bool2icon(value):
477 """Returns True/False values represented as small html image of true/false
477 """Returns True/False values represented as small html image of true/false
478 icons
478 icons
479
479
480 :param value: bool value
480 :param value: bool value
481 """
481 """
482
482
483 if value is True:
483 if value is True:
484 return HTML.tag('img', src=url("/images/icons/accept.png"),
484 return HTML.tag('img', src=url("/images/icons/accept.png"),
485 alt=_('True'))
485 alt=_('True'))
486
486
487 if value is False:
487 if value is False:
488 return HTML.tag('img', src=url("/images/icons/cancel.png"),
488 return HTML.tag('img', src=url("/images/icons/cancel.png"),
489 alt=_('False'))
489 alt=_('False'))
490
490
491 return value
491 return value
492
492
493
493
494 def action_parser(user_log, feed=False, parse_cs=True):
494 def action_parser(user_log, feed=False, parse_cs=False):
495 """
495 """
496 This helper will action_map the specified string action into translated
496 This helper will action_map the specified string action into translated
497 fancy names with icons and links
497 fancy names with icons and links
498
498
499 :param user_log: user log instance
499 :param user_log: user log instance
500 :param feed: use output for feeds (no html and fancy icons)
500 :param feed: use output for feeds (no html and fancy icons)
501 :param parse_cs: parse Changesets into VCS instances
501 :param parse_cs: parse Changesets into VCS instances
502 """
502 """
503 from pylons import tmpl_context as c
503
504 if c.visual.lightweight_journal:
505 parse_cs = False
506 action = user_log.action
504 action = user_log.action
507 action_params = ' '
505 action_params = ' '
508
506
509 x = action.split(':')
507 x = action.split(':')
510
508
511 if len(x) > 1:
509 if len(x) > 1:
512 action, action_params = x
510 action, action_params = x
513
511
514 def get_cs_links():
512 def get_cs_links():
515 revs_limit = 3 # display this amount always
513 revs_limit = 3 # display this amount always
516 revs_top_limit = 50 # show upto this amount of changesets hidden
514 revs_top_limit = 50 # show upto this amount of changesets hidden
517 revs_ids = action_params.split(',')
515 revs_ids = action_params.split(',')
518 deleted = user_log.repository is None
516 deleted = user_log.repository is None
519 if deleted:
517 if deleted:
520 return ','.join(revs_ids)
518 return ','.join(revs_ids)
521
519
522 repo_name = user_log.repository.repo_name
520 repo_name = user_log.repository.repo_name
523
521
524 def lnk(rev, repo_name):
522 def lnk(rev, repo_name):
525
526 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
523 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
527 if rev.revision:
524 lbl = '%s' % (rev.short_id[:8])
528 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
529 else:
530 lbl = '%s' % (rev.short_id)
531 _url = url('changeset_home', repo_name=repo_name,
525 _url = url('changeset_home', repo_name=repo_name,
532 revision=rev.raw_id)
526 revision=rev.raw_id)
533 title = tooltip(rev.message) if parse_cs else ''
527 title = tooltip(rev.message)
534 else:
528 else:
529 ## changeset cannot be found/striped/removed etc.
535 lbl = ('%s' % rev)[:12]
530 lbl = ('%s' % rev)[:12]
536 _url = '#'
531 _url = '#'
537 title = _('Changeset not found')
532 title = _('Changeset not found')
538
533 if parse_cs:
539 return link_to(lbl, _url, title=title,
534 return link_to(lbl, _url, title=title, class_='tooltip')
540 class_='tooltip' if parse_cs else '',)
535 return link_to(lbl, _url, raw_id=rev.raw_id, class_='journal-cs')
541
536
542 revs = []
537 revs = []
543 if len(filter(lambda v: v != '', revs_ids)) > 0:
538 if len(filter(lambda v: v != '', revs_ids)) > 0:
544 if parse_cs:
539 repo = None
545 repo = user_log.repository.scm_instance
546 for rev in revs_ids[:revs_top_limit]:
540 for rev in revs_ids[:revs_top_limit]:
541 # we want parsed changesets, or new log store format is bad
547 if parse_cs:
542 if parse_cs:
548 try:
543 try:
549 rev = repo.get_changeset(rev)
544 if repo is None:
550 revs.append(rev)
545 repo = user_log.repository.scm_instance
546 _rev = repo.get_changeset(rev)
547 revs.append(_rev)
551 except ChangesetDoesNotExistError:
548 except ChangesetDoesNotExistError:
552 log.error('cannot find revision %s in this repo' % rev)
549 log.error('cannot find revision %s in this repo' % rev)
553 revs.append(rev)
550 revs.append(rev)
554 continue
551 continue
555 else:
552 else:
556 rev = AttributeDict({
553 _rev = AttributeDict({
557 'short_id': rev[:12],
554 'short_id': rev[:12],
558 'raw_id': rev,
555 'raw_id': rev,
556 'message': '',
559 })
557 })
560 revs.append(rev)
558 revs.append(_rev)
561 cs_links = []
559 cs_links = []
562 cs_links.append(" " + ', '.join(
560 cs_links.append(" " + ', '.join(
563 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
561 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
564 )
562 )
565 )
563 )
566
564
567 compare_view = (
565 compare_view = (
568 ' <div class="compare_view tooltip" title="%s">'
566 ' <div class="compare_view tooltip" title="%s">'
569 '<a href="%s">%s</a> </div>' % (
567 '<a href="%s">%s</a> </div>' % (
570 _('Show all combined changesets %s->%s') % (
568 _('Show all combined changesets %s->%s') % (
571 revs_ids[0][:12], revs_ids[-1][:12]
569 revs_ids[0][:12], revs_ids[-1][:12]
572 ),
570 ),
573 url('changeset_home', repo_name=repo_name,
571 url('changeset_home', repo_name=repo_name,
574 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
572 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
575 ),
573 ),
576 _('compare view')
574 _('compare view')
577 )
575 )
578 )
576 )
579
577
580 # if we have exactly one more than normally displayed
578 # if we have exactly one more than normally displayed
581 # just display it, takes less space than displaying
579 # just display it, takes less space than displaying
582 # "and 1 more revisions"
580 # "and 1 more revisions"
583 if len(revs_ids) == revs_limit + 1:
581 if len(revs_ids) == revs_limit + 1:
584 rev = revs[revs_limit]
582 rev = revs[revs_limit]
585 cs_links.append(", " + lnk(rev, repo_name))
583 cs_links.append(", " + lnk(rev, repo_name))
586
584
587 # hidden-by-default ones
585 # hidden-by-default ones
588 if len(revs_ids) > revs_limit + 1:
586 if len(revs_ids) > revs_limit + 1:
589 uniq_id = revs_ids[0]
587 uniq_id = revs_ids[0]
590 html_tmpl = (
588 html_tmpl = (
591 '<span> %s <a class="show_more" id="_%s" '
589 '<span> %s <a class="show_more" id="_%s" '
592 'href="#more">%s</a> %s</span>'
590 'href="#more">%s</a> %s</span>'
593 )
591 )
594 if not feed:
592 if not feed:
595 cs_links.append(html_tmpl % (
593 cs_links.append(html_tmpl % (
596 _('and'),
594 _('and'),
597 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
595 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
598 _('revisions')
596 _('revisions')
599 )
597 )
600 )
598 )
601
599
602 if not feed:
600 if not feed:
603 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
601 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
604 else:
602 else:
605 html_tmpl = '<span id="%s"> %s </span>'
603 html_tmpl = '<span id="%s"> %s </span>'
606
604
607 morelinks = ', '.join(
605 morelinks = ', '.join(
608 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
606 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
609 )
607 )
610
608
611 if len(revs_ids) > revs_top_limit:
609 if len(revs_ids) > revs_top_limit:
612 morelinks += ', ...'
610 morelinks += ', ...'
613
611
614 cs_links.append(html_tmpl % (uniq_id, morelinks))
612 cs_links.append(html_tmpl % (uniq_id, morelinks))
615 if len(revs) > 1:
613 if len(revs) > 1:
616 cs_links.append(compare_view)
614 cs_links.append(compare_view)
617 return ''.join(cs_links)
615 return ''.join(cs_links)
618
616
619 def get_fork_name():
617 def get_fork_name():
620 repo_name = action_params
618 repo_name = action_params
621 _url = url('summary_home', repo_name=repo_name)
619 _url = url('summary_home', repo_name=repo_name)
622 return _('fork name %s') % link_to(action_params, _url)
620 return _('fork name %s') % link_to(action_params, _url)
623
621
624 def get_user_name():
622 def get_user_name():
625 user_name = action_params
623 user_name = action_params
626 return user_name
624 return user_name
627
625
628 def get_users_group():
626 def get_users_group():
629 group_name = action_params
627 group_name = action_params
630 return group_name
628 return group_name
631
629
632 def get_pull_request():
630 def get_pull_request():
633 pull_request_id = action_params
631 pull_request_id = action_params
634 repo_name = user_log.repository.repo_name
632 repo_name = user_log.repository.repo_name
635 return link_to(_('Pull request #%s') % pull_request_id,
633 return link_to(_('Pull request #%s') % pull_request_id,
636 url('pullrequest_show', repo_name=repo_name,
634 url('pullrequest_show', repo_name=repo_name,
637 pull_request_id=pull_request_id))
635 pull_request_id=pull_request_id))
638
636
639 # action : translated str, callback(extractor), icon
637 # action : translated str, callback(extractor), icon
640 action_map = {
638 action_map = {
641 'user_deleted_repo': (_('[deleted] repository'),
639 'user_deleted_repo': (_('[deleted] repository'),
642 None, 'database_delete.png'),
640 None, 'database_delete.png'),
643 'user_created_repo': (_('[created] repository'),
641 'user_created_repo': (_('[created] repository'),
644 None, 'database_add.png'),
642 None, 'database_add.png'),
645 'user_created_fork': (_('[created] repository as fork'),
643 'user_created_fork': (_('[created] repository as fork'),
646 None, 'arrow_divide.png'),
644 None, 'arrow_divide.png'),
647 'user_forked_repo': (_('[forked] repository'),
645 'user_forked_repo': (_('[forked] repository'),
648 get_fork_name, 'arrow_divide.png'),
646 get_fork_name, 'arrow_divide.png'),
649 'user_updated_repo': (_('[updated] repository'),
647 'user_updated_repo': (_('[updated] repository'),
650 None, 'database_edit.png'),
648 None, 'database_edit.png'),
651 'admin_deleted_repo': (_('[delete] repository'),
649 'admin_deleted_repo': (_('[delete] repository'),
652 None, 'database_delete.png'),
650 None, 'database_delete.png'),
653 'admin_created_repo': (_('[created] repository'),
651 'admin_created_repo': (_('[created] repository'),
654 None, 'database_add.png'),
652 None, 'database_add.png'),
655 'admin_forked_repo': (_('[forked] repository'),
653 'admin_forked_repo': (_('[forked] repository'),
656 None, 'arrow_divide.png'),
654 None, 'arrow_divide.png'),
657 'admin_updated_repo': (_('[updated] repository'),
655 'admin_updated_repo': (_('[updated] repository'),
658 None, 'database_edit.png'),
656 None, 'database_edit.png'),
659 'admin_created_user': (_('[created] user'),
657 'admin_created_user': (_('[created] user'),
660 get_user_name, 'user_add.png'),
658 get_user_name, 'user_add.png'),
661 'admin_updated_user': (_('[updated] user'),
659 'admin_updated_user': (_('[updated] user'),
662 get_user_name, 'user_edit.png'),
660 get_user_name, 'user_edit.png'),
663 'admin_created_users_group': (_('[created] users group'),
661 'admin_created_users_group': (_('[created] users group'),
664 get_users_group, 'group_add.png'),
662 get_users_group, 'group_add.png'),
665 'admin_updated_users_group': (_('[updated] users group'),
663 'admin_updated_users_group': (_('[updated] users group'),
666 get_users_group, 'group_edit.png'),
664 get_users_group, 'group_edit.png'),
667 'user_commented_revision': (_('[commented] on revision in repository'),
665 'user_commented_revision': (_('[commented] on revision in repository'),
668 get_cs_links, 'comment_add.png'),
666 get_cs_links, 'comment_add.png'),
669 'user_commented_pull_request': (_('[commented] on pull request for'),
667 'user_commented_pull_request': (_('[commented] on pull request for'),
670 get_pull_request, 'comment_add.png'),
668 get_pull_request, 'comment_add.png'),
671 'user_closed_pull_request': (_('[closed] pull request for'),
669 'user_closed_pull_request': (_('[closed] pull request for'),
672 get_pull_request, 'tick.png'),
670 get_pull_request, 'tick.png'),
673 'push': (_('[pushed] into'),
671 'push': (_('[pushed] into'),
674 get_cs_links, 'script_add.png'),
672 get_cs_links, 'script_add.png'),
675 'push_local': (_('[committed via RhodeCode] into repository'),
673 'push_local': (_('[committed via RhodeCode] into repository'),
676 get_cs_links, 'script_edit.png'),
674 get_cs_links, 'script_edit.png'),
677 'push_remote': (_('[pulled from remote] into repository'),
675 'push_remote': (_('[pulled from remote] into repository'),
678 get_cs_links, 'connect.png'),
676 get_cs_links, 'connect.png'),
679 'pull': (_('[pulled] from'),
677 'pull': (_('[pulled] from'),
680 None, 'down_16.png'),
678 None, 'down_16.png'),
681 'started_following_repo': (_('[started following] repository'),
679 'started_following_repo': (_('[started following] repository'),
682 None, 'heart_add.png'),
680 None, 'heart_add.png'),
683 'stopped_following_repo': (_('[stopped following] repository'),
681 'stopped_following_repo': (_('[stopped following] repository'),
684 None, 'heart_delete.png'),
682 None, 'heart_delete.png'),
685 }
683 }
686
684
687 action_str = action_map.get(action, action)
685 action_str = action_map.get(action, action)
688 if feed:
686 if feed:
689 action = action_str[0].replace('[', '').replace(']', '')
687 action = action_str[0].replace('[', '').replace(']', '')
690 else:
688 else:
691 action = action_str[0]\
689 action = action_str[0]\
692 .replace('[', '<span class="journal_highlight">')\
690 .replace('[', '<span class="journal_highlight">')\
693 .replace(']', '</span>')
691 .replace(']', '</span>')
694
692
695 action_params_func = lambda: ""
693 action_params_func = lambda: ""
696
694
697 if callable(action_str[1]):
695 if callable(action_str[1]):
698 action_params_func = action_str[1]
696 action_params_func = action_str[1]
699
697
700 def action_parser_icon():
698 def action_parser_icon():
701 action = user_log.action
699 action = user_log.action
702 action_params = None
700 action_params = None
703 x = action.split(':')
701 x = action.split(':')
704
702
705 if len(x) > 1:
703 if len(x) > 1:
706 action, action_params = x
704 action, action_params = x
707
705
708 tmpl = """<img src="%s%s" alt="%s"/>"""
706 tmpl = """<img src="%s%s" alt="%s"/>"""
709 ico = action_map.get(action, ['', '', ''])[2]
707 ico = action_map.get(action, ['', '', ''])[2]
710 return literal(tmpl % ((url('/images/icons/')), ico, action))
708 return literal(tmpl % ((url('/images/icons/')), ico, action))
711
709
712 # returned callbacks we need to call to get
710 # returned callbacks we need to call to get
713 return [lambda: literal(action), action_params_func, action_parser_icon]
711 return [lambda: literal(action), action_params_func, action_parser_icon]
714
712
715
713
716
714
717 #==============================================================================
715 #==============================================================================
718 # PERMS
716 # PERMS
719 #==============================================================================
717 #==============================================================================
720 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
718 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
721 HasRepoPermissionAny, HasRepoPermissionAll
719 HasRepoPermissionAny, HasRepoPermissionAll
722
720
723
721
724 #==============================================================================
722 #==============================================================================
725 # GRAVATAR URL
723 # GRAVATAR URL
726 #==============================================================================
724 #==============================================================================
727
725
728 def gravatar_url(email_address, size=30):
726 def gravatar_url(email_address, size=30):
729 from pylons import url ## doh, we need to re-import url to mock it later
727 from pylons import url ## doh, we need to re-import url to mock it later
730 if(str2bool(config['app_conf'].get('use_gravatar')) and
728 if(str2bool(config['app_conf'].get('use_gravatar')) and
731 config['app_conf'].get('alternative_gravatar_url')):
729 config['app_conf'].get('alternative_gravatar_url')):
732 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
730 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
733 parsed_url = urlparse.urlparse(url.current(qualified=True))
731 parsed_url = urlparse.urlparse(url.current(qualified=True))
734 tmpl = tmpl.replace('{email}', email_address)\
732 tmpl = tmpl.replace('{email}', email_address)\
735 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
733 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
736 .replace('{netloc}', parsed_url.netloc)\
734 .replace('{netloc}', parsed_url.netloc)\
737 .replace('{scheme}', parsed_url.scheme)\
735 .replace('{scheme}', parsed_url.scheme)\
738 .replace('{size}', str(size))
736 .replace('{size}', str(size))
739 return tmpl
737 return tmpl
740
738
741 if (not str2bool(config['app_conf'].get('use_gravatar')) or
739 if (not str2bool(config['app_conf'].get('use_gravatar')) or
742 not email_address or email_address == 'anonymous@rhodecode.org'):
740 not email_address or email_address == 'anonymous@rhodecode.org'):
743 f = lambda a, l: min(l, key=lambda x: abs(x - a))
741 f = lambda a, l: min(l, key=lambda x: abs(x - a))
744 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
742 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
745
743
746 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
744 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
747 default = 'identicon'
745 default = 'identicon'
748 baseurl_nossl = "http://www.gravatar.com/avatar/"
746 baseurl_nossl = "http://www.gravatar.com/avatar/"
749 baseurl_ssl = "https://secure.gravatar.com/avatar/"
747 baseurl_ssl = "https://secure.gravatar.com/avatar/"
750 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
748 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
751
749
752 if isinstance(email_address, unicode):
750 if isinstance(email_address, unicode):
753 #hashlib crashes on unicode items
751 #hashlib crashes on unicode items
754 email_address = safe_str(email_address)
752 email_address = safe_str(email_address)
755 # construct the url
753 # construct the url
756 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
754 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
757 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
755 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
758
756
759 return gravatar_url
757 return gravatar_url
760
758
761
759
762 #==============================================================================
760 #==============================================================================
763 # REPO PAGER, PAGER FOR REPOSITORY
761 # REPO PAGER, PAGER FOR REPOSITORY
764 #==============================================================================
762 #==============================================================================
765 class RepoPage(Page):
763 class RepoPage(Page):
766
764
767 def __init__(self, collection, page=1, items_per_page=20,
765 def __init__(self, collection, page=1, items_per_page=20,
768 item_count=None, url=None, **kwargs):
766 item_count=None, url=None, **kwargs):
769
767
770 """Create a "RepoPage" instance. special pager for paging
768 """Create a "RepoPage" instance. special pager for paging
771 repository
769 repository
772 """
770 """
773 self._url_generator = url
771 self._url_generator = url
774
772
775 # Safe the kwargs class-wide so they can be used in the pager() method
773 # Safe the kwargs class-wide so they can be used in the pager() method
776 self.kwargs = kwargs
774 self.kwargs = kwargs
777
775
778 # Save a reference to the collection
776 # Save a reference to the collection
779 self.original_collection = collection
777 self.original_collection = collection
780
778
781 self.collection = collection
779 self.collection = collection
782
780
783 # The self.page is the number of the current page.
781 # The self.page is the number of the current page.
784 # The first page has the number 1!
782 # The first page has the number 1!
785 try:
783 try:
786 self.page = int(page) # make it int() if we get it as a string
784 self.page = int(page) # make it int() if we get it as a string
787 except (ValueError, TypeError):
785 except (ValueError, TypeError):
788 self.page = 1
786 self.page = 1
789
787
790 self.items_per_page = items_per_page
788 self.items_per_page = items_per_page
791
789
792 # Unless the user tells us how many items the collections has
790 # Unless the user tells us how many items the collections has
793 # we calculate that ourselves.
791 # we calculate that ourselves.
794 if item_count is not None:
792 if item_count is not None:
795 self.item_count = item_count
793 self.item_count = item_count
796 else:
794 else:
797 self.item_count = len(self.collection)
795 self.item_count = len(self.collection)
798
796
799 # Compute the number of the first and last available page
797 # Compute the number of the first and last available page
800 if self.item_count > 0:
798 if self.item_count > 0:
801 self.first_page = 1
799 self.first_page = 1
802 self.page_count = int(math.ceil(float(self.item_count) /
800 self.page_count = int(math.ceil(float(self.item_count) /
803 self.items_per_page))
801 self.items_per_page))
804 self.last_page = self.first_page + self.page_count - 1
802 self.last_page = self.first_page + self.page_count - 1
805
803
806 # Make sure that the requested page number is the range of
804 # Make sure that the requested page number is the range of
807 # valid pages
805 # valid pages
808 if self.page > self.last_page:
806 if self.page > self.last_page:
809 self.page = self.last_page
807 self.page = self.last_page
810 elif self.page < self.first_page:
808 elif self.page < self.first_page:
811 self.page = self.first_page
809 self.page = self.first_page
812
810
813 # Note: the number of items on this page can be less than
811 # Note: the number of items on this page can be less than
814 # items_per_page if the last page is not full
812 # items_per_page if the last page is not full
815 self.first_item = max(0, (self.item_count) - (self.page *
813 self.first_item = max(0, (self.item_count) - (self.page *
816 items_per_page))
814 items_per_page))
817 self.last_item = ((self.item_count - 1) - items_per_page *
815 self.last_item = ((self.item_count - 1) - items_per_page *
818 (self.page - 1))
816 (self.page - 1))
819
817
820 self.items = list(self.collection[self.first_item:self.last_item + 1])
818 self.items = list(self.collection[self.first_item:self.last_item + 1])
821
819
822 # Links to previous and next page
820 # Links to previous and next page
823 if self.page > self.first_page:
821 if self.page > self.first_page:
824 self.previous_page = self.page - 1
822 self.previous_page = self.page - 1
825 else:
823 else:
826 self.previous_page = None
824 self.previous_page = None
827
825
828 if self.page < self.last_page:
826 if self.page < self.last_page:
829 self.next_page = self.page + 1
827 self.next_page = self.page + 1
830 else:
828 else:
831 self.next_page = None
829 self.next_page = None
832
830
833 # No items available
831 # No items available
834 else:
832 else:
835 self.first_page = None
833 self.first_page = None
836 self.page_count = 0
834 self.page_count = 0
837 self.last_page = None
835 self.last_page = None
838 self.first_item = None
836 self.first_item = None
839 self.last_item = None
837 self.last_item = None
840 self.previous_page = None
838 self.previous_page = None
841 self.next_page = None
839 self.next_page = None
842 self.items = []
840 self.items = []
843
841
844 # This is a subclass of the 'list' type. Initialise the list now.
842 # This is a subclass of the 'list' type. Initialise the list now.
845 list.__init__(self, reversed(self.items))
843 list.__init__(self, reversed(self.items))
846
844
847
845
848 def changed_tooltip(nodes):
846 def changed_tooltip(nodes):
849 """
847 """
850 Generates a html string for changed nodes in changeset page.
848 Generates a html string for changed nodes in changeset page.
851 It limits the output to 30 entries
849 It limits the output to 30 entries
852
850
853 :param nodes: LazyNodesGenerator
851 :param nodes: LazyNodesGenerator
854 """
852 """
855 if nodes:
853 if nodes:
856 pref = ': <br/> '
854 pref = ': <br/> '
857 suf = ''
855 suf = ''
858 if len(nodes) > 30:
856 if len(nodes) > 30:
859 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
857 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
860 return literal(pref + '<br/> '.join([safe_unicode(x.path)
858 return literal(pref + '<br/> '.join([safe_unicode(x.path)
861 for x in nodes[:30]]) + suf)
859 for x in nodes[:30]]) + suf)
862 else:
860 else:
863 return ': ' + _('No Files')
861 return ': ' + _('No Files')
864
862
865
863
866 def repo_link(groups_and_repos):
864 def repo_link(groups_and_repos):
867 """
865 """
868 Makes a breadcrumbs link to repo within a group
866 Makes a breadcrumbs link to repo within a group
869 joins &raquo; on each group to create a fancy link
867 joins &raquo; on each group to create a fancy link
870
868
871 ex::
869 ex::
872 group >> subgroup >> repo
870 group >> subgroup >> repo
873
871
874 :param groups_and_repos:
872 :param groups_and_repos:
875 """
873 """
876 groups, repo_name = groups_and_repos
874 groups, repo_name = groups_and_repos
877
875
878 if not groups:
876 if not groups:
879 return repo_name
877 return repo_name
880 else:
878 else:
881 def make_link(group):
879 def make_link(group):
882 return link_to(group.name, url('repos_group_home',
880 return link_to(group.name, url('repos_group_home',
883 group_name=group.group_name))
881 group_name=group.group_name))
884 return literal(' &raquo; '.join(map(make_link, groups)) + \
882 return literal(' &raquo; '.join(map(make_link, groups)) + \
885 " &raquo; " + repo_name)
883 " &raquo; " + repo_name)
886
884
887
885
888 def fancy_file_stats(stats):
886 def fancy_file_stats(stats):
889 """
887 """
890 Displays a fancy two colored bar for number of added/deleted
888 Displays a fancy two colored bar for number of added/deleted
891 lines of code on file
889 lines of code on file
892
890
893 :param stats: two element list of added/deleted lines of code
891 :param stats: two element list of added/deleted lines of code
894 """
892 """
895
893
896 a, d, t = stats[0], stats[1], stats[0] + stats[1]
894 a, d, t = stats[0], stats[1], stats[0] + stats[1]
897 width = 100
895 width = 100
898 unit = float(width) / (t or 1)
896 unit = float(width) / (t or 1)
899
897
900 # needs > 9% of width to be visible or 0 to be hidden
898 # needs > 9% of width to be visible or 0 to be hidden
901 a_p = max(9, unit * a) if a > 0 else 0
899 a_p = max(9, unit * a) if a > 0 else 0
902 d_p = max(9, unit * d) if d > 0 else 0
900 d_p = max(9, unit * d) if d > 0 else 0
903 p_sum = a_p + d_p
901 p_sum = a_p + d_p
904
902
905 if p_sum > width:
903 if p_sum > width:
906 #adjust the percentage to be == 100% since we adjusted to 9
904 #adjust the percentage to be == 100% since we adjusted to 9
907 if a_p > d_p:
905 if a_p > d_p:
908 a_p = a_p - (p_sum - width)
906 a_p = a_p - (p_sum - width)
909 else:
907 else:
910 d_p = d_p - (p_sum - width)
908 d_p = d_p - (p_sum - width)
911
909
912 a_v = a if a > 0 else ''
910 a_v = a if a > 0 else ''
913 d_v = d if d > 0 else ''
911 d_v = d if d > 0 else ''
914
912
915 def cgen(l_type):
913 def cgen(l_type):
916 mapping = {'tr': 'top-right-rounded-corner-mid',
914 mapping = {'tr': 'top-right-rounded-corner-mid',
917 'tl': 'top-left-rounded-corner-mid',
915 'tl': 'top-left-rounded-corner-mid',
918 'br': 'bottom-right-rounded-corner-mid',
916 'br': 'bottom-right-rounded-corner-mid',
919 'bl': 'bottom-left-rounded-corner-mid'}
917 'bl': 'bottom-left-rounded-corner-mid'}
920 map_getter = lambda x: mapping[x]
918 map_getter = lambda x: mapping[x]
921
919
922 if l_type == 'a' and d_v:
920 if l_type == 'a' and d_v:
923 #case when added and deleted are present
921 #case when added and deleted are present
924 return ' '.join(map(map_getter, ['tl', 'bl']))
922 return ' '.join(map(map_getter, ['tl', 'bl']))
925
923
926 if l_type == 'a' and not d_v:
924 if l_type == 'a' and not d_v:
927 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
925 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
928
926
929 if l_type == 'd' and a_v:
927 if l_type == 'd' and a_v:
930 return ' '.join(map(map_getter, ['tr', 'br']))
928 return ' '.join(map(map_getter, ['tr', 'br']))
931
929
932 if l_type == 'd' and not a_v:
930 if l_type == 'd' and not a_v:
933 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
931 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
934
932
935 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
933 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
936 cgen('a'), a_p, a_v
934 cgen('a'), a_p, a_v
937 )
935 )
938 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
936 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
939 cgen('d'), d_p, d_v
937 cgen('d'), d_p, d_v
940 )
938 )
941 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
939 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
942
940
943
941
944 def urlify_text(text_):
942 def urlify_text(text_):
945 import re
943 import re
946
944
947 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
945 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
948 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
946 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
949
947
950 def url_func(match_obj):
948 def url_func(match_obj):
951 url_full = match_obj.groups()[0]
949 url_full = match_obj.groups()[0]
952 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
950 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
953
951
954 return literal(url_pat.sub(url_func, text_))
952 return literal(url_pat.sub(url_func, text_))
955
953
956
954
957 def urlify_changesets(text_, repository):
955 def urlify_changesets(text_, repository):
958 """
956 """
959 Extract revision ids from changeset and make link from them
957 Extract revision ids from changeset and make link from them
960
958
961 :param text_:
959 :param text_:
962 :param repository:
960 :param repository:
963 """
961 """
964 import re
962 import re
965 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
963 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
966
964
967 def url_func(match_obj):
965 def url_func(match_obj):
968 rev = match_obj.groups()[0]
966 rev = match_obj.groups()[0]
969 pref = ''
967 pref = ''
970 if match_obj.group().startswith(' '):
968 if match_obj.group().startswith(' '):
971 pref = ' '
969 pref = ' '
972 tmpl = (
970 tmpl = (
973 '%(pref)s<a class="%(cls)s" href="%(url)s">'
971 '%(pref)s<a class="%(cls)s" href="%(url)s">'
974 '%(rev)s'
972 '%(rev)s'
975 '</a>'
973 '</a>'
976 )
974 )
977 return tmpl % {
975 return tmpl % {
978 'pref': pref,
976 'pref': pref,
979 'cls': 'revision-link',
977 'cls': 'revision-link',
980 'url': url('changeset_home', repo_name=repository, revision=rev),
978 'url': url('changeset_home', repo_name=repository, revision=rev),
981 'rev': rev,
979 'rev': rev,
982 }
980 }
983
981
984 newtext = URL_PAT.sub(url_func, text_)
982 newtext = URL_PAT.sub(url_func, text_)
985
983
986 return newtext
984 return newtext
987
985
988
986
989 def urlify_commit(text_, repository=None, link_=None):
987 def urlify_commit(text_, repository=None, link_=None):
990 """
988 """
991 Parses given text message and makes proper links.
989 Parses given text message and makes proper links.
992 issues are linked to given issue-server, and rest is a changeset link
990 issues are linked to given issue-server, and rest is a changeset link
993 if link_ is given, in other case it's a plain text
991 if link_ is given, in other case it's a plain text
994
992
995 :param text_:
993 :param text_:
996 :param repository:
994 :param repository:
997 :param link_: changeset link
995 :param link_: changeset link
998 """
996 """
999 import re
997 import re
1000 import traceback
998 import traceback
1001
999
1002 def escaper(string):
1000 def escaper(string):
1003 return string.replace('<', '&lt;').replace('>', '&gt;')
1001 return string.replace('<', '&lt;').replace('>', '&gt;')
1004
1002
1005 def linkify_others(t, l):
1003 def linkify_others(t, l):
1006 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1004 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1007 links = []
1005 links = []
1008 for e in urls.split(t):
1006 for e in urls.split(t):
1009 if not urls.match(e):
1007 if not urls.match(e):
1010 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1008 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1011 else:
1009 else:
1012 links.append(e)
1010 links.append(e)
1013
1011
1014 return ''.join(links)
1012 return ''.join(links)
1015
1013
1016 # urlify changesets - extrac revisions and make link out of them
1014 # urlify changesets - extrac revisions and make link out of them
1017 newtext = urlify_changesets(escaper(text_), repository)
1015 newtext = urlify_changesets(escaper(text_), repository)
1018
1016
1019 try:
1017 try:
1020 conf = config['app_conf']
1018 conf = config['app_conf']
1021
1019
1022 # allow multiple issue servers to be used
1020 # allow multiple issue servers to be used
1023 valid_indices = [
1021 valid_indices = [
1024 x.group(1)
1022 x.group(1)
1025 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1023 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1026 if x and 'issue_server_link%s' % x.group(1) in conf
1024 if x and 'issue_server_link%s' % x.group(1) in conf
1027 and 'issue_prefix%s' % x.group(1) in conf
1025 and 'issue_prefix%s' % x.group(1) in conf
1028 ]
1026 ]
1029
1027
1030 log.debug('found issue server suffixes `%s` during valuation of: %s'
1028 log.debug('found issue server suffixes `%s` during valuation of: %s'
1031 % (','.join(valid_indices), newtext))
1029 % (','.join(valid_indices), newtext))
1032
1030
1033 for pattern_index in valid_indices:
1031 for pattern_index in valid_indices:
1034 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1032 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1035 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1033 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1036 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1034 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1037
1035
1038 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1036 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1039 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1037 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1040 ISSUE_PREFIX))
1038 ISSUE_PREFIX))
1041
1039
1042 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1040 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1043
1041
1044 def url_func(match_obj):
1042 def url_func(match_obj):
1045 pref = ''
1043 pref = ''
1046 if match_obj.group().startswith(' '):
1044 if match_obj.group().startswith(' '):
1047 pref = ' '
1045 pref = ' '
1048
1046
1049 issue_id = ''.join(match_obj.groups())
1047 issue_id = ''.join(match_obj.groups())
1050 tmpl = (
1048 tmpl = (
1051 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1049 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1052 '%(issue-prefix)s%(id-repr)s'
1050 '%(issue-prefix)s%(id-repr)s'
1053 '</a>'
1051 '</a>'
1054 )
1052 )
1055 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1053 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1056 if repository:
1054 if repository:
1057 url = url.replace('{repo}', repository)
1055 url = url.replace('{repo}', repository)
1058 repo_name = repository.split(URL_SEP)[-1]
1056 repo_name = repository.split(URL_SEP)[-1]
1059 url = url.replace('{repo_name}', repo_name)
1057 url = url.replace('{repo_name}', repo_name)
1060
1058
1061 return tmpl % {
1059 return tmpl % {
1062 'pref': pref,
1060 'pref': pref,
1063 'cls': 'issue-tracker-link',
1061 'cls': 'issue-tracker-link',
1064 'url': url,
1062 'url': url,
1065 'id-repr': issue_id,
1063 'id-repr': issue_id,
1066 'issue-prefix': ISSUE_PREFIX,
1064 'issue-prefix': ISSUE_PREFIX,
1067 'serv': ISSUE_SERVER_LNK,
1065 'serv': ISSUE_SERVER_LNK,
1068 }
1066 }
1069 newtext = URL_PAT.sub(url_func, newtext)
1067 newtext = URL_PAT.sub(url_func, newtext)
1070 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1068 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1071
1069
1072 # if we actually did something above
1070 # if we actually did something above
1073 if link_:
1071 if link_:
1074 # wrap not links into final link => link_
1072 # wrap not links into final link => link_
1075 newtext = linkify_others(newtext, link_)
1073 newtext = linkify_others(newtext, link_)
1076
1074
1077 return literal(newtext)
1075 return literal(newtext)
1078 except:
1076 except:
1079 log.error(traceback.format_exc())
1077 log.error(traceback.format_exc())
1080 pass
1078 pass
1081
1079
1082 return newtext
1080 return newtext
1083
1081
1084
1082
1085 def rst(source):
1083 def rst(source):
1086 return literal('<div class="rst-block">%s</div>' %
1084 return literal('<div class="rst-block">%s</div>' %
1087 MarkupRenderer.rst(source))
1085 MarkupRenderer.rst(source))
1088
1086
1089
1087
1090 def rst_w_mentions(source):
1088 def rst_w_mentions(source):
1091 """
1089 """
1092 Wrapped rst renderer with @mention highlighting
1090 Wrapped rst renderer with @mention highlighting
1093
1091
1094 :param source:
1092 :param source:
1095 """
1093 """
1096 return literal('<div class="rst-block">%s</div>' %
1094 return literal('<div class="rst-block">%s</div>' %
1097 MarkupRenderer.rst_with_mentions(source))
1095 MarkupRenderer.rst_with_mentions(source))
1098
1096
1099
1097
1100 def changeset_status(repo, revision):
1098 def changeset_status(repo, revision):
1101 return ChangesetStatusModel().get_status(repo, revision)
1099 return ChangesetStatusModel().get_status(repo, revision)
1102
1100
1103
1101
1104 def changeset_status_lbl(changeset_status):
1102 def changeset_status_lbl(changeset_status):
1105 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1103 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1106
1104
1107
1105
1108 def get_permission_name(key):
1106 def get_permission_name(key):
1109 return dict(Permission.PERMS).get(key)
1107 return dict(Permission.PERMS).get(key)
@@ -1,422 +1,422
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.hooks
3 rhodecode.lib.hooks
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Hooks runned by rhodecode
6 Hooks runned by rhodecode
7
7
8 :created_on: Aug 6, 2010
8 :created_on: Aug 6, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import sys
26 import sys
27 import time
27 import time
28 import binascii
28 import binascii
29 from inspect import isfunction
29 from inspect import isfunction
30
30
31 from mercurial.scmutil import revrange
31 from mercurial.scmutil import revrange
32 from mercurial.node import nullrev
32 from mercurial.node import nullrev
33
33
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.lib.utils import action_logger
35 from rhodecode.lib.utils import action_logger
36 from rhodecode.lib.vcs.backends.base import EmptyChangeset
36 from rhodecode.lib.vcs.backends.base import EmptyChangeset
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.exceptions import HTTPLockedRC
38 from rhodecode.lib.exceptions import HTTPLockedRC
39 from rhodecode.lib.utils2 import safe_str
39 from rhodecode.lib.utils2 import safe_str, datetime_to_time
40 from rhodecode.model.db import Repository, User
40 from rhodecode.model.db import Repository, User
41
41
42
42
43 def _get_scm_size(alias, root_path):
43 def _get_scm_size(alias, root_path):
44
44
45 if not alias.startswith('.'):
45 if not alias.startswith('.'):
46 alias += '.'
46 alias += '.'
47
47
48 size_scm, size_root = 0, 0
48 size_scm, size_root = 0, 0
49 for path, dirs, files in os.walk(root_path):
49 for path, dirs, files in os.walk(root_path):
50 if path.find(alias) != -1:
50 if path.find(alias) != -1:
51 for f in files:
51 for f in files:
52 try:
52 try:
53 size_scm += os.path.getsize(os.path.join(path, f))
53 size_scm += os.path.getsize(os.path.join(path, f))
54 except OSError:
54 except OSError:
55 pass
55 pass
56 else:
56 else:
57 for f in files:
57 for f in files:
58 try:
58 try:
59 size_root += os.path.getsize(os.path.join(path, f))
59 size_root += os.path.getsize(os.path.join(path, f))
60 except OSError:
60 except OSError:
61 pass
61 pass
62
62
63 size_scm_f = h.format_byte_size(size_scm)
63 size_scm_f = h.format_byte_size(size_scm)
64 size_root_f = h.format_byte_size(size_root)
64 size_root_f = h.format_byte_size(size_root)
65 size_total_f = h.format_byte_size(size_root + size_scm)
65 size_total_f = h.format_byte_size(size_root + size_scm)
66
66
67 return size_scm_f, size_root_f, size_total_f
67 return size_scm_f, size_root_f, size_total_f
68
68
69
69
70 def repo_size(ui, repo, hooktype=None, **kwargs):
70 def repo_size(ui, repo, hooktype=None, **kwargs):
71 """
71 """
72 Presents size of repository after push
72 Presents size of repository after push
73
73
74 :param ui:
74 :param ui:
75 :param repo:
75 :param repo:
76 :param hooktype:
76 :param hooktype:
77 """
77 """
78
78
79 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
79 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
80
80
81 last_cs = repo[len(repo) - 1]
81 last_cs = repo[len(repo) - 1]
82
82
83 msg = ('Repository size .hg:%s repo:%s total:%s\n'
83 msg = ('Repository size .hg:%s repo:%s total:%s\n'
84 'Last revision is now r%s:%s\n') % (
84 'Last revision is now r%s:%s\n') % (
85 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
85 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
86 )
86 )
87
87
88 sys.stdout.write(msg)
88 sys.stdout.write(msg)
89
89
90
90
91 def pre_push(ui, repo, **kwargs):
91 def pre_push(ui, repo, **kwargs):
92 # pre push function, currently used to ban pushing when
92 # pre push function, currently used to ban pushing when
93 # repository is locked
93 # repository is locked
94 try:
94 try:
95 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
95 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
96 except:
96 except:
97 rc_extras = {}
97 rc_extras = {}
98 extras = dict(repo.ui.configitems('rhodecode_extras'))
98 extras = dict(repo.ui.configitems('rhodecode_extras'))
99
99
100 if 'username' in extras:
100 if 'username' in extras:
101 username = extras['username']
101 username = extras['username']
102 repository = extras['repository']
102 repository = extras['repository']
103 scm = extras['scm']
103 scm = extras['scm']
104 locked_by = extras['locked_by']
104 locked_by = extras['locked_by']
105 elif 'username' in rc_extras:
105 elif 'username' in rc_extras:
106 username = rc_extras['username']
106 username = rc_extras['username']
107 repository = rc_extras['repository']
107 repository = rc_extras['repository']
108 scm = rc_extras['scm']
108 scm = rc_extras['scm']
109 locked_by = rc_extras['locked_by']
109 locked_by = rc_extras['locked_by']
110 else:
110 else:
111 raise Exception('Missing data in repo.ui and os.environ')
111 raise Exception('Missing data in repo.ui and os.environ')
112
112
113 usr = User.get_by_username(username)
113 usr = User.get_by_username(username)
114 if locked_by[0] and usr.user_id != int(locked_by[0]):
114 if locked_by[0] and usr.user_id != int(locked_by[0]):
115 locked_by = User.get(locked_by[0]).username
115 locked_by = User.get(locked_by[0]).username
116 raise HTTPLockedRC(repository, locked_by)
116 raise HTTPLockedRC(repository, locked_by)
117
117
118
118
119 def pre_pull(ui, repo, **kwargs):
119 def pre_pull(ui, repo, **kwargs):
120 # pre push function, currently used to ban pushing when
120 # pre push function, currently used to ban pushing when
121 # repository is locked
121 # repository is locked
122 try:
122 try:
123 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
123 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
124 except:
124 except:
125 rc_extras = {}
125 rc_extras = {}
126 extras = dict(repo.ui.configitems('rhodecode_extras'))
126 extras = dict(repo.ui.configitems('rhodecode_extras'))
127 if 'username' in extras:
127 if 'username' in extras:
128 username = extras['username']
128 username = extras['username']
129 repository = extras['repository']
129 repository = extras['repository']
130 scm = extras['scm']
130 scm = extras['scm']
131 locked_by = extras['locked_by']
131 locked_by = extras['locked_by']
132 elif 'username' in rc_extras:
132 elif 'username' in rc_extras:
133 username = rc_extras['username']
133 username = rc_extras['username']
134 repository = rc_extras['repository']
134 repository = rc_extras['repository']
135 scm = rc_extras['scm']
135 scm = rc_extras['scm']
136 locked_by = rc_extras['locked_by']
136 locked_by = rc_extras['locked_by']
137 else:
137 else:
138 raise Exception('Missing data in repo.ui and os.environ')
138 raise Exception('Missing data in repo.ui and os.environ')
139
139
140 if locked_by[0]:
140 if locked_by[0]:
141 locked_by = User.get(locked_by[0]).username
141 locked_by = User.get(locked_by[0]).username
142 raise HTTPLockedRC(repository, locked_by)
142 raise HTTPLockedRC(repository, locked_by)
143
143
144
144
145 def log_pull_action(ui, repo, **kwargs):
145 def log_pull_action(ui, repo, **kwargs):
146 """
146 """
147 Logs user last pull action
147 Logs user last pull action
148
148
149 :param ui:
149 :param ui:
150 :param repo:
150 :param repo:
151 """
151 """
152 try:
152 try:
153 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
153 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
154 except:
154 except:
155 rc_extras = {}
155 rc_extras = {}
156 extras = dict(repo.ui.configitems('rhodecode_extras'))
156 extras = dict(repo.ui.configitems('rhodecode_extras'))
157 if 'username' in extras:
157 if 'username' in extras:
158 username = extras['username']
158 username = extras['username']
159 repository = extras['repository']
159 repository = extras['repository']
160 scm = extras['scm']
160 scm = extras['scm']
161 make_lock = extras['make_lock']
161 make_lock = extras['make_lock']
162 elif 'username' in rc_extras:
162 elif 'username' in rc_extras:
163 username = rc_extras['username']
163 username = rc_extras['username']
164 repository = rc_extras['repository']
164 repository = rc_extras['repository']
165 scm = rc_extras['scm']
165 scm = rc_extras['scm']
166 make_lock = rc_extras['make_lock']
166 make_lock = rc_extras['make_lock']
167 else:
167 else:
168 raise Exception('Missing data in repo.ui and os.environ')
168 raise Exception('Missing data in repo.ui and os.environ')
169 user = User.get_by_username(username)
169 user = User.get_by_username(username)
170 action = 'pull'
170 action = 'pull'
171 action_logger(user, action, repository, extras['ip'], commit=True)
171 action_logger(user, action, repository, extras['ip'], commit=True)
172 # extension hook call
172 # extension hook call
173 from rhodecode import EXTENSIONS
173 from rhodecode import EXTENSIONS
174 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
174 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
175
175
176 if isfunction(callback):
176 if isfunction(callback):
177 kw = {}
177 kw = {}
178 kw.update(extras)
178 kw.update(extras)
179 callback(**kw)
179 callback(**kw)
180
180
181 if make_lock is True:
181 if make_lock is True:
182 Repository.lock(Repository.get_by_repo_name(repository), user.user_id)
182 Repository.lock(Repository.get_by_repo_name(repository), user.user_id)
183 #msg = 'Made lock on repo `%s`' % repository
183 #msg = 'Made lock on repo `%s`' % repository
184 #sys.stdout.write(msg)
184 #sys.stdout.write(msg)
185
185
186 return 0
186 return 0
187
187
188
188
189 def log_push_action(ui, repo, **kwargs):
189 def log_push_action(ui, repo, **kwargs):
190 """
190 """
191 Maps user last push action to new changeset id, from mercurial
191 Maps user last push action to new changeset id, from mercurial
192
192
193 :param ui:
193 :param ui:
194 :param repo: repo object containing the `ui` object
194 :param repo: repo object containing the `ui` object
195 """
195 """
196
196
197 try:
197 try:
198 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
198 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
199 except:
199 except:
200 rc_extras = {}
200 rc_extras = {}
201
201
202 extras = dict(repo.ui.configitems('rhodecode_extras'))
202 extras = dict(repo.ui.configitems('rhodecode_extras'))
203 if 'username' in extras:
203 if 'username' in extras:
204 username = extras['username']
204 username = extras['username']
205 repository = extras['repository']
205 repository = extras['repository']
206 scm = extras['scm']
206 scm = extras['scm']
207 make_lock = extras['make_lock']
207 make_lock = extras['make_lock']
208 elif 'username' in rc_extras:
208 elif 'username' in rc_extras:
209 username = rc_extras['username']
209 username = rc_extras['username']
210 repository = rc_extras['repository']
210 repository = rc_extras['repository']
211 scm = rc_extras['scm']
211 scm = rc_extras['scm']
212 make_lock = rc_extras['make_lock']
212 make_lock = rc_extras['make_lock']
213 else:
213 else:
214 raise Exception('Missing data in repo.ui and os.environ')
214 raise Exception('Missing data in repo.ui and os.environ')
215
215
216 action = 'push' + ':%s'
216 action = 'push' + ':%s'
217
217
218 if scm == 'hg':
218 if scm == 'hg':
219 node = kwargs['node']
219 node = kwargs['node']
220
220
221 def get_revs(repo, rev_opt):
221 def get_revs(repo, rev_opt):
222 if rev_opt:
222 if rev_opt:
223 revs = revrange(repo, rev_opt)
223 revs = revrange(repo, rev_opt)
224
224
225 if len(revs) == 0:
225 if len(revs) == 0:
226 return (nullrev, nullrev)
226 return (nullrev, nullrev)
227 return (max(revs), min(revs))
227 return (max(revs), min(revs))
228 else:
228 else:
229 return (len(repo) - 1, 0)
229 return (len(repo) - 1, 0)
230
230
231 stop, start = get_revs(repo, [node + ':'])
231 stop, start = get_revs(repo, [node + ':'])
232 h = binascii.hexlify
232 h = binascii.hexlify
233 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
233 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
234 elif scm == 'git':
234 elif scm == 'git':
235 revs = kwargs.get('_git_revs', [])
235 revs = kwargs.get('_git_revs', [])
236 if '_git_revs' in kwargs:
236 if '_git_revs' in kwargs:
237 kwargs.pop('_git_revs')
237 kwargs.pop('_git_revs')
238
238
239 action = action % ','.join(revs)
239 action = action % ','.join(revs)
240
240
241 action_logger(username, action, repository, extras['ip'], commit=True)
241 action_logger(username, action, repository, extras['ip'], commit=True)
242
242
243 # extension hook call
243 # extension hook call
244 from rhodecode import EXTENSIONS
244 from rhodecode import EXTENSIONS
245 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
245 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
246 if isfunction(callback):
246 if isfunction(callback):
247 kw = {'pushed_revs': revs}
247 kw = {'pushed_revs': revs}
248 kw.update(extras)
248 kw.update(extras)
249 callback(**kw)
249 callback(**kw)
250
250
251 if make_lock is False:
251 if make_lock is False:
252 Repository.unlock(Repository.get_by_repo_name(repository))
252 Repository.unlock(Repository.get_by_repo_name(repository))
253 msg = 'Released lock on repo `%s`\n' % repository
253 msg = 'Released lock on repo `%s`\n' % repository
254 sys.stdout.write(msg)
254 sys.stdout.write(msg)
255
255
256 return 0
256 return 0
257
257
258
258
259 def log_create_repository(repository_dict, created_by, **kwargs):
259 def log_create_repository(repository_dict, created_by, **kwargs):
260 """
260 """
261 Post create repository Hook. This is a dummy function for admins to re-use
261 Post create repository Hook. This is a dummy function for admins to re-use
262 if needed. It's taken from rhodecode-extensions module and executed
262 if needed. It's taken from rhodecode-extensions module and executed
263 if present
263 if present
264
264
265 :param repository: dict dump of repository object
265 :param repository: dict dump of repository object
266 :param created_by: username who created repository
266 :param created_by: username who created repository
267
267
268 available keys of repository_dict:
268 available keys of repository_dict:
269
269
270 'repo_type',
270 'repo_type',
271 'description',
271 'description',
272 'private',
272 'private',
273 'created_on',
273 'created_on',
274 'enable_downloads',
274 'enable_downloads',
275 'repo_id',
275 'repo_id',
276 'user_id',
276 'user_id',
277 'enable_statistics',
277 'enable_statistics',
278 'clone_uri',
278 'clone_uri',
279 'fork_id',
279 'fork_id',
280 'group_id',
280 'group_id',
281 'repo_name'
281 'repo_name'
282
282
283 """
283 """
284 from rhodecode import EXTENSIONS
284 from rhodecode import EXTENSIONS
285 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
285 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
286 if isfunction(callback):
286 if isfunction(callback):
287 kw = {}
287 kw = {}
288 kw.update(repository_dict)
288 kw.update(repository_dict)
289 kw.update({'created_by': created_by})
289 kw.update({'created_by': created_by})
290 kw.update(kwargs)
290 kw.update(kwargs)
291 return callback(**kw)
291 return callback(**kw)
292
292
293 return 0
293 return 0
294
294
295
295
296 def log_delete_repository(repository_dict, deleted_by, **kwargs):
296 def log_delete_repository(repository_dict, deleted_by, **kwargs):
297 """
297 """
298 Post delete repository Hook. This is a dummy function for admins to re-use
298 Post delete repository Hook. This is a dummy function for admins to re-use
299 if needed. It's taken from rhodecode-extensions module and executed
299 if needed. It's taken from rhodecode-extensions module and executed
300 if present
300 if present
301
301
302 :param repository: dict dump of repository object
302 :param repository: dict dump of repository object
303 :param deleted_by: username who deleted the repository
303 :param deleted_by: username who deleted the repository
304
304
305 available keys of repository_dict:
305 available keys of repository_dict:
306
306
307 'repo_type',
307 'repo_type',
308 'description',
308 'description',
309 'private',
309 'private',
310 'created_on',
310 'created_on',
311 'enable_downloads',
311 'enable_downloads',
312 'repo_id',
312 'repo_id',
313 'user_id',
313 'user_id',
314 'enable_statistics',
314 'enable_statistics',
315 'clone_uri',
315 'clone_uri',
316 'fork_id',
316 'fork_id',
317 'group_id',
317 'group_id',
318 'repo_name'
318 'repo_name'
319
319
320 """
320 """
321 from rhodecode import EXTENSIONS
321 from rhodecode import EXTENSIONS
322 callback = getattr(EXTENSIONS, 'DELETE_REPO_HOOK', None)
322 callback = getattr(EXTENSIONS, 'DELETE_REPO_HOOK', None)
323 if isfunction(callback):
323 if isfunction(callback):
324 kw = {}
324 kw = {}
325 kw.update(repository_dict)
325 kw.update(repository_dict)
326 kw.update({'deleted_by': deleted_by,
326 kw.update({'deleted_by': deleted_by,
327 'deleted_on': time.time()})
327 'deleted_on': time.time()})
328 kw.update(kwargs)
328 kw.update(kwargs)
329 return callback(**kw)
329 return callback(**kw)
330
330
331 return 0
331 return 0
332
332
333
333
334 handle_git_pre_receive = (lambda repo_path, revs, env:
334 handle_git_pre_receive = (lambda repo_path, revs, env:
335 handle_git_receive(repo_path, revs, env, hook_type='pre'))
335 handle_git_receive(repo_path, revs, env, hook_type='pre'))
336 handle_git_post_receive = (lambda repo_path, revs, env:
336 handle_git_post_receive = (lambda repo_path, revs, env:
337 handle_git_receive(repo_path, revs, env, hook_type='post'))
337 handle_git_receive(repo_path, revs, env, hook_type='post'))
338
338
339
339
340 def handle_git_receive(repo_path, revs, env, hook_type='post'):
340 def handle_git_receive(repo_path, revs, env, hook_type='post'):
341 """
341 """
342 A really hacky method that is runned by git post-receive hook and logs
342 A really hacky method that is runned by git post-receive hook and logs
343 an push action together with pushed revisions. It's executed by subprocess
343 an push action together with pushed revisions. It's executed by subprocess
344 thus needs all info to be able to create a on the fly pylons enviroment,
344 thus needs all info to be able to create a on the fly pylons enviroment,
345 connect to database and run the logging code. Hacky as sh*t but works.
345 connect to database and run the logging code. Hacky as sh*t but works.
346
346
347 :param repo_path:
347 :param repo_path:
348 :type repo_path:
348 :type repo_path:
349 :param revs:
349 :param revs:
350 :type revs:
350 :type revs:
351 :param env:
351 :param env:
352 :type env:
352 :type env:
353 """
353 """
354 from paste.deploy import appconfig
354 from paste.deploy import appconfig
355 from sqlalchemy import engine_from_config
355 from sqlalchemy import engine_from_config
356 from rhodecode.config.environment import load_environment
356 from rhodecode.config.environment import load_environment
357 from rhodecode.model import init_model
357 from rhodecode.model import init_model
358 from rhodecode.model.db import RhodeCodeUi
358 from rhodecode.model.db import RhodeCodeUi
359 from rhodecode.lib.utils import make_ui
359 from rhodecode.lib.utils import make_ui
360 extras = json.loads(env['RHODECODE_EXTRAS'])
360 extras = json.loads(env['RHODECODE_EXTRAS'])
361
361
362 path, ini_name = os.path.split(extras['config'])
362 path, ini_name = os.path.split(extras['config'])
363 conf = appconfig('config:%s' % ini_name, relative_to=path)
363 conf = appconfig('config:%s' % ini_name, relative_to=path)
364 load_environment(conf.global_conf, conf.local_conf)
364 load_environment(conf.global_conf, conf.local_conf)
365
365
366 engine = engine_from_config(conf, 'sqlalchemy.db1.')
366 engine = engine_from_config(conf, 'sqlalchemy.db1.')
367 init_model(engine)
367 init_model(engine)
368
368
369 baseui = make_ui('db')
369 baseui = make_ui('db')
370 # fix if it's not a bare repo
370 # fix if it's not a bare repo
371 if repo_path.endswith(os.sep + '.git'):
371 if repo_path.endswith(os.sep + '.git'):
372 repo_path = repo_path[:-5]
372 repo_path = repo_path[:-5]
373
373
374 repo = Repository.get_by_full_path(repo_path)
374 repo = Repository.get_by_full_path(repo_path)
375 if not repo:
375 if not repo:
376 raise OSError('Repository %s not found in database'
376 raise OSError('Repository %s not found in database'
377 % (safe_str(repo_path)))
377 % (safe_str(repo_path)))
378
378
379 _hooks = dict(baseui.configitems('hooks')) or {}
379 _hooks = dict(baseui.configitems('hooks')) or {}
380
380
381 for k, v in extras.items():
381 for k, v in extras.items():
382 baseui.setconfig('rhodecode_extras', k, v)
382 baseui.setconfig('rhodecode_extras', k, v)
383 repo = repo.scm_instance
383 repo = repo.scm_instance
384 repo.ui = baseui
384 repo.ui = baseui
385
385
386 if hook_type == 'pre':
386 if hook_type == 'pre':
387 pre_push(baseui, repo)
387 pre_push(baseui, repo)
388
388
389 # if push hook is enabled via web interface
389 # if push hook is enabled via web interface
390 elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
390 elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
391
391
392 rev_data = []
392 rev_data = []
393 for l in revs:
393 for l in revs:
394 old_rev, new_rev, ref = l.split(' ')
394 old_rev, new_rev, ref = l.split(' ')
395 _ref_data = ref.split('/')
395 _ref_data = ref.split('/')
396 if _ref_data[1] in ['tags', 'heads']:
396 if _ref_data[1] in ['tags', 'heads']:
397 rev_data.append({'old_rev': old_rev,
397 rev_data.append({'old_rev': old_rev,
398 'new_rev': new_rev,
398 'new_rev': new_rev,
399 'ref': ref,
399 'ref': ref,
400 'type': _ref_data[1],
400 'type': _ref_data[1],
401 'name': _ref_data[2].strip()})
401 'name': _ref_data[2].strip()})
402
402
403 git_revs = []
403 git_revs = []
404 for push_ref in rev_data:
404 for push_ref in rev_data:
405 _type = push_ref['type']
405 _type = push_ref['type']
406 if _type == 'heads':
406 if _type == 'heads':
407 if push_ref['old_rev'] == EmptyChangeset().raw_id:
407 if push_ref['old_rev'] == EmptyChangeset().raw_id:
408 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
408 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
409 heads = repo.run_git_command(cmd)[0]
409 heads = repo.run_git_command(cmd)[0]
410 heads = heads.replace(push_ref['ref'], '')
410 heads = heads.replace(push_ref['ref'], '')
411 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
411 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
412 heads.splitlines()))
412 heads.splitlines()))
413 cmd = (('log %(new_rev)s' % push_ref) +
413 cmd = (('log %(new_rev)s' % push_ref) +
414 ' --reverse --pretty=format:"%H" --not ' + heads)
414 ' --reverse --pretty=format:"%H" --not ' + heads)
415 else:
415 else:
416 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
416 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
417 ' --reverse --pretty=format:"%H"')
417 ' --reverse --pretty=format:"%H"')
418 git_revs += repo.run_git_command(cmd)[0].splitlines()
418 git_revs += repo.run_git_command(cmd)[0].splitlines()
419 elif _type == 'tags':
419 elif _type == 'tags':
420 git_revs += [push_ref['name']]
420 git_revs += [push_ref['name']]
421
421
422 log_push_action(baseui, repo, _git_revs=git_revs)
422 log_push_action(baseui, repo, _git_revs=git_revs)
@@ -1,342 +1,338
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 ${_('Settings administration')} - ${c.rhodecode_name}
5 ${_('Settings administration')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
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 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 <!-- end box / title -->
22 <!-- end box / title -->
23
23
24 <h3>${_('Remap and rescan repositories')}</h3>
24 <h3>${_('Remap and rescan repositories')}</h3>
25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
26 <div class="form">
26 <div class="form">
27 <!-- fields -->
27 <!-- fields -->
28
28
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label label-checkbox">
31 <div class="label label-checkbox">
32 <label for="destroy">${_('rescan option')}:</label>
32 <label for="destroy">${_('rescan option')}:</label>
33 </div>
33 </div>
34 <div class="checkboxes">
34 <div class="checkboxes">
35 <div class="checkbox">
35 <div class="checkbox">
36 ${h.checkbox('destroy',True)}
36 ${h.checkbox('destroy',True)}
37 <label for="destroy">
37 <label for="destroy">
38 <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
38 <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
39 ${_('destroy old data')}</span> </label>
39 ${_('destroy old data')}</span> </label>
40 </div>
40 </div>
41 <span class="help-block">${_('Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked ')}</span>
41 <span class="help-block">${_('Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked ')}</span>
42 </div>
42 </div>
43 </div>
43 </div>
44
44
45 <div class="buttons">
45 <div class="buttons">
46 ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")}
46 ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")}
47 </div>
47 </div>
48 </div>
48 </div>
49 </div>
49 </div>
50 ${h.end_form()}
50 ${h.end_form()}
51
51
52 <h3>${_('Whoosh indexing')}</h3>
52 <h3>${_('Whoosh indexing')}</h3>
53 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
53 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
54 <div class="form">
54 <div class="form">
55 <!-- fields -->
55 <!-- fields -->
56
56
57 <div class="fields">
57 <div class="fields">
58 <div class="field">
58 <div class="field">
59 <div class="label label-checkbox">
59 <div class="label label-checkbox">
60 <label>${_('index build option')}:</label>
60 <label>${_('index build option')}:</label>
61 </div>
61 </div>
62 <div class="checkboxes">
62 <div class="checkboxes">
63 <div class="checkbox">
63 <div class="checkbox">
64 ${h.checkbox('full_index',True)}
64 ${h.checkbox('full_index',True)}
65 <label for="full_index">${_('build from scratch')}</label>
65 <label for="full_index">${_('build from scratch')}</label>
66 </div>
66 </div>
67 </div>
67 </div>
68 </div>
68 </div>
69
69
70 <div class="buttons">
70 <div class="buttons">
71 ${h.submit('reindex',_('Reindex'),class_="ui-btn large")}
71 ${h.submit('reindex',_('Reindex'),class_="ui-btn large")}
72 </div>
72 </div>
73 </div>
73 </div>
74 </div>
74 </div>
75 ${h.end_form()}
75 ${h.end_form()}
76
76
77 <h3>${_('Global application settings')}</h3>
77 <h3>${_('Global application settings')}</h3>
78 ${h.form(url('admin_setting', setting_id='global'),method='put')}
78 ${h.form(url('admin_setting', setting_id='global'),method='put')}
79 <div class="form">
79 <div class="form">
80 <!-- fields -->
80 <!-- fields -->
81
81
82 <div class="fields">
82 <div class="fields">
83
83
84 <div class="field">
84 <div class="field">
85 <div class="label">
85 <div class="label">
86 <label for="rhodecode_title">${_('Application name')}:</label>
86 <label for="rhodecode_title">${_('Application name')}:</label>
87 </div>
87 </div>
88 <div class="input">
88 <div class="input">
89 ${h.text('rhodecode_title',size=30)}
89 ${h.text('rhodecode_title',size=30)}
90 </div>
90 </div>
91 </div>
91 </div>
92
92
93 <div class="field">
93 <div class="field">
94 <div class="label">
94 <div class="label">
95 <label for="rhodecode_realm">${_('Realm text')}:</label>
95 <label for="rhodecode_realm">${_('Realm text')}:</label>
96 </div>
96 </div>
97 <div class="input">
97 <div class="input">
98 ${h.text('rhodecode_realm',size=30)}
98 ${h.text('rhodecode_realm',size=30)}
99 </div>
99 </div>
100 </div>
100 </div>
101
101
102 <div class="field">
102 <div class="field">
103 <div class="label">
103 <div class="label">
104 <label for="rhodecode_ga_code">${_('GA code')}:</label>
104 <label for="rhodecode_ga_code">${_('GA code')}:</label>
105 </div>
105 </div>
106 <div class="input">
106 <div class="input">
107 ${h.text('rhodecode_ga_code',size=30)}
107 ${h.text('rhodecode_ga_code',size=30)}
108 </div>
108 </div>
109 </div>
109 </div>
110
110
111 <div class="buttons">
111 <div class="buttons">
112 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
112 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
113 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
113 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
114 </div>
114 </div>
115 </div>
115 </div>
116 </div>
116 </div>
117 ${h.end_form()}
117 ${h.end_form()}
118
118
119 <h3>${_('Visualisation settings')}</h3>
119 <h3>${_('Visualisation settings')}</h3>
120 ${h.form(url('admin_setting', setting_id='visual'),method='put')}
120 ${h.form(url('admin_setting', setting_id='visual'),method='put')}
121 <div class="form">
121 <div class="form">
122 <!-- fields -->
122 <!-- fields -->
123
123
124 <div class="fields">
124 <div class="fields">
125 <div class="field">
125 <div class="field">
126 <div class="label label-checkbox">
126 <div class="label label-checkbox">
127 <label>${_('General')}:</label>
127 <label>${_('General')}:</label>
128 </div>
128 </div>
129 <div class="checkboxes">
129 <div class="checkboxes">
130 <div class="checkbox">
130 <div class="checkbox">
131 ${h.checkbox('rhodecode_lightweight_dashboard','True')}
131 ${h.checkbox('rhodecode_lightweight_dashboard','True')}
132 <label for="rhodecode_lightweight_dashboard">${_('Use lightweight dashboard')}</label>
132 <label for="rhodecode_lightweight_dashboard">${_('Use lightweight dashboard')}</label>
133 </div>
133 </div>
134 <div class="checkbox">
135 ${h.checkbox('rhodecode_lightweight_journal','True')}
136 <label for="rhodecode_lightweight_journal">${_('Use lightweight journal')}</label>
137 </div>
138 </div>
134 </div>
139 </div>
135 </div>
140
136
141 <div class="field">
137 <div class="field">
142 <div class="label label-checkbox">
138 <div class="label label-checkbox">
143 <label>${_('Icons')}:</label>
139 <label>${_('Icons')}:</label>
144 </div>
140 </div>
145 <div class="checkboxes">
141 <div class="checkboxes">
146 <div class="checkbox">
142 <div class="checkbox">
147 ${h.checkbox('rhodecode_show_public_icon','True')}
143 ${h.checkbox('rhodecode_show_public_icon','True')}
148 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
144 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
149 </div>
145 </div>
150 <div class="checkbox">
146 <div class="checkbox">
151 ${h.checkbox('rhodecode_show_private_icon','True')}
147 ${h.checkbox('rhodecode_show_private_icon','True')}
152 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
148 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
153 </div>
149 </div>
154 </div>
150 </div>
155 </div>
151 </div>
156
152
157 <div class="field">
153 <div class="field">
158 <div class="label label-checkbox">
154 <div class="label label-checkbox">
159 <label>${_('Meta-Tagging')}:</label>
155 <label>${_('Meta-Tagging')}:</label>
160 </div>
156 </div>
161 <div class="checkboxes">
157 <div class="checkboxes">
162 <div class="checkbox">
158 <div class="checkbox">
163 ${h.checkbox('rhodecode_stylify_metatags','True')}
159 ${h.checkbox('rhodecode_stylify_metatags','True')}
164 <label for="rhodecode_stylify_metatags">${_('Stylify recognised metatags:')}</label>
160 <label for="rhodecode_stylify_metatags">${_('Stylify recognised metatags:')}</label>
165 </div>
161 </div>
166 <div style="padding-left: 20px;">
162 <div style="padding-left: 20px;">
167 <ul> <!-- Fix style here -->
163 <ul> <!-- Fix style here -->
168 <li>[featured] <span class="metatag" tag="featured">featured</span></li>
164 <li>[featured] <span class="metatag" tag="featured">featured</span></li>
169 <li>[stale] <span class="metatag" tag="stale">stale</span></li>
165 <li>[stale] <span class="metatag" tag="stale">stale</span></li>
170 <li>[dead] <span class="metatag" tag="dead">dead</span></li>
166 <li>[dead] <span class="metatag" tag="dead">dead</span></li>
171 <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
167 <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
172 <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
168 <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
173 <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
169 <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
174 <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
170 <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
175 <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
171 <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
176 </ul>
172 </ul>
177 </div>
173 </div>
178 </div>
174 </div>
179 </div>
175 </div>
180
176
181 <div class="buttons">
177 <div class="buttons">
182 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
178 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
183 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
179 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
184 </div>
180 </div>
185
181
186 </div>
182 </div>
187 </div>
183 </div>
188 ${h.end_form()}
184 ${h.end_form()}
189
185
190
186
191 <h3>${_('VCS settings')}</h3>
187 <h3>${_('VCS settings')}</h3>
192 ${h.form(url('admin_setting', setting_id='vcs'),method='put')}
188 ${h.form(url('admin_setting', setting_id='vcs'),method='put')}
193 <div class="form">
189 <div class="form">
194 <!-- fields -->
190 <!-- fields -->
195
191
196 <div class="fields">
192 <div class="fields">
197
193
198 <div class="field">
194 <div class="field">
199 <div class="label label-checkbox">
195 <div class="label label-checkbox">
200 <label>${_('Web')}:</label>
196 <label>${_('Web')}:</label>
201 </div>
197 </div>
202 <div class="checkboxes">
198 <div class="checkboxes">
203 <div class="checkbox">
199 <div class="checkbox">
204 ${h.checkbox('web_push_ssl', 'True')}
200 ${h.checkbox('web_push_ssl', 'True')}
205 <label for="web_push_ssl">${_('require ssl for vcs operations')}</label>
201 <label for="web_push_ssl">${_('require ssl for vcs operations')}</label>
206 </div>
202 </div>
207 <span class="help-block">${_('RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable')}</span>
203 <span class="help-block">${_('RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable')}</span>
208 </div>
204 </div>
209 </div>
205 </div>
210
206
211 <div class="field">
207 <div class="field">
212 <div class="label label-checkbox">
208 <div class="label label-checkbox">
213 <label>${_('Hooks')}:</label>
209 <label>${_('Hooks')}:</label>
214 </div>
210 </div>
215 <div class="checkboxes">
211 <div class="checkboxes">
216 <div class="checkbox">
212 <div class="checkbox">
217 ${h.checkbox('hooks_changegroup_update','True')}
213 ${h.checkbox('hooks_changegroup_update','True')}
218 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
214 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
219 </div>
215 </div>
220 <div class="checkbox">
216 <div class="checkbox">
221 ${h.checkbox('hooks_changegroup_repo_size','True')}
217 ${h.checkbox('hooks_changegroup_repo_size','True')}
222 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
218 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
223 </div>
219 </div>
224 <div class="checkbox">
220 <div class="checkbox">
225 ${h.checkbox('hooks_changegroup_push_logger','True')}
221 ${h.checkbox('hooks_changegroup_push_logger','True')}
226 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
222 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
227 </div>
223 </div>
228 <div class="checkbox">
224 <div class="checkbox">
229 ${h.checkbox('hooks_outgoing_pull_logger','True')}
225 ${h.checkbox('hooks_outgoing_pull_logger','True')}
230 <label for="hooks_outgoing_pull_logger">${_('Log user pull commands')}</label>
226 <label for="hooks_outgoing_pull_logger">${_('Log user pull commands')}</label>
231 </div>
227 </div>
232 </div>
228 </div>
233 <div class="input" style="margin-top:10px">
229 <div class="input" style="margin-top:10px">
234 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
230 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
235 </div>
231 </div>
236 </div>
232 </div>
237 <div class="field">
233 <div class="field">
238 <div class="label label-checkbox">
234 <div class="label label-checkbox">
239 <label>${_('Mercurial Extensions')}:</label>
235 <label>${_('Mercurial Extensions')}:</label>
240 </div>
236 </div>
241 <div class="checkboxes">
237 <div class="checkboxes">
242 <div class="checkbox">
238 <div class="checkbox">
243 ${h.checkbox('extensions_largefiles','True')}
239 ${h.checkbox('extensions_largefiles','True')}
244 <label for="extensions_hgsubversion">${_('largefiles extensions')}</label>
240 <label for="extensions_hgsubversion">${_('largefiles extensions')}</label>
245 </div>
241 </div>
246 <div class="checkbox">
242 <div class="checkbox">
247 ${h.checkbox('extensions_hgsubversion','True')}
243 ${h.checkbox('extensions_hgsubversion','True')}
248 <label for="extensions_hgsubversion">${_('hgsubversion extensions')}</label>
244 <label for="extensions_hgsubversion">${_('hgsubversion extensions')}</label>
249 </div>
245 </div>
250 <span class="help-block">${_('Requires hgsubversion library installed. Allows clonning from svn remote locations')}</span>
246 <span class="help-block">${_('Requires hgsubversion library installed. Allows clonning from svn remote locations')}</span>
251 ##<div class="checkbox">
247 ##<div class="checkbox">
252 ## ${h.checkbox('extensions_hggit','True')}
248 ## ${h.checkbox('extensions_hggit','True')}
253 ## <label for="extensions_hggit">${_('hg-git extensions')}</label>
249 ## <label for="extensions_hggit">${_('hg-git extensions')}</label>
254 ##</div>
250 ##</div>
255 ##<span class="help-block">${_('Requires hg-git library installed. Allows clonning from git remote locations')}</span>
251 ##<span class="help-block">${_('Requires hg-git library installed. Allows clonning from git remote locations')}</span>
256 </div>
252 </div>
257 </div>
253 </div>
258 <div class="field">
254 <div class="field">
259 <div class="label">
255 <div class="label">
260 <label for="paths_root_path">${_('Repositories location')}:</label>
256 <label for="paths_root_path">${_('Repositories location')}:</label>
261 </div>
257 </div>
262 <div class="input">
258 <div class="input">
263 ${h.text('paths_root_path',size=30,readonly="readonly")}
259 ${h.text('paths_root_path',size=30,readonly="readonly")}
264 <span id="path_unlock" class="tooltip"
260 <span id="path_unlock" class="tooltip"
265 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
261 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
266 ${_('unlock')}</span>
262 ${_('unlock')}</span>
267 <span class="help-block">${_('Location where repositories are stored. After changing this value a restart, and rescan is required')}</span>
263 <span class="help-block">${_('Location where repositories are stored. After changing this value a restart, and rescan is required')}</span>
268 </div>
264 </div>
269 </div>
265 </div>
270
266
271 <div class="buttons">
267 <div class="buttons">
272 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
268 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
273 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
269 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
274 </div>
270 </div>
275 </div>
271 </div>
276 </div>
272 </div>
277 ${h.end_form()}
273 ${h.end_form()}
278
274
279 <script type="text/javascript">
275 <script type="text/javascript">
280 YAHOO.util.Event.onDOMReady(function(){
276 YAHOO.util.Event.onDOMReady(function(){
281 YAHOO.util.Event.addListener('path_unlock','click',function(){
277 YAHOO.util.Event.addListener('path_unlock','click',function(){
282 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
278 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
283 });
279 });
284 });
280 });
285 </script>
281 </script>
286
282
287 <h3>${_('Test Email')}</h3>
283 <h3>${_('Test Email')}</h3>
288 ${h.form(url('admin_setting', setting_id='email'),method='put')}
284 ${h.form(url('admin_setting', setting_id='email'),method='put')}
289 <div class="form">
285 <div class="form">
290 <!-- fields -->
286 <!-- fields -->
291
287
292 <div class="fields">
288 <div class="fields">
293 <div class="field">
289 <div class="field">
294 <div class="label">
290 <div class="label">
295 <label for="test_email">${_('Email to')}:</label>
291 <label for="test_email">${_('Email to')}:</label>
296 </div>
292 </div>
297 <div class="input">
293 <div class="input">
298 ${h.text('test_email',size=30)}
294 ${h.text('test_email',size=30)}
299 </div>
295 </div>
300 </div>
296 </div>
301
297
302 <div class="buttons">
298 <div class="buttons">
303 ${h.submit('send',_('Send'),class_="ui-btn large")}
299 ${h.submit('send',_('Send'),class_="ui-btn large")}
304 </div>
300 </div>
305 </div>
301 </div>
306 </div>
302 </div>
307 ${h.end_form()}
303 ${h.end_form()}
308
304
309 <h3>${_('System Info and Packages')}</h3>
305 <h3>${_('System Info and Packages')}</h3>
310 <div class="form">
306 <div class="form">
311 <div>
307 <div>
312 <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('show')} &darr;</h5>
308 <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('show')} &darr;</h5>
313 </div>
309 </div>
314 <div id="expand_modules_table" style="display:none">
310 <div id="expand_modules_table" style="display:none">
315 <h5>Python - ${c.py_version}</h5>
311 <h5>Python - ${c.py_version}</h5>
316 <h5>System - ${c.platform}</h5>
312 <h5>System - ${c.platform}</h5>
317
313
318 <table class="table" style="margin:0px 0px 0px 20px">
314 <table class="table" style="margin:0px 0px 0px 20px">
319 <colgroup>
315 <colgroup>
320 <col style="width:220px">
316 <col style="width:220px">
321 </colgroup>
317 </colgroup>
322 <tbody>
318 <tbody>
323 %for key, value in c.modules:
319 %for key, value in c.modules:
324 <tr>
320 <tr>
325 <th style="text-align: right;padding-right:5px;">${key}</th>
321 <th style="text-align: right;padding-right:5px;">${key}</th>
326 <td>${value}</td>
322 <td>${value}</td>
327 </tr>
323 </tr>
328 %endfor
324 %endfor
329 </tbody>
325 </tbody>
330 </table>
326 </table>
331 </div>
327 </div>
332 </div>
328 </div>
333
329
334 <script type="text/javascript">
330 <script type="text/javascript">
335 YUE.on('expand_modules','click',function(e){
331 YUE.on('expand_modules','click',function(e){
336 YUD.setStyle('expand_modules_table','display','');
332 YUD.setStyle('expand_modules_table','display','');
337 YUD.setStyle('expand_modules','display','none');
333 YUD.setStyle('expand_modules','display','none');
338 })
334 })
339 </script>
335 </script>
340
336
341 </div>
337 </div>
342 </%def>
338 </%def>
@@ -1,199 +1,207
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_(u'Home'),h.url('/'))}
8 ${h.link_to(_(u'Home'),h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
13 </%def>
13 </%def>
14
14
15 <%def name="main()">
15 <%def name="main()">
16
16
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 %if c.pull_request.is_closed():
22 %if c.pull_request.is_closed():
23 <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))} ${_('with status %s') % h.changeset_status_lbl(c.current_changeset_status)}</div>
23 <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))} ${_('with status %s') % h.changeset_status_lbl(c.current_changeset_status)}</div>
24 %endif
24 %endif
25 <h3>${_('Title')}: ${c.pull_request.title}</h3>
25 <h3>${_('Title')}: ${c.pull_request.title}</h3>
26
26
27 <div class="form">
27 <div class="form">
28 <div id="summary" class="fields">
28 <div id="summary" class="fields">
29 <div class="field">
29 <div class="field">
30 <div class="label-summary">
30 <div class="label-summary">
31 <label>${_('Status')}:</label>
31 <label>${_('Status')}:</label>
32 </div>
32 </div>
33 <div class="input">
33 <div class="input">
34 <div class="changeset-status-container" style="float:none;clear:both">
34 <div class="changeset-status-container" style="float:none;clear:both">
35 %if c.current_changeset_status:
35 %if c.current_changeset_status:
36 <div title="${_('Pull request status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
36 <div title="${_('Pull request status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
37 <div class="changeset-status-ico" style="padding:1px 4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
37 <div class="changeset-status-ico" style="padding:1px 4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
38 %endif
38 %endif
39 </div>
39 </div>
40 </div>
40 </div>
41 </div>
41 </div>
42 <div class="field">
42 <div class="field">
43 <div class="label-summary">
43 <div class="label-summary">
44 <label>${_('Still not reviewed by')}:</label>
44 <label>${_('Still not reviewed by')}:</label>
45 </div>
45 </div>
46 <div class="input">
46 <div class="input">
47 % if len(c.pull_request_pending_reviewers) > 0:
47 % if len(c.pull_request_pending_reviewers) > 0:
48 <div class="tooltip" title="${h.tooltip(','.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
48 <div class="tooltip" title="${h.tooltip(','.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
49 %else:
49 %else:
50 <div>${_('pull request was reviewed by all reviewers')}</div>
50 <div>${_('pull request was reviewed by all reviewers')}</div>
51 %endif
51 %endif
52 </div>
52 </div>
53 </div>
53 </div>
54 </div>
54 </div>
55 </div>
55 </div>
56 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
56 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
57 <div style="padding:4px 4px 10px 20px">
57 <div style="padding:4px 4px 10px 20px">
58 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
58 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
59 </div>
59 </div>
60
60
61 <div style="min-height:160px">
61 <div style="min-height:160px">
62 ##DIFF
62 ##DIFF
63 <div class="table" style="float:left;clear:none">
63 <div class="table" style="float:left;clear:none">
64 <div id="body" class="diffblock">
64 <div id="body" class="diffblock">
65 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
65 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
66 </div>
66 </div>
67 <div id="changeset_compare_view_content">
67 <div id="changeset_compare_view_content">
68 ##CS
68 ##CS
69 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
69 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
70 <%include file="/compare/compare_cs.html" />
70 <%include file="/compare/compare_cs.html" />
71
71
72 ## FILES
72 ## FILES
73 <div id="affected_files">
74 % if c.files:
73 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
75 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
74 <div class="cs_files">
76 <div class="cs_files">
75 %for fid, change, f, stat in c.files:
77 %for fid, change, f, stat in c.files:
76 <div class="cs_${change}">
78 <div class="cs_${change}">
77 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
79 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
78 <div class="changes">${h.fancy_file_stats(stat)}</div>
80 <div class="changes">${h.fancy_file_stats(stat)}</div>
79 </div>
81 </div>
80 %endfor
82 %endfor
81 </div>
83 </div>
84 %else:
85 <div class="ui-btn" style="text-align: center;margin-top:5px">${_('Click to load diff details')}</div>
86 %endif
87 </div>
82 </div>
88 </div>
83 </div>
89 </div>
84 ## REVIEWERS
90 ## REVIEWERS
85 <div style="float:left; border-left:1px dashed #eee">
91 <div style="float:left; border-left:1px dashed #eee">
86 <h4>${_('Pull request reviewers')}</h4>
92 <h4>${_('Pull request reviewers')}</h4>
87 <div id="reviewers" style="padding:0px 0px 0px 15px">
93 <div id="reviewers" style="padding:0px 0px 0px 15px">
88 ## members goes here !
94 ## members goes here !
89 <div class="group_members_wrap">
95 <div class="group_members_wrap">
90 <ul id="review_members" class="group_members">
96 <ul id="review_members" class="group_members">
91 %for member,status in c.pull_request_reviewers:
97 %for member,status in c.pull_request_reviewers:
92 <li id="reviewer_${member.user_id}">
98 <li id="reviewer_${member.user_id}">
93 <div class="reviewers_member">
99 <div class="reviewers_member">
94 <div style="float:left;padding:0px 3px 0px 0px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
100 <div style="float:left;padding:0px 3px 0px 0px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
95 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
101 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
96 </div>
102 </div>
97 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
103 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
98 <div style="float:left">${member.full_name} (${_('owner')})</div>
104 <div style="float:left">${member.full_name} (${_('owner')})</div>
99 <input type="hidden" value="${member.user_id}" name="review_members" />
105 <input type="hidden" value="${member.user_id}" name="review_members" />
100 %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id):
106 %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id):
101 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
107 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
102 %endif
108 %endif
103 </div>
109 </div>
104 </li>
110 </li>
105 %endfor
111 %endfor
106 </ul>
112 </ul>
107 </div>
113 </div>
108 %if not c.pull_request.is_closed():
114 %if not c.pull_request.is_closed():
109 <div class='ac'>
115 <div class='ac'>
110 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id:
116 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id:
111 <div class="reviewer_ac">
117 <div class="reviewer_ac">
112 ${h.text('user', class_='yui-ac-input')}
118 ${h.text('user', class_='yui-ac-input')}
113 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
119 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
114 <div id="reviewers_container"></div>
120 <div id="reviewers_container"></div>
115 </div>
121 </div>
116 <div style="padding:0px 10px">
122 <div style="padding:0px 10px">
117 <span id="update_pull_request" class="ui-btn xsmall">${_('save')}</span>
123 <span id="update_pull_request" class="ui-btn xsmall">${_('save')}</span>
118 </div>
124 </div>
119 %endif
125 %endif
120 </div>
126 </div>
121 %endif
127 %endif
122 </div>
128 </div>
123 </div>
129 </div>
124 </div>
130 </div>
125 <script>
131 <script>
126 var _USERS_AC_DATA = ${c.users_array|n};
132 var _USERS_AC_DATA = ${c.users_array|n};
127 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
133 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
128 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
134 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
129 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
135 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
130 AJAX_UPDATE_PULLREQUEST = "${url('pullrequest_update',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}"
136 AJAX_UPDATE_PULLREQUEST = "${url('pullrequest_update',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}"
131 </script>
137 </script>
132
138
133 ## diff block
139 ## diff block
140 <div id="diff_block_container" style="clear:both;">
134 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
141 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
135 %for fid, change, f, stat in c.files:
142 %for fid, change, f, stat in c.files:
136 ${diff_block.diff_block_simple([c.changes[fid]])}
143 ${diff_block.diff_block_simple([c.changes[fid]])}
137 %endfor
144 %endfor
145 </div>
138
146
139 ## template for inline comment form
147 ## template for inline comment form
140 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
148 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
141 ${comment.comment_inline_form()}
149 ${comment.comment_inline_form()}
142
150
143 ## render comments and inlines
151 ## render comments and inlines
144 ${comment.generate_comments()}
152 ${comment.generate_comments()}
145
153
146 % if not c.pull_request.is_closed():
154 % if not c.pull_request.is_closed():
147 ## main comment form and it status
155 ## main comment form and it status
148 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
156 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
149 pull_request_id=c.pull_request.pull_request_id),
157 pull_request_id=c.pull_request.pull_request_id),
150 c.current_changeset_status,
158 c.current_changeset_status,
151 close_btn=True)}
159 close_btn=True)}
152 %endif
160 %endif
153
161
154 <script type="text/javascript">
162 <script type="text/javascript">
155 YUE.onDOMReady(function(){
163 YUE.onDOMReady(function(){
156 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
164 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
157
165
158 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
166 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
159 var show = 'none';
167 var show = 'none';
160 var target = e.currentTarget;
168 var target = e.currentTarget;
161 if(target.checked){
169 if(target.checked){
162 var show = ''
170 var show = ''
163 }
171 }
164 var boxid = YUD.getAttribute(target,'id_for');
172 var boxid = YUD.getAttribute(target,'id_for');
165 var comments = YUQ('#{0} .inline-comments'.format(boxid));
173 var comments = YUQ('#{0} .inline-comments'.format(boxid));
166 for(c in comments){
174 for(c in comments){
167 YUD.setStyle(comments[c],'display',show);
175 YUD.setStyle(comments[c],'display',show);
168 }
176 }
169 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
177 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
170 for(c in btns){
178 for(c in btns){
171 YUD.setStyle(btns[c],'display',show);
179 YUD.setStyle(btns[c],'display',show);
172 }
180 }
173 })
181 })
174
182
175 YUE.on(YUQ('.line'),'click',function(e){
183 YUE.on(YUQ('.line'),'click',function(e){
176 var tr = e.currentTarget;
184 var tr = e.currentTarget;
177 injectInlineForm(tr);
185 injectInlineForm(tr);
178 });
186 });
179
187
180 // inject comments into they proper positions
188 // inject comments into they proper positions
181 var file_comments = YUQ('.inline-comment-placeholder');
189 var file_comments = YUQ('.inline-comment-placeholder');
182 renderInlineComments(file_comments);
190 renderInlineComments(file_comments);
183
191
184 YUE.on(YUD.get('update_pull_request'),'click',function(e){
192 YUE.on(YUD.get('update_pull_request'),'click',function(e){
185
193
186 var reviewers_ids = [];
194 var reviewers_ids = [];
187 var ids = YUQ('#review_members input');
195 var ids = YUQ('#review_members input');
188 for(var i=0; i<ids.length;i++){
196 for(var i=0; i<ids.length;i++){
189 var id = ids[i].value
197 var id = ids[i].value
190 reviewers_ids.push(id);
198 reviewers_ids.push(id);
191 }
199 }
192 updateReviewers(reviewers_ids);
200 updateReviewers(reviewers_ids);
193 })
201 })
194 })
202 })
195 </script>
203 </script>
196
204
197 </div>
205 </div>
198
206
199 </%def>
207 </%def>
General Comments 0
You need to be logged in to leave comments. Login now