##// END OF EJS Templates
white space cleanup
marcink -
r1944:5fc9c920 beta
parent child Browse files
Show More
@@ -1,414 +1,414 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.celerylib.tasks
3 rhodecode.lib.celerylib.tasks
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode task modules, containing all task that suppose to be run
6 RhodeCode task modules, containing all task that suppose to be run
7 by celery daemon
7 by celery daemon
8
8
9 :created_on: Oct 6, 2010
9 :created_on: Oct 6, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 from celery.decorators import task
26 from celery.decorators import task
27
27
28 import os
28 import os
29 import traceback
29 import traceback
30 import logging
30 import logging
31 from os.path import join as jn
31 from os.path import join as jn
32
32
33 from time import mktime
33 from time import mktime
34 from operator import itemgetter
34 from operator import itemgetter
35 from string import lower
35 from string import lower
36
36
37 from pylons import config, url
37 from pylons import config, url
38 from pylons.i18n.translation import _
38 from pylons.i18n.translation import _
39
39
40 from vcs import get_backend
40 from vcs import get_backend
41
41
42 from rhodecode import CELERY_ON
42 from rhodecode import CELERY_ON
43 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
43 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
44 from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
44 from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
45 str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
45 str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
46 from rhodecode.lib.helpers import person
46 from rhodecode.lib.helpers import person
47 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
47 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
48 from rhodecode.lib.utils import add_cache, action_logger
48 from rhodecode.lib.utils import add_cache, action_logger
49 from rhodecode.lib.compat import json, OrderedDict
49 from rhodecode.lib.compat import json, OrderedDict
50
50
51 from rhodecode.model.db import Statistics, Repository, User
51 from rhodecode.model.db import Statistics, Repository, User
52
52
53
53
54 add_cache(config)
54 add_cache(config)
55
55
56 __all__ = ['whoosh_index', 'get_commits_stats',
56 __all__ = ['whoosh_index', 'get_commits_stats',
57 'reset_user_password', 'send_email']
57 'reset_user_password', 'send_email']
58
58
59
59
60 def get_logger(cls):
60 def get_logger(cls):
61 if CELERY_ON:
61 if CELERY_ON:
62 try:
62 try:
63 log = cls.get_logger()
63 log = cls.get_logger()
64 except:
64 except:
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66 else:
66 else:
67 log = logging.getLogger(__name__)
67 log = logging.getLogger(__name__)
68
68
69 return log
69 return log
70
70
71
71
72 @task(ignore_result=True)
72 @task(ignore_result=True)
73 @locked_task
73 @locked_task
74 @dbsession
74 @dbsession
75 def whoosh_index(repo_location, full_index):
75 def whoosh_index(repo_location, full_index):
76 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
76 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
77 log = whoosh_index.get_logger(whoosh_index)
77 log = whoosh_index.get_logger(whoosh_index)
78 DBS = get_session()
78 DBS = get_session()
79
79
80 index_location = config['index_dir']
80 index_location = config['index_dir']
81 WhooshIndexingDaemon(index_location=index_location,
81 WhooshIndexingDaemon(index_location=index_location,
82 repo_location=repo_location, sa=DBS)\
82 repo_location=repo_location, sa=DBS)\
83 .run(full_index=full_index)
83 .run(full_index=full_index)
84
84
85
85
86 @task(ignore_result=True)
86 @task(ignore_result=True)
87 @dbsession
87 @dbsession
88 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
88 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
89 log = get_logger(get_commits_stats)
89 log = get_logger(get_commits_stats)
90 DBS = get_session()
90 DBS = get_session()
91 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
91 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
92 ts_max_y)
92 ts_max_y)
93 lockkey_path = config['here']
93 lockkey_path = config['here']
94
94
95 log.info('running task with lockkey %s', lockkey)
95 log.info('running task with lockkey %s', lockkey)
96
96
97 try:
97 try:
98 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
98 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
99
99
100 # for js data compatibilty cleans the key for person from '
100 # for js data compatibilty cleans the key for person from '
101 akc = lambda k: person(k).replace('"', "")
101 akc = lambda k: person(k).replace('"', "")
102
102
103 co_day_auth_aggr = {}
103 co_day_auth_aggr = {}
104 commits_by_day_aggregate = {}
104 commits_by_day_aggregate = {}
105 repo = Repository.get_by_repo_name(repo_name)
105 repo = Repository.get_by_repo_name(repo_name)
106 if repo is None:
106 if repo is None:
107 return True
107 return True
108
108
109 repo = repo.scm_instance
109 repo = repo.scm_instance
110 repo_size = repo.count()
110 repo_size = repo.count()
111 # return if repo have no revisions
111 # return if repo have no revisions
112 if repo_size < 1:
112 if repo_size < 1:
113 lock.release()
113 lock.release()
114 return True
114 return True
115
115
116 skip_date_limit = True
116 skip_date_limit = True
117 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
117 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
118 last_rev = None
118 last_rev = None
119 last_cs = None
119 last_cs = None
120 timegetter = itemgetter('time')
120 timegetter = itemgetter('time')
121
121
122 dbrepo = DBS.query(Repository)\
122 dbrepo = DBS.query(Repository)\
123 .filter(Repository.repo_name == repo_name).scalar()
123 .filter(Repository.repo_name == repo_name).scalar()
124 cur_stats = DBS.query(Statistics)\
124 cur_stats = DBS.query(Statistics)\
125 .filter(Statistics.repository == dbrepo).scalar()
125 .filter(Statistics.repository == dbrepo).scalar()
126
126
127 if cur_stats is not None:
127 if cur_stats is not None:
128 last_rev = cur_stats.stat_on_revision
128 last_rev = cur_stats.stat_on_revision
129
129
130 if last_rev == repo.get_changeset().revision and repo_size > 1:
130 if last_rev == repo.get_changeset().revision and repo_size > 1:
131 # pass silently without any work if we're not on first revision or
131 # pass silently without any work if we're not on first revision or
132 # current state of parsing revision(from db marker) is the
132 # current state of parsing revision(from db marker) is the
133 # last revision
133 # last revision
134 lock.release()
134 lock.release()
135 return True
135 return True
136
136
137 if cur_stats:
137 if cur_stats:
138 commits_by_day_aggregate = OrderedDict(json.loads(
138 commits_by_day_aggregate = OrderedDict(json.loads(
139 cur_stats.commit_activity_combined))
139 cur_stats.commit_activity_combined))
140 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
140 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
141
141
142 log.debug('starting parsing %s', parse_limit)
142 log.debug('starting parsing %s', parse_limit)
143 lmktime = mktime
143 lmktime = mktime
144
144
145 last_rev = last_rev + 1 if last_rev >= 0 else 0
145 last_rev = last_rev + 1 if last_rev >= 0 else 0
146 log.debug('Getting revisions from %s to %s' % (
146 log.debug('Getting revisions from %s to %s' % (
147 last_rev, last_rev + parse_limit)
147 last_rev, last_rev + parse_limit)
148 )
148 )
149 for cs in repo[last_rev:last_rev + parse_limit]:
149 for cs in repo[last_rev:last_rev + parse_limit]:
150 last_cs = cs # remember last parsed changeset
150 last_cs = cs # remember last parsed changeset
151 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
151 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
152 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
152 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
153
153
154 if akc(cs.author) in co_day_auth_aggr:
154 if akc(cs.author) in co_day_auth_aggr:
155 try:
155 try:
156 l = [timegetter(x) for x in
156 l = [timegetter(x) for x in
157 co_day_auth_aggr[akc(cs.author)]['data']]
157 co_day_auth_aggr[akc(cs.author)]['data']]
158 time_pos = l.index(k)
158 time_pos = l.index(k)
159 except ValueError:
159 except ValueError:
160 time_pos = False
160 time_pos = False
161
161
162 if time_pos >= 0 and time_pos is not False:
162 if time_pos >= 0 and time_pos is not False:
163
163
164 datadict = \
164 datadict = \
165 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
165 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
166
166
167 datadict["commits"] += 1
167 datadict["commits"] += 1
168 datadict["added"] += len(cs.added)
168 datadict["added"] += len(cs.added)
169 datadict["changed"] += len(cs.changed)
169 datadict["changed"] += len(cs.changed)
170 datadict["removed"] += len(cs.removed)
170 datadict["removed"] += len(cs.removed)
171
171
172 else:
172 else:
173 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
173 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
174
174
175 datadict = {"time": k,
175 datadict = {"time": k,
176 "commits": 1,
176 "commits": 1,
177 "added": len(cs.added),
177 "added": len(cs.added),
178 "changed": len(cs.changed),
178 "changed": len(cs.changed),
179 "removed": len(cs.removed),
179 "removed": len(cs.removed),
180 }
180 }
181 co_day_auth_aggr[akc(cs.author)]['data']\
181 co_day_auth_aggr[akc(cs.author)]['data']\
182 .append(datadict)
182 .append(datadict)
183
183
184 else:
184 else:
185 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
185 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
186 co_day_auth_aggr[akc(cs.author)] = {
186 co_day_auth_aggr[akc(cs.author)] = {
187 "label": akc(cs.author),
187 "label": akc(cs.author),
188 "data": [{"time":k,
188 "data": [{"time":k,
189 "commits":1,
189 "commits":1,
190 "added":len(cs.added),
190 "added":len(cs.added),
191 "changed":len(cs.changed),
191 "changed":len(cs.changed),
192 "removed":len(cs.removed),
192 "removed":len(cs.removed),
193 }],
193 }],
194 "schema": ["commits"],
194 "schema": ["commits"],
195 }
195 }
196
196
197 #gather all data by day
197 #gather all data by day
198 if k in commits_by_day_aggregate:
198 if k in commits_by_day_aggregate:
199 commits_by_day_aggregate[k] += 1
199 commits_by_day_aggregate[k] += 1
200 else:
200 else:
201 commits_by_day_aggregate[k] = 1
201 commits_by_day_aggregate[k] = 1
202
202
203 overview_data = sorted(commits_by_day_aggregate.items(),
203 overview_data = sorted(commits_by_day_aggregate.items(),
204 key=itemgetter(0))
204 key=itemgetter(0))
205
205
206 if not co_day_auth_aggr:
206 if not co_day_auth_aggr:
207 co_day_auth_aggr[akc(repo.contact)] = {
207 co_day_auth_aggr[akc(repo.contact)] = {
208 "label": akc(repo.contact),
208 "label": akc(repo.contact),
209 "data": [0, 1],
209 "data": [0, 1],
210 "schema": ["commits"],
210 "schema": ["commits"],
211 }
211 }
212
212
213 stats = cur_stats if cur_stats else Statistics()
213 stats = cur_stats if cur_stats else Statistics()
214 stats.commit_activity = json.dumps(co_day_auth_aggr)
214 stats.commit_activity = json.dumps(co_day_auth_aggr)
215 stats.commit_activity_combined = json.dumps(overview_data)
215 stats.commit_activity_combined = json.dumps(overview_data)
216
216
217 log.debug('last revison %s', last_rev)
217 log.debug('last revison %s', last_rev)
218 leftovers = len(repo.revisions[last_rev:])
218 leftovers = len(repo.revisions[last_rev:])
219 log.debug('revisions to parse %s', leftovers)
219 log.debug('revisions to parse %s', leftovers)
220
220
221 if last_rev == 0 or leftovers < parse_limit:
221 if last_rev == 0 or leftovers < parse_limit:
222 log.debug('getting code trending stats')
222 log.debug('getting code trending stats')
223 stats.languages = json.dumps(__get_codes_stats(repo_name))
223 stats.languages = json.dumps(__get_codes_stats(repo_name))
224
224
225 try:
225 try:
226 stats.repository = dbrepo
226 stats.repository = dbrepo
227 stats.stat_on_revision = last_cs.revision if last_cs else 0
227 stats.stat_on_revision = last_cs.revision if last_cs else 0
228 DBS.add(stats)
228 DBS.add(stats)
229 DBS.commit()
229 DBS.commit()
230 except:
230 except:
231 log.error(traceback.format_exc())
231 log.error(traceback.format_exc())
232 DBS.rollback()
232 DBS.rollback()
233 lock.release()
233 lock.release()
234 return False
234 return False
235
235
236 #final release
236 #final release
237 lock.release()
237 lock.release()
238
238
239 #execute another task if celery is enabled
239 #execute another task if celery is enabled
240 if len(repo.revisions) > 1 and CELERY_ON:
240 if len(repo.revisions) > 1 and CELERY_ON:
241 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
241 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
242 return True
242 return True
243 except LockHeld:
243 except LockHeld:
244 log.info('LockHeld')
244 log.info('LockHeld')
245 return 'Task with key %s already running' % lockkey
245 return 'Task with key %s already running' % lockkey
246
246
247 @task(ignore_result=True)
247 @task(ignore_result=True)
248 @dbsession
248 @dbsession
249 def send_password_link(user_email):
249 def send_password_link(user_email):
250 from rhodecode.model.notification import EmailNotificationModel
250 from rhodecode.model.notification import EmailNotificationModel
251
251
252 log = get_logger(send_password_link)
252 log = get_logger(send_password_link)
253 DBS = get_session()
253 DBS = get_session()
254
254
255 try:
255 try:
256 user = User.get_by_email(user_email)
256 user = User.get_by_email(user_email)
257 if user:
257 if user:
258 log.debug('password reset user found %s' % user)
258 log.debug('password reset user found %s' % user)
259 link = url('reset_password_confirmation', key=user.api_key,
259 link = url('reset_password_confirmation', key=user.api_key,
260 qualified=True)
260 qualified=True)
261 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
261 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
262 body = EmailNotificationModel().get_email_tmpl(reg_type,
262 body = EmailNotificationModel().get_email_tmpl(reg_type,
263 **{'user':user.short_contact,
263 **{'user':user.short_contact,
264 'reset_url':link})
264 'reset_url':link})
265 log.debug('sending email')
265 log.debug('sending email')
266 run_task(send_email, user_email,
266 run_task(send_email, user_email,
267 _("password reset link"), body)
267 _("password reset link"), body)
268 log.info('send new password mail to %s', user_email)
268 log.info('send new password mail to %s', user_email)
269 else:
269 else:
270 log.debug("password reset email %s not found" % user_email)
270 log.debug("password reset email %s not found" % user_email)
271 except:
271 except:
272 log.error(traceback.format_exc())
272 log.error(traceback.format_exc())
273 return False
273 return False
274
274
275 return True
275 return True
276
276
277 @task(ignore_result=True)
277 @task(ignore_result=True)
278 @dbsession
278 @dbsession
279 def reset_user_password(user_email):
279 def reset_user_password(user_email):
280 from rhodecode.lib import auth
280 from rhodecode.lib import auth
281
281
282 log = get_logger(reset_user_password)
282 log = get_logger(reset_user_password)
283 DBS = get_session()
283 DBS = get_session()
284
284
285 try:
285 try:
286 try:
286 try:
287 user = User.get_by_email(user_email)
287 user = User.get_by_email(user_email)
288 new_passwd = auth.PasswordGenerator().gen_password(8,
288 new_passwd = auth.PasswordGenerator().gen_password(8,
289 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
289 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
290 if user:
290 if user:
291 user.password = auth.get_crypt_password(new_passwd)
291 user.password = auth.get_crypt_password(new_passwd)
292 user.api_key = auth.generate_api_key(user.username)
292 user.api_key = auth.generate_api_key(user.username)
293 DBS.add(user)
293 DBS.add(user)
294 DBS.commit()
294 DBS.commit()
295 log.info('change password for %s', user_email)
295 log.info('change password for %s', user_email)
296 if new_passwd is None:
296 if new_passwd is None:
297 raise Exception('unable to generate new password')
297 raise Exception('unable to generate new password')
298 except:
298 except:
299 log.error(traceback.format_exc())
299 log.error(traceback.format_exc())
300 DBS.rollback()
300 DBS.rollback()
301
301
302 run_task(send_email, user_email,
302 run_task(send_email, user_email,
303 'Your new password',
303 'Your new password',
304 'Your new RhodeCode password:%s' % (new_passwd))
304 'Your new RhodeCode password:%s' % (new_passwd))
305 log.info('send new password mail to %s', user_email)
305 log.info('send new password mail to %s', user_email)
306
306
307 except:
307 except:
308 log.error('Failed to update user password')
308 log.error('Failed to update user password')
309 log.error(traceback.format_exc())
309 log.error(traceback.format_exc())
310
310
311 return True
311 return True
312
312
313
313
314 @task(ignore_result=True)
314 @task(ignore_result=True)
315 @dbsession
315 @dbsession
316 def send_email(recipients, subject, body, html_body=''):
316 def send_email(recipients, subject, body, html_body=''):
317 """
317 """
318 Sends an email with defined parameters from the .ini files.
318 Sends an email with defined parameters from the .ini files.
319
319
320 :param recipients: list of recipients, it this is empty the defined email
320 :param recipients: list of recipients, it this is empty the defined email
321 address from field 'email_to' is used instead
321 address from field 'email_to' is used instead
322 :param subject: subject of the mail
322 :param subject: subject of the mail
323 :param body: body of the mail
323 :param body: body of the mail
324 :param html_body: html version of body
324 :param html_body: html version of body
325 """
325 """
326 log = get_logger(send_email)
326 log = get_logger(send_email)
327 DBS = get_session()
327 DBS = get_session()
328
328
329 email_config = config
329 email_config = config
330 subject = "%s %s" % (email_config.get('email_prefix'), subject)
330 subject = "%s %s" % (email_config.get('email_prefix'), subject)
331 if not recipients:
331 if not recipients:
332 # if recipients are not defined we send to email_config + all admins
332 # if recipients are not defined we send to email_config + all admins
333 admins = [u.email for u in User.query()
333 admins = [u.email for u in User.query()
334 .filter(User.admin == True).all()]
334 .filter(User.admin == True).all()]
335 recipients = [email_config.get('email_to')] + admins
335 recipients = [email_config.get('email_to')] + admins
336
336
337 mail_from = email_config.get('app_email_from', 'RhodeCode')
337 mail_from = email_config.get('app_email_from', 'RhodeCode')
338 user = email_config.get('smtp_username')
338 user = email_config.get('smtp_username')
339 passwd = email_config.get('smtp_password')
339 passwd = email_config.get('smtp_password')
340 mail_server = email_config.get('smtp_server')
340 mail_server = email_config.get('smtp_server')
341 mail_port = email_config.get('smtp_port')
341 mail_port = email_config.get('smtp_port')
342 tls = str2bool(email_config.get('smtp_use_tls'))
342 tls = str2bool(email_config.get('smtp_use_tls'))
343 ssl = str2bool(email_config.get('smtp_use_ssl'))
343 ssl = str2bool(email_config.get('smtp_use_ssl'))
344 debug = str2bool(config.get('debug'))
344 debug = str2bool(config.get('debug'))
345 smtp_auth = email_config.get('smtp_auth')
345 smtp_auth = email_config.get('smtp_auth')
346
346
347 try:
347 try:
348 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
348 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
349 mail_port, ssl, tls, debug=debug)
349 mail_port, ssl, tls, debug=debug)
350 m.send(recipients, subject, body, html_body)
350 m.send(recipients, subject, body, html_body)
351 except:
351 except:
352 log.error('Mail sending failed')
352 log.error('Mail sending failed')
353 log.error(traceback.format_exc())
353 log.error(traceback.format_exc())
354 return False
354 return False
355 return True
355 return True
356
356
357
357
358 @task(ignore_result=True)
358 @task(ignore_result=True)
359 @dbsession
359 @dbsession
360 def create_repo_fork(form_data, cur_user):
360 def create_repo_fork(form_data, cur_user):
361 """
361 """
362 Creates a fork of repository using interval VCS methods
362 Creates a fork of repository using interval VCS methods
363
363
364 :param form_data:
364 :param form_data:
365 :param cur_user:
365 :param cur_user:
366 """
366 """
367 from rhodecode.model.repo import RepoModel
367 from rhodecode.model.repo import RepoModel
368
368
369 log = get_logger(create_repo_fork)
369 log = get_logger(create_repo_fork)
370 DBS = get_session()
370 DBS = get_session()
371
371
372 base_path = Repository.base_path()
372 base_path = Repository.base_path()
373
373
374 RepoModel(DBS).create(form_data, cur_user, just_db=True, fork=True)
374 RepoModel(DBS).create(form_data, cur_user, just_db=True, fork=True)
375
375
376 alias = form_data['repo_type']
376 alias = form_data['repo_type']
377 org_repo_name = form_data['org_path']
377 org_repo_name = form_data['org_path']
378 fork_name = form_data['repo_name_full']
378 fork_name = form_data['repo_name_full']
379 update_after_clone = form_data['update_after_clone']
379 update_after_clone = form_data['update_after_clone']
380 source_repo_path = os.path.join(base_path, org_repo_name)
380 source_repo_path = os.path.join(base_path, org_repo_name)
381 destination_fork_path = os.path.join(base_path, fork_name)
381 destination_fork_path = os.path.join(base_path, fork_name)
382
382
383 log.info('creating fork of %s as %s', source_repo_path,
383 log.info('creating fork of %s as %s', source_repo_path,
384 destination_fork_path)
384 destination_fork_path)
385 backend = get_backend(alias)
385 backend = get_backend(alias)
386 backend(safe_str(destination_fork_path), create=True,
386 backend(safe_str(destination_fork_path), create=True,
387 src_url=safe_str(source_repo_path),
387 src_url=safe_str(source_repo_path),
388 update_after_clone=update_after_clone)
388 update_after_clone=update_after_clone)
389 action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
389 action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
390 org_repo_name, '', DBS)
390 org_repo_name, '', DBS)
391
391
392 action_logger(cur_user, 'user_created_fork:%s' % fork_name,
392 action_logger(cur_user, 'user_created_fork:%s' % fork_name,
393 fork_name, '', DBS)
393 fork_name, '', DBS)
394 # finally commit at latest possible stage
394 # finally commit at latest possible stage
395 DBS.commit()
395 DBS.commit()
396
396
397 def __get_codes_stats(repo_name):
397 def __get_codes_stats(repo_name):
398 repo = Repository.get_by_repo_name(repo_name).scm_instance
398 repo = Repository.get_by_repo_name(repo_name).scm_instance
399
399
400 tip = repo.get_changeset()
400 tip = repo.get_changeset()
401 code_stats = {}
401 code_stats = {}
402
402
403 def aggregate(cs):
403 def aggregate(cs):
404 for f in cs[2]:
404 for f in cs[2]:
405 ext = lower(f.extension)
405 ext = lower(f.extension)
406 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
406 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
407 if ext in code_stats:
407 if ext in code_stats:
408 code_stats[ext] += 1
408 code_stats[ext] += 1
409 else:
409 else:
410 code_stats[ext] = 1
410 code_stats[ext] = 1
411
411
412 map(aggregate, tip.walk('/'))
412 map(aggregate, tip.walk('/'))
413
413
414 return code_stats or {}
414 return code_stats or {}
@@ -1,846 +1,846 b''
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
12
13 from datetime import datetime
13 from datetime import datetime
14 from pygments.formatters.html import HtmlFormatter
14 from pygments.formatters.html import HtmlFormatter
15 from pygments import highlight as code_highlight
15 from pygments import highlight as code_highlight
16 from pylons import url, request, config
16 from pylons import url, request, config
17 from pylons.i18n.translation import _, ungettext
17 from pylons.i18n.translation import _, ungettext
18 from hashlib import md5
18 from hashlib import md5
19
19
20 from webhelpers.html import literal, HTML, escape
20 from webhelpers.html import literal, HTML, escape
21 from webhelpers.html.tools import *
21 from webhelpers.html.tools import *
22 from webhelpers.html.builder import make_tag
22 from webhelpers.html.builder import make_tag
23 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
23 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
24 end_form, file, form, hidden, image, javascript_link, link_to, \
24 end_form, file, form, hidden, image, javascript_link, link_to, \
25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
26 submit, text, password, textarea, title, ul, xml_declaration, radio
26 submit, text, password, textarea, title, ul, xml_declaration, radio
27 from webhelpers.html.tools import auto_link, button_to, highlight, \
27 from webhelpers.html.tools import auto_link, button_to, highlight, \
28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
29 from webhelpers.number import format_byte_size, format_bit_size
29 from webhelpers.number import format_byte_size, format_bit_size
30 from webhelpers.pylonslib import Flash as _Flash
30 from webhelpers.pylonslib import Flash as _Flash
31 from webhelpers.pylonslib.secure_form import secure_form
31 from webhelpers.pylonslib.secure_form import secure_form
32 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
32 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
33 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
33 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
34 replace_whitespace, urlify, truncate, wrap_paragraphs
34 replace_whitespace, urlify, truncate, wrap_paragraphs
35 from webhelpers.date import time_ago_in_words
35 from webhelpers.date import time_ago_in_words
36 from webhelpers.paginate import Page
36 from webhelpers.paginate import Page
37 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
37 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
38 convert_boolean_attrs, NotGiven, _make_safe_id_component
38 convert_boolean_attrs, NotGiven, _make_safe_id_component
39
39
40 from rhodecode.lib.annotate import annotate_highlight
40 from rhodecode.lib.annotate import annotate_highlight
41 from rhodecode.lib.utils import repo_name_slug
41 from rhodecode.lib.utils import repo_name_slug
42 from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
42 from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
43 from rhodecode.lib.markup_renderer import MarkupRenderer
43 from rhodecode.lib.markup_renderer import MarkupRenderer
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
48 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
49 """
49 """
50 Reset button
50 Reset button
51 """
51 """
52 _set_input_attrs(attrs, type, name, value)
52 _set_input_attrs(attrs, type, name, value)
53 _set_id_attr(attrs, id, name)
53 _set_id_attr(attrs, id, name)
54 convert_boolean_attrs(attrs, ["disabled"])
54 convert_boolean_attrs(attrs, ["disabled"])
55 return HTML.input(**attrs)
55 return HTML.input(**attrs)
56
56
57 reset = _reset
57 reset = _reset
58 safeid = _make_safe_id_component
58 safeid = _make_safe_id_component
59
59
60
60
61 def FID(raw_id, path):
61 def FID(raw_id, path):
62 """
62 """
63 Creates a uniqe ID for filenode based on it's hash of path and revision
63 Creates a uniqe ID for filenode based on it's hash of path and revision
64 it's safe to use in urls
64 it's safe to use in urls
65
65
66 :param raw_id:
66 :param raw_id:
67 :param path:
67 :param path:
68 """
68 """
69
69
70 return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
70 return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
71
71
72
72
73 def get_token():
73 def get_token():
74 """Return the current authentication token, creating one if one doesn't
74 """Return the current authentication token, creating one if one doesn't
75 already exist.
75 already exist.
76 """
76 """
77 token_key = "_authentication_token"
77 token_key = "_authentication_token"
78 from pylons import session
78 from pylons import session
79 if not token_key in session:
79 if not token_key in session:
80 try:
80 try:
81 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
81 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
82 except AttributeError: # Python < 2.4
82 except AttributeError: # Python < 2.4
83 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
83 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
84 session[token_key] = token
84 session[token_key] = token
85 if hasattr(session, 'save'):
85 if hasattr(session, 'save'):
86 session.save()
86 session.save()
87 return session[token_key]
87 return session[token_key]
88
88
89 class _GetError(object):
89 class _GetError(object):
90 """Get error from form_errors, and represent it as span wrapped error
90 """Get error from form_errors, and represent it as span wrapped error
91 message
91 message
92
92
93 :param field_name: field to fetch errors for
93 :param field_name: field to fetch errors for
94 :param form_errors: form errors dict
94 :param form_errors: form errors dict
95 """
95 """
96
96
97 def __call__(self, field_name, form_errors):
97 def __call__(self, field_name, form_errors):
98 tmpl = """<span class="error_msg">%s</span>"""
98 tmpl = """<span class="error_msg">%s</span>"""
99 if form_errors and form_errors.has_key(field_name):
99 if form_errors and form_errors.has_key(field_name):
100 return literal(tmpl % form_errors.get(field_name))
100 return literal(tmpl % form_errors.get(field_name))
101
101
102 get_error = _GetError()
102 get_error = _GetError()
103
103
104 class _ToolTip(object):
104 class _ToolTip(object):
105
105
106 def __call__(self, tooltip_title, trim_at=50):
106 def __call__(self, tooltip_title, trim_at=50):
107 """Special function just to wrap our text into nice formatted
107 """Special function just to wrap our text into nice formatted
108 autowrapped text
108 autowrapped text
109
109
110 :param tooltip_title:
110 :param tooltip_title:
111 """
111 """
112 return escape(tooltip_title)
112 return escape(tooltip_title)
113 tooltip = _ToolTip()
113 tooltip = _ToolTip()
114
114
115 class _FilesBreadCrumbs(object):
115 class _FilesBreadCrumbs(object):
116
116
117 def __call__(self, repo_name, rev, paths):
117 def __call__(self, repo_name, rev, paths):
118 if isinstance(paths, str):
118 if isinstance(paths, str):
119 paths = safe_unicode(paths)
119 paths = safe_unicode(paths)
120 url_l = [link_to(repo_name, url('files_home',
120 url_l = [link_to(repo_name, url('files_home',
121 repo_name=repo_name,
121 repo_name=repo_name,
122 revision=rev, f_path=''))]
122 revision=rev, f_path=''))]
123 paths_l = paths.split('/')
123 paths_l = paths.split('/')
124 for cnt, p in enumerate(paths_l):
124 for cnt, p in enumerate(paths_l):
125 if p != '':
125 if p != '':
126 url_l.append(link_to(p,
126 url_l.append(link_to(p,
127 url('files_home',
127 url('files_home',
128 repo_name=repo_name,
128 repo_name=repo_name,
129 revision=rev,
129 revision=rev,
130 f_path='/'.join(paths_l[:cnt + 1])
130 f_path='/'.join(paths_l[:cnt + 1])
131 )
131 )
132 )
132 )
133 )
133 )
134
134
135 return literal('/'.join(url_l))
135 return literal('/'.join(url_l))
136
136
137 files_breadcrumbs = _FilesBreadCrumbs()
137 files_breadcrumbs = _FilesBreadCrumbs()
138
138
139 class CodeHtmlFormatter(HtmlFormatter):
139 class CodeHtmlFormatter(HtmlFormatter):
140 """My code Html Formatter for source codes
140 """My code Html Formatter for source codes
141 """
141 """
142
142
143 def wrap(self, source, outfile):
143 def wrap(self, source, outfile):
144 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
144 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
145
145
146 def _wrap_code(self, source):
146 def _wrap_code(self, source):
147 for cnt, it in enumerate(source):
147 for cnt, it in enumerate(source):
148 i, t = it
148 i, t = it
149 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
149 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
150 yield i, t
150 yield i, t
151
151
152 def _wrap_tablelinenos(self, inner):
152 def _wrap_tablelinenos(self, inner):
153 dummyoutfile = StringIO.StringIO()
153 dummyoutfile = StringIO.StringIO()
154 lncount = 0
154 lncount = 0
155 for t, line in inner:
155 for t, line in inner:
156 if t:
156 if t:
157 lncount += 1
157 lncount += 1
158 dummyoutfile.write(line)
158 dummyoutfile.write(line)
159
159
160 fl = self.linenostart
160 fl = self.linenostart
161 mw = len(str(lncount + fl - 1))
161 mw = len(str(lncount + fl - 1))
162 sp = self.linenospecial
162 sp = self.linenospecial
163 st = self.linenostep
163 st = self.linenostep
164 la = self.lineanchors
164 la = self.lineanchors
165 aln = self.anchorlinenos
165 aln = self.anchorlinenos
166 nocls = self.noclasses
166 nocls = self.noclasses
167 if sp:
167 if sp:
168 lines = []
168 lines = []
169
169
170 for i in range(fl, fl + lncount):
170 for i in range(fl, fl + lncount):
171 if i % st == 0:
171 if i % st == 0:
172 if i % sp == 0:
172 if i % sp == 0:
173 if aln:
173 if aln:
174 lines.append('<a href="#%s%d" class="special">%*d</a>' %
174 lines.append('<a href="#%s%d" class="special">%*d</a>' %
175 (la, i, mw, i))
175 (la, i, mw, i))
176 else:
176 else:
177 lines.append('<span class="special">%*d</span>' % (mw, i))
177 lines.append('<span class="special">%*d</span>' % (mw, i))
178 else:
178 else:
179 if aln:
179 if aln:
180 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
180 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
181 else:
181 else:
182 lines.append('%*d' % (mw, i))
182 lines.append('%*d' % (mw, i))
183 else:
183 else:
184 lines.append('')
184 lines.append('')
185 ls = '\n'.join(lines)
185 ls = '\n'.join(lines)
186 else:
186 else:
187 lines = []
187 lines = []
188 for i in range(fl, fl + lncount):
188 for i in range(fl, fl + lncount):
189 if i % st == 0:
189 if i % st == 0:
190 if aln:
190 if aln:
191 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
191 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
192 else:
192 else:
193 lines.append('%*d' % (mw, i))
193 lines.append('%*d' % (mw, i))
194 else:
194 else:
195 lines.append('')
195 lines.append('')
196 ls = '\n'.join(lines)
196 ls = '\n'.join(lines)
197
197
198 # in case you wonder about the seemingly redundant <div> here: since the
198 # in case you wonder about the seemingly redundant <div> here: since the
199 # content in the other cell also is wrapped in a div, some browsers in
199 # content in the other cell also is wrapped in a div, some browsers in
200 # some configurations seem to mess up the formatting...
200 # some configurations seem to mess up the formatting...
201 if nocls:
201 if nocls:
202 yield 0, ('<table class="%stable">' % self.cssclass +
202 yield 0, ('<table class="%stable">' % self.cssclass +
203 '<tr><td><div class="linenodiv" '
203 '<tr><td><div class="linenodiv" '
204 'style="background-color: #f0f0f0; padding-right: 10px">'
204 'style="background-color: #f0f0f0; padding-right: 10px">'
205 '<pre style="line-height: 125%">' +
205 '<pre style="line-height: 125%">' +
206 ls + '</pre></div></td><td id="hlcode" class="code">')
206 ls + '</pre></div></td><td id="hlcode" class="code">')
207 else:
207 else:
208 yield 0, ('<table class="%stable">' % self.cssclass +
208 yield 0, ('<table class="%stable">' % self.cssclass +
209 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
209 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
210 ls + '</pre></div></td><td id="hlcode" class="code">')
210 ls + '</pre></div></td><td id="hlcode" class="code">')
211 yield 0, dummyoutfile.getvalue()
211 yield 0, dummyoutfile.getvalue()
212 yield 0, '</td></tr></table>'
212 yield 0, '</td></tr></table>'
213
213
214
214
215 def pygmentize(filenode, **kwargs):
215 def pygmentize(filenode, **kwargs):
216 """pygmentize function using pygments
216 """pygmentize function using pygments
217
217
218 :param filenode:
218 :param filenode:
219 """
219 """
220
220
221 return literal(code_highlight(filenode.content,
221 return literal(code_highlight(filenode.content,
222 filenode.lexer, CodeHtmlFormatter(**kwargs)))
222 filenode.lexer, CodeHtmlFormatter(**kwargs)))
223
223
224
224
225 def pygmentize_annotation(repo_name, filenode, **kwargs):
225 def pygmentize_annotation(repo_name, filenode, **kwargs):
226 """
226 """
227 pygmentize function for annotation
227 pygmentize function for annotation
228
228
229 :param filenode:
229 :param filenode:
230 """
230 """
231
231
232 color_dict = {}
232 color_dict = {}
233
233
234 def gen_color(n=10000):
234 def gen_color(n=10000):
235 """generator for getting n of evenly distributed colors using
235 """generator for getting n of evenly distributed colors using
236 hsv color and golden ratio. It always return same order of colors
236 hsv color and golden ratio. It always return same order of colors
237
237
238 :returns: RGB tuple
238 :returns: RGB tuple
239 """
239 """
240
240
241 def hsv_to_rgb(h, s, v):
241 def hsv_to_rgb(h, s, v):
242 if s == 0.0:
242 if s == 0.0:
243 return v, v, v
243 return v, v, v
244 i = int(h * 6.0) # XXX assume int() truncates!
244 i = int(h * 6.0) # XXX assume int() truncates!
245 f = (h * 6.0) - i
245 f = (h * 6.0) - i
246 p = v * (1.0 - s)
246 p = v * (1.0 - s)
247 q = v * (1.0 - s * f)
247 q = v * (1.0 - s * f)
248 t = v * (1.0 - s * (1.0 - f))
248 t = v * (1.0 - s * (1.0 - f))
249 i = i % 6
249 i = i % 6
250 if i == 0:
250 if i == 0:
251 return v, t, p
251 return v, t, p
252 if i == 1:
252 if i == 1:
253 return q, v, p
253 return q, v, p
254 if i == 2:
254 if i == 2:
255 return p, v, t
255 return p, v, t
256 if i == 3:
256 if i == 3:
257 return p, q, v
257 return p, q, v
258 if i == 4:
258 if i == 4:
259 return t, p, v
259 return t, p, v
260 if i == 5:
260 if i == 5:
261 return v, p, q
261 return v, p, q
262
262
263 golden_ratio = 0.618033988749895
263 golden_ratio = 0.618033988749895
264 h = 0.22717784590367374
264 h = 0.22717784590367374
265
265
266 for _ in xrange(n):
266 for _ in xrange(n):
267 h += golden_ratio
267 h += golden_ratio
268 h %= 1
268 h %= 1
269 HSV_tuple = [h, 0.95, 0.95]
269 HSV_tuple = [h, 0.95, 0.95]
270 RGB_tuple = hsv_to_rgb(*HSV_tuple)
270 RGB_tuple = hsv_to_rgb(*HSV_tuple)
271 yield map(lambda x: str(int(x * 256)), RGB_tuple)
271 yield map(lambda x: str(int(x * 256)), RGB_tuple)
272
272
273 cgenerator = gen_color()
273 cgenerator = gen_color()
274
274
275 def get_color_string(cs):
275 def get_color_string(cs):
276 if cs in color_dict:
276 if cs in color_dict:
277 col = color_dict[cs]
277 col = color_dict[cs]
278 else:
278 else:
279 col = color_dict[cs] = cgenerator.next()
279 col = color_dict[cs] = cgenerator.next()
280 return "color: rgb(%s)! important;" % (', '.join(col))
280 return "color: rgb(%s)! important;" % (', '.join(col))
281
281
282 def url_func(repo_name):
282 def url_func(repo_name):
283
283
284 def _url_func(changeset):
284 def _url_func(changeset):
285 author = changeset.author
285 author = changeset.author
286 date = changeset.date
286 date = changeset.date
287 message = tooltip(changeset.message)
287 message = tooltip(changeset.message)
288
288
289 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
289 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
290 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
290 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
291 "</b> %s<br/></div>")
291 "</b> %s<br/></div>")
292
292
293 tooltip_html = tooltip_html % (author, date, message)
293 tooltip_html = tooltip_html % (author, date, message)
294 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
294 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
295 short_id(changeset.raw_id))
295 short_id(changeset.raw_id))
296 uri = link_to(
296 uri = link_to(
297 lnk_format,
297 lnk_format,
298 url('changeset_home', repo_name=repo_name,
298 url('changeset_home', repo_name=repo_name,
299 revision=changeset.raw_id),
299 revision=changeset.raw_id),
300 style=get_color_string(changeset.raw_id),
300 style=get_color_string(changeset.raw_id),
301 class_='tooltip',
301 class_='tooltip',
302 title=tooltip_html
302 title=tooltip_html
303 )
303 )
304
304
305 uri += '\n'
305 uri += '\n'
306 return uri
306 return uri
307 return _url_func
307 return _url_func
308
308
309 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
309 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
310
310
311
311
312 def is_following_repo(repo_name, user_id):
312 def is_following_repo(repo_name, user_id):
313 from rhodecode.model.scm import ScmModel
313 from rhodecode.model.scm import ScmModel
314 return ScmModel().is_following_repo(repo_name, user_id)
314 return ScmModel().is_following_repo(repo_name, user_id)
315
315
316 flash = _Flash()
316 flash = _Flash()
317
317
318 #==============================================================================
318 #==============================================================================
319 # SCM FILTERS available via h.
319 # SCM FILTERS available via h.
320 #==============================================================================
320 #==============================================================================
321 from vcs.utils import author_name, author_email
321 from vcs.utils import author_name, author_email
322 from rhodecode.lib import credentials_filter, age as _age
322 from rhodecode.lib import credentials_filter, age as _age
323 from rhodecode.model.db import User
323 from rhodecode.model.db import User
324
324
325 age = lambda x: _age(x)
325 age = lambda x: _age(x)
326 capitalize = lambda x: x.capitalize()
326 capitalize = lambda x: x.capitalize()
327 email = author_email
327 email = author_email
328 short_id = lambda x: x[:12]
328 short_id = lambda x: x[:12]
329 hide_credentials = lambda x: ''.join(credentials_filter(x))
329 hide_credentials = lambda x: ''.join(credentials_filter(x))
330
330
331
331
332 def email_or_none(author):
332 def email_or_none(author):
333 _email = email(author)
333 _email = email(author)
334 if _email != '':
334 if _email != '':
335 return _email
335 return _email
336
336
337 # See if it contains a username we can get an email from
337 # See if it contains a username we can get an email from
338 user = User.get_by_username(author_name(author), case_insensitive=True,
338 user = User.get_by_username(author_name(author), case_insensitive=True,
339 cache=True)
339 cache=True)
340 if user is not None:
340 if user is not None:
341 return user.email
341 return user.email
342
342
343 # No valid email, not a valid user in the system, none!
343 # No valid email, not a valid user in the system, none!
344 return None
344 return None
345
345
346
346
347 def person(author):
347 def person(author):
348 # attr to return from fetched user
348 # attr to return from fetched user
349 person_getter = lambda usr: usr.username
349 person_getter = lambda usr: usr.username
350
350
351 # Valid email in the attribute passed, see if they're in the system
351 # Valid email in the attribute passed, see if they're in the system
352 _email = email(author)
352 _email = email(author)
353 if _email != '':
353 if _email != '':
354 user = User.get_by_email(_email, case_insensitive=True, cache=True)
354 user = User.get_by_email(_email, case_insensitive=True, cache=True)
355 if user is not None:
355 if user is not None:
356 return person_getter(user)
356 return person_getter(user)
357 return _email
357 return _email
358
358
359 # Maybe it's a username?
359 # Maybe it's a username?
360 _author = author_name(author)
360 _author = author_name(author)
361 user = User.get_by_username(_author, case_insensitive=True,
361 user = User.get_by_username(_author, case_insensitive=True,
362 cache=True)
362 cache=True)
363 if user is not None:
363 if user is not None:
364 return person_getter(user)
364 return person_getter(user)
365
365
366 # Still nothing? Just pass back the author name then
366 # Still nothing? Just pass back the author name then
367 return _author
367 return _author
368
368
369 def bool2icon(value):
369 def bool2icon(value):
370 """Returns True/False values represented as small html image of true/false
370 """Returns True/False values represented as small html image of true/false
371 icons
371 icons
372
372
373 :param value: bool value
373 :param value: bool value
374 """
374 """
375
375
376 if value is True:
376 if value is True:
377 return HTML.tag('img', src=url("/images/icons/accept.png"),
377 return HTML.tag('img', src=url("/images/icons/accept.png"),
378 alt=_('True'))
378 alt=_('True'))
379
379
380 if value is False:
380 if value is False:
381 return HTML.tag('img', src=url("/images/icons/cancel.png"),
381 return HTML.tag('img', src=url("/images/icons/cancel.png"),
382 alt=_('False'))
382 alt=_('False'))
383
383
384 return value
384 return value
385
385
386
386
387 def action_parser(user_log, feed=False):
387 def action_parser(user_log, feed=False):
388 """This helper will action_map the specified string action into translated
388 """This helper will action_map the specified string action into translated
389 fancy names with icons and links
389 fancy names with icons and links
390
390
391 :param user_log: user log instance
391 :param user_log: user log instance
392 :param feed: use output for feeds (no html and fancy icons)
392 :param feed: use output for feeds (no html and fancy icons)
393 """
393 """
394
394
395 action = user_log.action
395 action = user_log.action
396 action_params = ' '
396 action_params = ' '
397
397
398 x = action.split(':')
398 x = action.split(':')
399
399
400 if len(x) > 1:
400 if len(x) > 1:
401 action, action_params = x
401 action, action_params = x
402
402
403 def get_cs_links():
403 def get_cs_links():
404 revs_limit = 3 #display this amount always
404 revs_limit = 3 #display this amount always
405 revs_top_limit = 50 #show upto this amount of changesets hidden
405 revs_top_limit = 50 #show upto this amount of changesets hidden
406 revs = action_params.split(',')
406 revs = action_params.split(',')
407 repo_name = user_log.repository.repo_name
407 repo_name = user_log.repository.repo_name
408
408
409 from rhodecode.model.scm import ScmModel
409 from rhodecode.model.scm import ScmModel
410 repo = user_log.repository.scm_instance
410 repo = user_log.repository.scm_instance
411
411
412 message = lambda rev: get_changeset_safe(repo, rev).message
412 message = lambda rev: get_changeset_safe(repo, rev).message
413 cs_links = []
413 cs_links = []
414 cs_links.append(" " + ', '.join ([link_to(rev,
414 cs_links.append(" " + ', '.join ([link_to(rev,
415 url('changeset_home',
415 url('changeset_home',
416 repo_name=repo_name,
416 repo_name=repo_name,
417 revision=rev), title=tooltip(message(rev)),
417 revision=rev), title=tooltip(message(rev)),
418 class_='tooltip') for rev in revs[:revs_limit] ]))
418 class_='tooltip') for rev in revs[:revs_limit] ]))
419
419
420 compare_view = (' <div class="compare_view tooltip" title="%s">'
420 compare_view = (' <div class="compare_view tooltip" title="%s">'
421 '<a href="%s">%s</a> '
421 '<a href="%s">%s</a> '
422 '</div>' % (_('Show all combined changesets %s->%s' \
422 '</div>' % (_('Show all combined changesets %s->%s' \
423 % (revs[0], revs[-1])),
423 % (revs[0], revs[-1])),
424 url('changeset_home', repo_name=repo_name,
424 url('changeset_home', repo_name=repo_name,
425 revision='%s...%s' % (revs[0], revs[-1])
425 revision='%s...%s' % (revs[0], revs[-1])
426 ),
426 ),
427 _('compare view'))
427 _('compare view'))
428 )
428 )
429
429
430 if len(revs) > revs_limit:
430 if len(revs) > revs_limit:
431 uniq_id = revs[0]
431 uniq_id = revs[0]
432 html_tmpl = ('<span> %s '
432 html_tmpl = ('<span> %s '
433 '<a class="show_more" id="_%s" href="#more">%s</a> '
433 '<a class="show_more" id="_%s" href="#more">%s</a> '
434 '%s</span>')
434 '%s</span>')
435 if not feed:
435 if not feed:
436 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
436 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
437 % (len(revs) - revs_limit),
437 % (len(revs) - revs_limit),
438 _('revisions')))
438 _('revisions')))
439
439
440 if not feed:
440 if not feed:
441 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
441 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
442 else:
442 else:
443 html_tmpl = '<span id="%s"> %s </span>'
443 html_tmpl = '<span id="%s"> %s </span>'
444
444
445 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
445 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
446 url('changeset_home',
446 url('changeset_home',
447 repo_name=repo_name, revision=rev),
447 repo_name=repo_name, revision=rev),
448 title=message(rev), class_='tooltip')
448 title=message(rev), class_='tooltip')
449 for rev in revs[revs_limit:revs_top_limit]])))
449 for rev in revs[revs_limit:revs_top_limit]])))
450 if len(revs) > 1:
450 if len(revs) > 1:
451 cs_links.append(compare_view)
451 cs_links.append(compare_view)
452 return ''.join(cs_links)
452 return ''.join(cs_links)
453
453
454 def get_fork_name():
454 def get_fork_name():
455 repo_name = action_params
455 repo_name = action_params
456 return _('fork name ') + str(link_to(action_params, url('summary_home',
456 return _('fork name ') + str(link_to(action_params, url('summary_home',
457 repo_name=repo_name,)))
457 repo_name=repo_name,)))
458
458
459 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
459 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
460 'user_created_repo':(_('[created] repository'), None),
460 'user_created_repo':(_('[created] repository'), None),
461 'user_created_fork':(_('[created] repository as fork'), None),
461 'user_created_fork':(_('[created] repository as fork'), None),
462 'user_forked_repo':(_('[forked] repository'), get_fork_name),
462 'user_forked_repo':(_('[forked] repository'), get_fork_name),
463 'user_updated_repo':(_('[updated] repository'), None),
463 'user_updated_repo':(_('[updated] repository'), None),
464 'admin_deleted_repo':(_('[delete] repository'), None),
464 'admin_deleted_repo':(_('[delete] repository'), None),
465 'admin_created_repo':(_('[created] repository'), None),
465 'admin_created_repo':(_('[created] repository'), None),
466 'admin_forked_repo':(_('[forked] repository'), None),
466 'admin_forked_repo':(_('[forked] repository'), None),
467 'admin_updated_repo':(_('[updated] repository'), None),
467 'admin_updated_repo':(_('[updated] repository'), None),
468 'push':(_('[pushed] into'), get_cs_links),
468 'push':(_('[pushed] into'), get_cs_links),
469 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
469 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
470 'push_remote':(_('[pulled from remote] into'), get_cs_links),
470 'push_remote':(_('[pulled from remote] into'), get_cs_links),
471 'pull':(_('[pulled] from'), None),
471 'pull':(_('[pulled] from'), None),
472 'started_following_repo':(_('[started following] repository'), None),
472 'started_following_repo':(_('[started following] repository'), None),
473 'stopped_following_repo':(_('[stopped following] repository'), None),
473 'stopped_following_repo':(_('[stopped following] repository'), None),
474 }
474 }
475
475
476 action_str = action_map.get(action, action)
476 action_str = action_map.get(action, action)
477 if feed:
477 if feed:
478 action = action_str[0].replace('[', '').replace(']', '')
478 action = action_str[0].replace('[', '').replace(']', '')
479 else:
479 else:
480 action = action_str[0].replace('[', '<span class="journal_highlight">')\
480 action = action_str[0].replace('[', '<span class="journal_highlight">')\
481 .replace(']', '</span>')
481 .replace(']', '</span>')
482
482
483 action_params_func = lambda :""
483 action_params_func = lambda :""
484
484
485 if callable(action_str[1]):
485 if callable(action_str[1]):
486 action_params_func = action_str[1]
486 action_params_func = action_str[1]
487
487
488 return [literal(action), action_params_func]
488 return [literal(action), action_params_func]
489
489
490 def action_parser_icon(user_log):
490 def action_parser_icon(user_log):
491 action = user_log.action
491 action = user_log.action
492 action_params = None
492 action_params = None
493 x = action.split(':')
493 x = action.split(':')
494
494
495 if len(x) > 1:
495 if len(x) > 1:
496 action, action_params = x
496 action, action_params = x
497
497
498 tmpl = """<img src="%s%s" alt="%s"/>"""
498 tmpl = """<img src="%s%s" alt="%s"/>"""
499 map = {'user_deleted_repo':'database_delete.png',
499 map = {'user_deleted_repo':'database_delete.png',
500 'user_created_repo':'database_add.png',
500 'user_created_repo':'database_add.png',
501 'user_created_fork':'arrow_divide.png',
501 'user_created_fork':'arrow_divide.png',
502 'user_forked_repo':'arrow_divide.png',
502 'user_forked_repo':'arrow_divide.png',
503 'user_updated_repo':'database_edit.png',
503 'user_updated_repo':'database_edit.png',
504 'admin_deleted_repo':'database_delete.png',
504 'admin_deleted_repo':'database_delete.png',
505 'admin_created_repo':'database_add.png',
505 'admin_created_repo':'database_add.png',
506 'admin_forked_repo':'arrow_divide.png',
506 'admin_forked_repo':'arrow_divide.png',
507 'admin_updated_repo':'database_edit.png',
507 'admin_updated_repo':'database_edit.png',
508 'push':'script_add.png',
508 'push':'script_add.png',
509 'push_local':'script_edit.png',
509 'push_local':'script_edit.png',
510 'push_remote':'connect.png',
510 'push_remote':'connect.png',
511 'pull':'down_16.png',
511 'pull':'down_16.png',
512 'started_following_repo':'heart_add.png',
512 'started_following_repo':'heart_add.png',
513 'stopped_following_repo':'heart_delete.png',
513 'stopped_following_repo':'heart_delete.png',
514 }
514 }
515 return literal(tmpl % ((url('/images/icons/')),
515 return literal(tmpl % ((url('/images/icons/')),
516 map.get(action, action), action))
516 map.get(action, action), action))
517
517
518
518
519 #==============================================================================
519 #==============================================================================
520 # PERMS
520 # PERMS
521 #==============================================================================
521 #==============================================================================
522 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
522 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
523 HasRepoPermissionAny, HasRepoPermissionAll
523 HasRepoPermissionAny, HasRepoPermissionAll
524
524
525 #==============================================================================
525 #==============================================================================
526 # GRAVATAR URL
526 # GRAVATAR URL
527 #==============================================================================
527 #==============================================================================
528
528
529 def gravatar_url(email_address, size=30):
529 def gravatar_url(email_address, size=30):
530 if (not str2bool(config['app_conf'].get('use_gravatar')) or
530 if (not str2bool(config['app_conf'].get('use_gravatar')) or
531 not email_address or email_address == 'anonymous@rhodecode.org'):
531 not email_address or email_address == 'anonymous@rhodecode.org'):
532 f=lambda a,l:min(l,key=lambda x:abs(x-a))
532 f=lambda a,l:min(l,key=lambda x:abs(x-a))
533 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
533 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
534
534
535 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
535 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
536 default = 'identicon'
536 default = 'identicon'
537 baseurl_nossl = "http://www.gravatar.com/avatar/"
537 baseurl_nossl = "http://www.gravatar.com/avatar/"
538 baseurl_ssl = "https://secure.gravatar.com/avatar/"
538 baseurl_ssl = "https://secure.gravatar.com/avatar/"
539 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
539 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
540
540
541 if isinstance(email_address, unicode):
541 if isinstance(email_address, unicode):
542 #hashlib crashes on unicode items
542 #hashlib crashes on unicode items
543 email_address = safe_str(email_address)
543 email_address = safe_str(email_address)
544 # construct the url
544 # construct the url
545 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
545 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
546 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
546 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
547
547
548 return gravatar_url
548 return gravatar_url
549
549
550
550
551 #==============================================================================
551 #==============================================================================
552 # REPO PAGER, PAGER FOR REPOSITORY
552 # REPO PAGER, PAGER FOR REPOSITORY
553 #==============================================================================
553 #==============================================================================
554 class RepoPage(Page):
554 class RepoPage(Page):
555
555
556 def __init__(self, collection, page=1, items_per_page=20,
556 def __init__(self, collection, page=1, items_per_page=20,
557 item_count=None, url=None, **kwargs):
557 item_count=None, url=None, **kwargs):
558
558
559 """Create a "RepoPage" instance. special pager for paging
559 """Create a "RepoPage" instance. special pager for paging
560 repository
560 repository
561 """
561 """
562 self._url_generator = url
562 self._url_generator = url
563
563
564 # Safe the kwargs class-wide so they can be used in the pager() method
564 # Safe the kwargs class-wide so they can be used in the pager() method
565 self.kwargs = kwargs
565 self.kwargs = kwargs
566
566
567 # Save a reference to the collection
567 # Save a reference to the collection
568 self.original_collection = collection
568 self.original_collection = collection
569
569
570 self.collection = collection
570 self.collection = collection
571
571
572 # The self.page is the number of the current page.
572 # The self.page is the number of the current page.
573 # The first page has the number 1!
573 # The first page has the number 1!
574 try:
574 try:
575 self.page = int(page) # make it int() if we get it as a string
575 self.page = int(page) # make it int() if we get it as a string
576 except (ValueError, TypeError):
576 except (ValueError, TypeError):
577 self.page = 1
577 self.page = 1
578
578
579 self.items_per_page = items_per_page
579 self.items_per_page = items_per_page
580
580
581 # Unless the user tells us how many items the collections has
581 # Unless the user tells us how many items the collections has
582 # we calculate that ourselves.
582 # we calculate that ourselves.
583 if item_count is not None:
583 if item_count is not None:
584 self.item_count = item_count
584 self.item_count = item_count
585 else:
585 else:
586 self.item_count = len(self.collection)
586 self.item_count = len(self.collection)
587
587
588 # Compute the number of the first and last available page
588 # Compute the number of the first and last available page
589 if self.item_count > 0:
589 if self.item_count > 0:
590 self.first_page = 1
590 self.first_page = 1
591 self.page_count = int(math.ceil(float(self.item_count) /
591 self.page_count = int(math.ceil(float(self.item_count) /
592 self.items_per_page))
592 self.items_per_page))
593 self.last_page = self.first_page + self.page_count - 1
593 self.last_page = self.first_page + self.page_count - 1
594
594
595 # Make sure that the requested page number is the range of
595 # Make sure that the requested page number is the range of
596 # valid pages
596 # valid pages
597 if self.page > self.last_page:
597 if self.page > self.last_page:
598 self.page = self.last_page
598 self.page = self.last_page
599 elif self.page < self.first_page:
599 elif self.page < self.first_page:
600 self.page = self.first_page
600 self.page = self.first_page
601
601
602 # Note: the number of items on this page can be less than
602 # Note: the number of items on this page can be less than
603 # items_per_page if the last page is not full
603 # items_per_page if the last page is not full
604 self.first_item = max(0, (self.item_count) - (self.page *
604 self.first_item = max(0, (self.item_count) - (self.page *
605 items_per_page))
605 items_per_page))
606 self.last_item = ((self.item_count - 1) - items_per_page *
606 self.last_item = ((self.item_count - 1) - items_per_page *
607 (self.page - 1))
607 (self.page - 1))
608
608
609 self.items = list(self.collection[self.first_item:self.last_item + 1])
609 self.items = list(self.collection[self.first_item:self.last_item + 1])
610
610
611 # Links to previous and next page
611 # Links to previous and next page
612 if self.page > self.first_page:
612 if self.page > self.first_page:
613 self.previous_page = self.page - 1
613 self.previous_page = self.page - 1
614 else:
614 else:
615 self.previous_page = None
615 self.previous_page = None
616
616
617 if self.page < self.last_page:
617 if self.page < self.last_page:
618 self.next_page = self.page + 1
618 self.next_page = self.page + 1
619 else:
619 else:
620 self.next_page = None
620 self.next_page = None
621
621
622 # No items available
622 # No items available
623 else:
623 else:
624 self.first_page = None
624 self.first_page = None
625 self.page_count = 0
625 self.page_count = 0
626 self.last_page = None
626 self.last_page = None
627 self.first_item = None
627 self.first_item = None
628 self.last_item = None
628 self.last_item = None
629 self.previous_page = None
629 self.previous_page = None
630 self.next_page = None
630 self.next_page = None
631 self.items = []
631 self.items = []
632
632
633 # This is a subclass of the 'list' type. Initialise the list now.
633 # This is a subclass of the 'list' type. Initialise the list now.
634 list.__init__(self, reversed(self.items))
634 list.__init__(self, reversed(self.items))
635
635
636
636
637 def changed_tooltip(nodes):
637 def changed_tooltip(nodes):
638 """
638 """
639 Generates a html string for changed nodes in changeset page.
639 Generates a html string for changed nodes in changeset page.
640 It limits the output to 30 entries
640 It limits the output to 30 entries
641
641
642 :param nodes: LazyNodesGenerator
642 :param nodes: LazyNodesGenerator
643 """
643 """
644 if nodes:
644 if nodes:
645 pref = ': <br/> '
645 pref = ': <br/> '
646 suf = ''
646 suf = ''
647 if len(nodes) > 30:
647 if len(nodes) > 30:
648 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
648 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
649 return literal(pref + '<br/> '.join([safe_unicode(x.path)
649 return literal(pref + '<br/> '.join([safe_unicode(x.path)
650 for x in nodes[:30]]) + suf)
650 for x in nodes[:30]]) + suf)
651 else:
651 else:
652 return ': ' + _('No Files')
652 return ': ' + _('No Files')
653
653
654
654
655
655
656 def repo_link(groups_and_repos):
656 def repo_link(groups_and_repos):
657 """
657 """
658 Makes a breadcrumbs link to repo within a group
658 Makes a breadcrumbs link to repo within a group
659 joins &raquo; on each group to create a fancy link
659 joins &raquo; on each group to create a fancy link
660
660
661 ex::
661 ex::
662 group >> subgroup >> repo
662 group >> subgroup >> repo
663
663
664 :param groups_and_repos:
664 :param groups_and_repos:
665 """
665 """
666 groups, repo_name = groups_and_repos
666 groups, repo_name = groups_and_repos
667
667
668 if not groups:
668 if not groups:
669 return repo_name
669 return repo_name
670 else:
670 else:
671 def make_link(group):
671 def make_link(group):
672 return link_to(group.name, url('repos_group_home',
672 return link_to(group.name, url('repos_group_home',
673 group_name=group.group_name))
673 group_name=group.group_name))
674 return literal(' &raquo; '.join(map(make_link, groups)) + \
674 return literal(' &raquo; '.join(map(make_link, groups)) + \
675 " &raquo; " + repo_name)
675 " &raquo; " + repo_name)
676
676
677 def fancy_file_stats(stats):
677 def fancy_file_stats(stats):
678 """
678 """
679 Displays a fancy two colored bar for number of added/deleted
679 Displays a fancy two colored bar for number of added/deleted
680 lines of code on file
680 lines of code on file
681
681
682 :param stats: two element list of added/deleted lines of code
682 :param stats: two element list of added/deleted lines of code
683 """
683 """
684
684
685 a, d, t = stats[0], stats[1], stats[0] + stats[1]
685 a, d, t = stats[0], stats[1], stats[0] + stats[1]
686 width = 100
686 width = 100
687 unit = float(width) / (t or 1)
687 unit = float(width) / (t or 1)
688
688
689 # needs > 9% of width to be visible or 0 to be hidden
689 # needs > 9% of width to be visible or 0 to be hidden
690 a_p = max(9, unit * a) if a > 0 else 0
690 a_p = max(9, unit * a) if a > 0 else 0
691 d_p = max(9, unit * d) if d > 0 else 0
691 d_p = max(9, unit * d) if d > 0 else 0
692 p_sum = a_p + d_p
692 p_sum = a_p + d_p
693
693
694 if p_sum > width:
694 if p_sum > width:
695 #adjust the percentage to be == 100% since we adjusted to 9
695 #adjust the percentage to be == 100% since we adjusted to 9
696 if a_p > d_p:
696 if a_p > d_p:
697 a_p = a_p - (p_sum - width)
697 a_p = a_p - (p_sum - width)
698 else:
698 else:
699 d_p = d_p - (p_sum - width)
699 d_p = d_p - (p_sum - width)
700
700
701 a_v = a if a > 0 else ''
701 a_v = a if a > 0 else ''
702 d_v = d if d > 0 else ''
702 d_v = d if d > 0 else ''
703
703
704
704
705 def cgen(l_type):
705 def cgen(l_type):
706 mapping = {'tr':'top-right-rounded-corner',
706 mapping = {'tr':'top-right-rounded-corner',
707 'tl':'top-left-rounded-corner',
707 'tl':'top-left-rounded-corner',
708 'br':'bottom-right-rounded-corner',
708 'br':'bottom-right-rounded-corner',
709 'bl':'bottom-left-rounded-corner'}
709 'bl':'bottom-left-rounded-corner'}
710 map_getter = lambda x:mapping[x]
710 map_getter = lambda x:mapping[x]
711
711
712 if l_type == 'a' and d_v:
712 if l_type == 'a' and d_v:
713 #case when added and deleted are present
713 #case when added and deleted are present
714 return ' '.join(map(map_getter, ['tl', 'bl']))
714 return ' '.join(map(map_getter, ['tl', 'bl']))
715
715
716 if l_type == 'a' and not d_v:
716 if l_type == 'a' and not d_v:
717 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
717 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
718
718
719 if l_type == 'd' and a_v:
719 if l_type == 'd' and a_v:
720 return ' '.join(map(map_getter, ['tr', 'br']))
720 return ' '.join(map(map_getter, ['tr', 'br']))
721
721
722 if l_type == 'd' and not a_v:
722 if l_type == 'd' and not a_v:
723 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
723 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
724
724
725
725
726
726
727 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
727 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
728 a_p, a_v)
728 a_p, a_v)
729 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
729 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
730 d_p, d_v)
730 d_p, d_v)
731 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
731 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
732
732
733
733
734 def urlify_text(text_):
734 def urlify_text(text_):
735 import re
735 import re
736
736
737 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
737 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
738 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
738 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
739
739
740 def url_func(match_obj):
740 def url_func(match_obj):
741 url_full = match_obj.groups()[0]
741 url_full = match_obj.groups()[0]
742 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
742 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
743
743
744 return literal(url_pat.sub(url_func, text_))
744 return literal(url_pat.sub(url_func, text_))
745
745
746 def urlify_changesets(text_, repository):
746 def urlify_changesets(text_, repository):
747 import re
747 import re
748 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
748 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
749
749
750 def url_func(match_obj):
750 def url_func(match_obj):
751 rev = match_obj.groups()[0]
751 rev = match_obj.groups()[0]
752 pref = ''
752 pref = ''
753 if match_obj.group().startswith(' '):
753 if match_obj.group().startswith(' '):
754 pref = ' '
754 pref = ' '
755 tmpl = (
755 tmpl = (
756 '%(pref)s<a class="%(cls)s" href="%(url)s">'
756 '%(pref)s<a class="%(cls)s" href="%(url)s">'
757 '%(rev)s'
757 '%(rev)s'
758 '</a>'
758 '</a>'
759 )
759 )
760 return tmpl % {
760 return tmpl % {
761 'pref': pref,
761 'pref': pref,
762 'cls': 'revision-link',
762 'cls': 'revision-link',
763 'url': url('changeset_home', repo_name=repository, revision=rev),
763 'url': url('changeset_home', repo_name=repository, revision=rev),
764 'rev': rev,
764 'rev': rev,
765 }
765 }
766
766
767 newtext = URL_PAT.sub(url_func, text_)
767 newtext = URL_PAT.sub(url_func, text_)
768
768
769 return newtext
769 return newtext
770
770
771 def urlify_commit(text_, repository=None, link_=None):
771 def urlify_commit(text_, repository=None, link_=None):
772 import re
772 import re
773 import traceback
773 import traceback
774
774
775 # urlify changesets
775 # urlify changesets
776 text_ = urlify_changesets(text_, repository)
776 text_ = urlify_changesets(text_, repository)
777
777
778 def linkify_others(t,l):
778 def linkify_others(t,l):
779 urls = re.compile(r'(\<a.*?\<\/a\>)',)
779 urls = re.compile(r'(\<a.*?\<\/a\>)',)
780 links = []
780 links = []
781 for e in urls.split(t):
781 for e in urls.split(t):
782 if not urls.match(e):
782 if not urls.match(e):
783 links.append('<a class="message-link" href="%s">%s</a>' % (l,e))
783 links.append('<a class="message-link" href="%s">%s</a>' % (l,e))
784 else:
784 else:
785 links.append(e)
785 links.append(e)
786
786
787 return ''.join(links)
787 return ''.join(links)
788 try:
788 try:
789 conf = config['app_conf']
789 conf = config['app_conf']
790
790
791 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
791 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
792
792
793 if URL_PAT:
793 if URL_PAT:
794 ISSUE_SERVER_LNK = conf.get('issue_server_link')
794 ISSUE_SERVER_LNK = conf.get('issue_server_link')
795 ISSUE_PREFIX = conf.get('issue_prefix')
795 ISSUE_PREFIX = conf.get('issue_prefix')
796
796
797 def url_func(match_obj):
797 def url_func(match_obj):
798 pref = ''
798 pref = ''
799 if match_obj.group().startswith(' '):
799 if match_obj.group().startswith(' '):
800 pref = ' '
800 pref = ' '
801
801
802 issue_id = ''.join(match_obj.groups())
802 issue_id = ''.join(match_obj.groups())
803 tmpl = (
803 tmpl = (
804 '%(pref)s<a class="%(cls)s" href="%(url)s">'
804 '%(pref)s<a class="%(cls)s" href="%(url)s">'
805 '%(issue-prefix)s%(id-repr)s'
805 '%(issue-prefix)s%(id-repr)s'
806 '</a>'
806 '</a>'
807 )
807 )
808 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
808 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
809 if repository:
809 if repository:
810 url = url.replace('{repo}', repository)
810 url = url.replace('{repo}', repository)
811
811
812 return tmpl % {
812 return tmpl % {
813 'pref': pref,
813 'pref': pref,
814 'cls': 'issue-tracker-link',
814 'cls': 'issue-tracker-link',
815 'url': url,
815 'url': url,
816 'id-repr': issue_id,
816 'id-repr': issue_id,
817 'issue-prefix': ISSUE_PREFIX,
817 'issue-prefix': ISSUE_PREFIX,
818 'serv': ISSUE_SERVER_LNK,
818 'serv': ISSUE_SERVER_LNK,
819 }
819 }
820
820
821 newtext = URL_PAT.sub(url_func, text_)
821 newtext = URL_PAT.sub(url_func, text_)
822
822
823 # wrap not links into final link => link_
823 # wrap not links into final link => link_
824 newtext = linkify_others(newtext, link_)
824 newtext = linkify_others(newtext, link_)
825
825
826 return literal(newtext)
826 return literal(newtext)
827 except:
827 except:
828 log.error(traceback.format_exc())
828 log.error(traceback.format_exc())
829 pass
829 pass
830
830
831 return text_
831 return text_
832
832
833
833
834 def rst(source):
834 def rst(source):
835 return literal('<div class="rst-block">%s</div>' %
835 return literal('<div class="rst-block">%s</div>' %
836 MarkupRenderer.rst(source))
836 MarkupRenderer.rst(source))
837
837
838
838
839 def rst_w_mentions(source):
839 def rst_w_mentions(source):
840 """
840 """
841 Wrapped rst renderer with @mention highlighting
841 Wrapped rst renderer with @mention highlighting
842
842
843 :param source:
843 :param source:
844 """
844 """
845 return literal('<div class="rst-block">%s</div>' %
845 return literal('<div class="rst-block">%s</div>' %
846 MarkupRenderer.rst_with_mentions(source))
846 MarkupRenderer.rst_with_mentions(source))
@@ -1,336 +1,336 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.html"/>
2 <%inherit file="root.html"/>
3
3
4 <!-- HEADER -->
4 <!-- HEADER -->
5 <div id="header">
5 <div id="header">
6 <div id="header-inner" class="title">
6 <div id="header-inner" class="title">
7 <div id="logo">
7 <div id="logo">
8 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
8 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
9 </div>
9 </div>
10 <!-- MENU -->
10 <!-- MENU -->
11 ${self.page_nav()}
11 ${self.page_nav()}
12 <!-- END MENU -->
12 <!-- END MENU -->
13 ${self.body()}
13 ${self.body()}
14 </div>
14 </div>
15 </div>
15 </div>
16 <!-- END HEADER -->
16 <!-- END HEADER -->
17
17
18 <!-- CONTENT -->
18 <!-- CONTENT -->
19 <div id="content">
19 <div id="content">
20 <div class="flash_msg">
20 <div class="flash_msg">
21 <% messages = h.flash.pop_messages() %>
21 <% messages = h.flash.pop_messages() %>
22 % if messages:
22 % if messages:
23 <ul id="flash-messages">
23 <ul id="flash-messages">
24 % for message in messages:
24 % for message in messages:
25 <li class="${message.category}_msg">${message}</li>
25 <li class="${message.category}_msg">${message}</li>
26 % endfor
26 % endfor
27 </ul>
27 </ul>
28 % endif
28 % endif
29 </div>
29 </div>
30 <div id="main">
30 <div id="main">
31 ${next.main()}
31 ${next.main()}
32 </div>
32 </div>
33 </div>
33 </div>
34 <!-- END CONTENT -->
34 <!-- END CONTENT -->
35
35
36 <!-- FOOTER -->
36 <!-- FOOTER -->
37 <div id="footer">
37 <div id="footer">
38 <div id="footer-inner" class="title">
38 <div id="footer-inner" class="title">
39 <div>
39 <div>
40 <p class="footer-link">
40 <p class="footer-link">
41 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
41 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
42 </p>
42 </p>
43 <p class="footer-link-right">
43 <p class="footer-link-right">
44 <a href="${h.url('rhodecode_official')}">RhodeCode</a>
44 <a href="${h.url('rhodecode_official')}">RhodeCode</a>
45 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
45 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
46 </p>
46 </p>
47 </div>
47 </div>
48 </div>
48 </div>
49 </div>
49 </div>
50 <!-- END FOOTER -->
50 <!-- END FOOTER -->
51
51
52 ### MAKO DEFS ###
52 ### MAKO DEFS ###
53 <%def name="page_nav()">
53 <%def name="page_nav()">
54 ${self.menu()}
54 ${self.menu()}
55 </%def>
55 </%def>
56
56
57 <%def name="breadcrumbs()">
57 <%def name="breadcrumbs()">
58 <div class="breadcrumbs">
58 <div class="breadcrumbs">
59 ${self.breadcrumbs_links()}
59 ${self.breadcrumbs_links()}
60 </div>
60 </div>
61 </%def>
61 </%def>
62
62
63 <%def name="usermenu()">
63 <%def name="usermenu()">
64 <div class="user-menu">
64 <div class="user-menu">
65 <div class="container">
65 <div class="container">
66 <div class="gravatar" id="quick_login_link">
66 <div class="gravatar" id="quick_login_link">
67 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,24)}" />
67 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,24)}" />
68 </div>
68 </div>
69 %if c.rhodecode_user.username != 'default' and c.unread_notifications != 0:
69 %if c.rhodecode_user.username != 'default' and c.unread_notifications != 0:
70 <div class="notifications">
70 <div class="notifications">
71 <a id="notification_counter" href="${h.url('notifications')}">${c.unread_notifications}</a>
71 <a id="notification_counter" href="${h.url('notifications')}">${c.unread_notifications}</a>
72 </div>
72 </div>
73 %endif
73 %endif
74 </div>
74 </div>
75 <div id="quick_login" style="display:none">
75 <div id="quick_login" style="display:none">
76 %if c.rhodecode_user.username == 'default':
76 %if c.rhodecode_user.username == 'default':
77 <h4>${_('Login to your account')}</h4>
77 <h4>${_('Login to your account')}</h4>
78 ${h.form(h.url('login_home',came_from=h.url.current()))}
78 ${h.form(h.url('login_home',came_from=h.url.current()))}
79 <div class="form">
79 <div class="form">
80 <div class="fields">
80 <div class="fields">
81 <div class="field">
81 <div class="field">
82 <div class="label">
82 <div class="label">
83 <label for="username">${_('Username')}:</label>
83 <label for="username">${_('Username')}:</label>
84 </div>
84 </div>
85 <div class="input">
85 <div class="input">
86 ${h.text('username',class_='focus',size=40)}
86 ${h.text('username',class_='focus',size=40)}
87 </div>
87 </div>
88
88
89 </div>
89 </div>
90 <div class="field">
90 <div class="field">
91 <div class="label">
91 <div class="label">
92 <label for="password">${_('Password')}:</label>
92 <label for="password">${_('Password')}:</label>
93 </div>
93 </div>
94 <div class="input">
94 <div class="input">
95 ${h.password('password',class_='focus',size=40)}
95 ${h.password('password',class_='focus',size=40)}
96 </div>
96 </div>
97
97
98 </div>
98 </div>
99 <div class="buttons">
99 <div class="buttons">
100 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
100 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
101 <div class="register">
101 <div class="register">
102 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
102 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
103 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
103 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
104 %endif
104 %endif
105 </div>
105 </div>
106 <div class="submit">
106 <div class="submit">
107 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
107 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
108 </div>
108 </div>
109 </div>
109 </div>
110 </div>
110 </div>
111 </div>
111 </div>
112 ${h.end_form()}
112 ${h.end_form()}
113 %else:
113 %else:
114 <div class="links_left">
114 <div class="links_left">
115 <div class="full_name">${c.rhodecode_user.full_name}</div>
115 <div class="full_name">${c.rhodecode_user.full_name}</div>
116 <div class="email">${c.rhodecode_user.email}</div>
116 <div class="email">${c.rhodecode_user.email}</div>
117 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
117 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
118 </div>
118 </div>
119 <div class="links_right">
119 <div class="links_right">
120 <ol class="links">
120 <ol class="links">
121 <li>${h.link_to(_(u'Home'),h.url('home'))}</li>
121 <li>${h.link_to(_(u'Home'),h.url('home'))}</li>
122 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
122 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
123 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
123 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
124 </ol>
124 </ol>
125 </div>
125 </div>
126 %endif
126 %endif
127 </div>
127 </div>
128 </div>
128 </div>
129 </%def>
129 </%def>
130
130
131 <%def name="menu(current=None)">
131 <%def name="menu(current=None)">
132 <%
132 <%
133 def is_current(selected):
133 def is_current(selected):
134 if selected == current:
134 if selected == current:
135 return h.literal('class="current"')
135 return h.literal('class="current"')
136 %>
136 %>
137 %if current not in ['home','admin']:
137 %if current not in ['home','admin']:
138 ##REGULAR MENU
138 ##REGULAR MENU
139 <ul id="quick">
139 <ul id="quick">
140 <!-- repo switcher -->
140 <!-- repo switcher -->
141 <li>
141 <li>
142 <a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="#">
142 <a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="#">
143 <span class="icon">
143 <span class="icon">
144 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
144 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
145 </span>
145 </span>
146 <span>&darr;</span>
146 <span>&darr;</span>
147 </a>
147 </a>
148 <ul id="repo_switcher_list" class="repo_switcher">
148 <ul id="repo_switcher_list" class="repo_switcher">
149 <li>
149 <li>
150 <a href="#">${_('loading...')}</a>
150 <a href="#">${_('loading...')}</a>
151 </li>
151 </li>
152 </ul>
152 </ul>
153 </li>
153 </li>
154
154
155 <li ${is_current('summary')}>
155 <li ${is_current('summary')}>
156 <a class="menu_link" title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
156 <a class="menu_link" title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
157 <span class="icon">
157 <span class="icon">
158 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
158 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
159 </span>
159 </span>
160 <span>${_('Summary')}</span>
160 <span>${_('Summary')}</span>
161 </a>
161 </a>
162 </li>
162 </li>
163 <li ${is_current('changelog')}>
163 <li ${is_current('changelog')}>
164 <a class="menu_link" title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
164 <a class="menu_link" title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
165 <span class="icon">
165 <span class="icon">
166 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
166 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
167 </span>
167 </span>
168 <span>${_('Changelog')}</span>
168 <span>${_('Changelog')}</span>
169 </a>
169 </a>
170 </li>
170 </li>
171
171
172 <li ${is_current('switch_to')}>
172 <li ${is_current('switch_to')}>
173 <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#">
173 <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#">
174 <span class="icon">
174 <span class="icon">
175 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
175 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
176 </span>
176 </span>
177 <span>${_('Switch to')}</span>
177 <span>${_('Switch to')}</span>
178 </a>
178 </a>
179 <ul id="switch_to_list" class="switch_to">
179 <ul id="switch_to_list" class="switch_to">
180 <li><a href="#">${_('loading...')}</a></li>
180 <li><a href="#">${_('loading...')}</a></li>
181 </ul>
181 </ul>
182 </li>
182 </li>
183 <li ${is_current('files')}>
183 <li ${is_current('files')}>
184 <a class="menu_link" title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
184 <a class="menu_link" title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
185 <span class="icon">
185 <span class="icon">
186 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
186 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
187 </span>
187 </span>
188 <span>${_('Files')}</span>
188 <span>${_('Files')}</span>
189 </a>
189 </a>
190 </li>
190 </li>
191
191
192 <li ${is_current('options')}>
192 <li ${is_current('options')}>
193 <a class="menu_link" title="${_('Options')}" href="#">
193 <a class="menu_link" title="${_('Options')}" href="#">
194 <span class="icon">
194 <span class="icon">
195 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
195 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
196 </span>
196 </span>
197 <span>${_('Options')}</span>
197 <span>${_('Options')}</span>
198 </a>
198 </a>
199 <ul>
199 <ul>
200 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
200 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
201 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
201 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
202 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
202 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
203 %else:
203 %else:
204 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
204 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
205 %endif
205 %endif
206 %endif
206 %endif
207 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
207 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
208 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
208 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
209
209
210 % if h.HasPermissionAll('hg.admin')('access admin main page'):
210 % if h.HasPermissionAll('hg.admin')('access admin main page'):
211 <li>
211 <li>
212 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
212 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
213 <%def name="admin_menu()">
213 <%def name="admin_menu()">
214 <ul>
214 <ul>
215 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
215 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
216 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
216 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
217 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
217 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
218 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
218 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
219 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
219 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
220 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
220 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
221 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
221 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
222 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
222 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
223 </ul>
223 </ul>
224 </%def>
224 </%def>
225
225
226 ${admin_menu()}
226 ${admin_menu()}
227 </li>
227 </li>
228 % endif
228 % endif
229 </ul>
229 </ul>
230 </li>
230 </li>
231
231
232 <li>
232 <li>
233 <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
233 <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
234 <span class="icon_short">
234 <span class="icon_short">
235 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
235 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
236 </span>
236 </span>
237 <span id="current_followers_count" class="short">${c.repository_followers}</span>
237 <span id="current_followers_count" class="short">${c.repository_followers}</span>
238 </a>
238 </a>
239 </li>
239 </li>
240 <li>
240 <li>
241 <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
241 <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
242 <span class="icon_short">
242 <span class="icon_short">
243 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
243 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
244 </span>
244 </span>
245 <span class="short">${c.repository_forks}</span>
245 <span class="short">${c.repository_forks}</span>
246 </a>
246 </a>
247 </li>
247 </li>
248 ${usermenu()}
248 ${usermenu()}
249 </ul>
249 </ul>
250 <script type="text/javascript">
250 <script type="text/javascript">
251 YUE.on('repo_switcher','mouseover',function(){
251 YUE.on('repo_switcher','mouseover',function(){
252 function qfilter(){
252 function qfilter(){
253 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
253 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
254 var target = 'q_filter_rs';
254 var target = 'q_filter_rs';
255 var func = function(node){
255 var func = function(node){
256 return node.parentNode;
256 return node.parentNode;
257 }
257 }
258 q_filter(target,nodes,func);
258 q_filter(target,nodes,func);
259 }
259 }
260 var loaded = YUD.hasClass('repo_switcher','loaded');
260 var loaded = YUD.hasClass('repo_switcher','loaded');
261 if(!loaded){
261 if(!loaded){
262 YUD.addClass('repo_switcher','loaded');
262 YUD.addClass('repo_switcher','loaded');
263 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
263 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
264 function(o){qfilter();},
264 function(o){qfilter();},
265 function(o){YUD.removeClass('repo_switcher','loaded');}
265 function(o){YUD.removeClass('repo_switcher','loaded');}
266 ,null);
266 ,null);
267 }
267 }
268 return false;
268 return false;
269 });
269 });
270
270
271 YUE.on('branch_tag_switcher','mouseover',function(){
271 YUE.on('branch_tag_switcher','mouseover',function(){
272 var loaded = YUD.hasClass('branch_tag_switcher','loaded');
272 var loaded = YUD.hasClass('branch_tag_switcher','loaded');
273 if(!loaded){
273 if(!loaded){
274 YUD.addClass('branch_tag_switcher','loaded');
274 YUD.addClass('branch_tag_switcher','loaded');
275 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list',
275 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list',
276 function(o){},
276 function(o){},
277 function(o){YUD.removeClass('branch_tag_switcher','loaded');}
277 function(o){YUD.removeClass('branch_tag_switcher','loaded');}
278 ,null);
278 ,null);
279 }
279 }
280 return false;
280 return false;
281 });
281 });
282 </script>
282 </script>
283 %else:
283 %else:
284 ##ROOT MENU
284 ##ROOT MENU
285 <ul id="quick">
285 <ul id="quick">
286 <li>
286 <li>
287 <a class="menu_link" title="${_('Home')}" href="${h.url('home')}">
287 <a class="menu_link" title="${_('Home')}" href="${h.url('home')}">
288 <span class="icon">
288 <span class="icon">
289 <img src="${h.url('/images/icons/home_16.png')}" alt="${_('Home')}" />
289 <img src="${h.url('/images/icons/home_16.png')}" alt="${_('Home')}" />
290 </span>
290 </span>
291 <span>${_('Home')}</span>
291 <span>${_('Home')}</span>
292 </a>
292 </a>
293 </li>
293 </li>
294 %if c.rhodecode_user.username != 'default':
294 %if c.rhodecode_user.username != 'default':
295 <li>
295 <li>
296 <a class="menu_link" title="${_('Journal')}" href="${h.url('journal')}">
296 <a class="menu_link" title="${_('Journal')}" href="${h.url('journal')}">
297 <span class="icon">
297 <span class="icon">
298 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
298 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
299 </span>
299 </span>
300 <span>${_('Journal')}</span>
300 <span>${_('Journal')}</span>
301 </a>
301 </a>
302 </li>
302 </li>
303 %else:
303 %else:
304 <li>
304 <li>
305 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
305 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
306 <span class="icon">
306 <span class="icon">
307 <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" />
307 <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" />
308 </span>
308 </span>
309 <span>${_('Public journal')}</span>
309 <span>${_('Public journal')}</span>
310 </a>
310 </a>
311 </li>
311 </li>
312 %endif
312 %endif
313 <li>
313 <li>
314 <a class="menu_link" title="${_('Search')}" href="${h.url('search')}">
314 <a class="menu_link" title="${_('Search')}" href="${h.url('search')}">
315 <span class="icon">
315 <span class="icon">
316 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
316 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
317 </span>
317 </span>
318 <span>${_('Search')}</span>
318 <span>${_('Search')}</span>
319 </a>
319 </a>
320 </li>
320 </li>
321
321
322 %if h.HasPermissionAll('hg.admin')('access admin main page'):
322 %if h.HasPermissionAll('hg.admin')('access admin main page'):
323 <li ${is_current('admin')}>
323 <li ${is_current('admin')}>
324 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
324 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
325 <span class="icon">
325 <span class="icon">
326 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
326 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
327 </span>
327 </span>
328 <span>${_('Admin')}</span>
328 <span>${_('Admin')}</span>
329 </a>
329 </a>
330 ${admin_menu()}
330 ${admin_menu()}
331 </li>
331 </li>
332 %endif
332 %endif
333 ${usermenu()}
333 ${usermenu()}
334 </ul>
334 </ul>
335 %endif
335 %endif
336 </%def>
336 </%def>
General Comments 0
You need to be logged in to leave comments. Login now