##// END OF EJS Templates
another major code rafactor, reimplemented (almost from scratch)...
marcink -
r1038:5554aa9c beta
parent child Browse files
Show More
@@ -1,340 +1,342 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos
3 rhodecode.controllers.admin.repos
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Admin controller for RhodeCode
6 Admin controller for RhodeCode
7
7
8 :created_on: Apr 7, 2010
8 :created_on: Apr 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import formencode
30 import formencode
31 from operator import itemgetter
31 from operator import itemgetter
32 from formencode import htmlfill
32 from formencode import htmlfill
33
33
34 from paste.httpexceptions import HTTPInternalServerError
34 from paste.httpexceptions import HTTPInternalServerError
35 from pylons import request, response, session, tmpl_context as c, url
35 from pylons import request, response, session, tmpl_context as c, url
36 from pylons.controllers.util import abort, redirect
36 from pylons.controllers.util import abort, redirect
37 from pylons.i18n.translation import _
37 from pylons.i18n.translation import _
38
38
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
41 HasPermissionAnyDecorator
41 HasPermissionAnyDecorator
42 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
43 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
44 from rhodecode.model.db import User
44 from rhodecode.model.db import User
45 from rhodecode.model.forms import RepoForm
45 from rhodecode.model.forms import RepoForm
46 from rhodecode.model.scm import ScmModel
46 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.repo import RepoModel
48
48
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52 class ReposController(BaseController):
52 class ReposController(BaseController):
53 """REST Controller styled on the Atom Publishing Protocol"""
53 """REST Controller styled on the Atom Publishing Protocol"""
54 # To properly map this controller, ensure your config/routing.py
54 # To properly map this controller, ensure your config/routing.py
55 # file has a resource setup:
55 # file has a resource setup:
56 # map.resource('repo', 'repos')
56 # map.resource('repo', 'repos')
57
57
58 @LoginRequired()
58 @LoginRequired()
59 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
59 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
60 def __before__(self):
60 def __before__(self):
61 c.admin_user = session.get('admin_user')
61 c.admin_user = session.get('admin_user')
62 c.admin_username = session.get('admin_username')
62 c.admin_username = session.get('admin_username')
63 super(ReposController, self).__before__()
63 super(ReposController, self).__before__()
64
64
65 @HasPermissionAllDecorator('hg.admin')
65 @HasPermissionAllDecorator('hg.admin')
66 def index(self, format='html'):
66 def index(self, format='html'):
67 """GET /repos: All items in the collection"""
67 """GET /repos: All items in the collection"""
68 # url('repos')
68 # url('repos')
69 cached_repo_list = ScmModel().get_repos()
69 cached_repo_list = ScmModel().get_repos()
70 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
70 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
71 return render('admin/repos/repos.html')
71 return render('admin/repos/repos.html')
72
72
73 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
73 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
74 def create(self):
74 def create(self):
75 """POST /repos: Create a new item"""
75 """POST /repos: Create a new item"""
76 # url('repos')
76 # url('repos')
77 repo_model = RepoModel()
77 repo_model = RepoModel()
78 _form = RepoForm()()
78 _form = RepoForm()()
79 form_result = {}
79 form_result = {}
80 try:
80 try:
81 form_result = _form.to_python(dict(request.POST))
81 form_result = _form.to_python(dict(request.POST))
82 repo_model.create(form_result, c.rhodecode_user)
82 repo_model.create(form_result, c.rhodecode_user)
83 h.flash(_('created repository %s') % form_result['repo_name'],
83 h.flash(_('created repository %s') % form_result['repo_name'],
84 category='success')
84 category='success')
85
85
86 if request.POST.get('user_created'):
86 if request.POST.get('user_created'):
87 action_logger(self.rhodecode_user, 'user_created_repo',
87 action_logger(self.rhodecode_user, 'user_created_repo',
88 form_result['repo_name'], '', self.sa)
88 form_result['repo_name'], '', self.sa)
89 else:
89 else:
90 action_logger(self.rhodecode_user, 'admin_created_repo',
90 action_logger(self.rhodecode_user, 'admin_created_repo',
91 form_result['repo_name'], '', self.sa)
91 form_result['repo_name'], '', self.sa)
92
92
93 except formencode.Invalid, errors:
93 except formencode.Invalid, errors:
94 c.new_repo = errors.value['repo_name']
94 c.new_repo = errors.value['repo_name']
95
95
96 if request.POST.get('user_created'):
96 if request.POST.get('user_created'):
97 r = render('admin/repos/repo_add_create_repository.html')
97 r = render('admin/repos/repo_add_create_repository.html')
98 else:
98 else:
99 r = render('admin/repos/repo_add.html')
99 r = render('admin/repos/repo_add.html')
100
100
101 return htmlfill.render(
101 return htmlfill.render(
102 r,
102 r,
103 defaults=errors.value,
103 defaults=errors.value,
104 errors=errors.error_dict or {},
104 errors=errors.error_dict or {},
105 prefix_error=False,
105 prefix_error=False,
106 encoding="UTF-8")
106 encoding="UTF-8")
107
107
108 except Exception:
108 except Exception:
109 log.error(traceback.format_exc())
109 log.error(traceback.format_exc())
110 msg = _('error occurred during creation of repository %s') \
110 msg = _('error occurred during creation of repository %s') \
111 % form_result.get('repo_name')
111 % form_result.get('repo_name')
112 h.flash(msg, category='error')
112 h.flash(msg, category='error')
113 if request.POST.get('user_created'):
113 if request.POST.get('user_created'):
114 return redirect(url('home'))
114 return redirect(url('home'))
115 return redirect(url('repos'))
115 return redirect(url('repos'))
116
116
117 @HasPermissionAllDecorator('hg.admin')
117 @HasPermissionAllDecorator('hg.admin')
118 def new(self, format='html'):
118 def new(self, format='html'):
119 """GET /repos/new: Form to create a new item"""
119 """GET /repos/new: Form to create a new item"""
120 new_repo = request.GET.get('repo', '')
120 new_repo = request.GET.get('repo', '')
121 c.new_repo = repo_name_slug(new_repo)
121 c.new_repo = repo_name_slug(new_repo)
122
122
123 return render('admin/repos/repo_add.html')
123 return render('admin/repos/repo_add.html')
124
124
125 @HasPermissionAllDecorator('hg.admin')
125 @HasPermissionAllDecorator('hg.admin')
126 def update(self, repo_name):
126 def update(self, repo_name):
127 """PUT /repos/repo_name: Update an existing item"""
127 """PUT /repos/repo_name: Update an existing item"""
128 # Forms posted to this method should contain a hidden field:
128 # Forms posted to this method should contain a hidden field:
129 # <input type="hidden" name="_method" value="PUT" />
129 # <input type="hidden" name="_method" value="PUT" />
130 # Or using helpers:
130 # Or using helpers:
131 # h.form(url('repo', repo_name=ID),
131 # h.form(url('repo', repo_name=ID),
132 # method='put')
132 # method='put')
133 # url('repo', repo_name=ID)
133 # url('repo', repo_name=ID)
134 repo_model = RepoModel()
134 repo_model = RepoModel()
135 changed_name = repo_name
135 changed_name = repo_name
136 _form = RepoForm(edit=True, old_data={'repo_name':repo_name})()
136 _form = RepoForm(edit=True, old_data={'repo_name':repo_name})()
137
137
138 try:
138 try:
139 form_result = _form.to_python(dict(request.POST))
139 form_result = _form.to_python(dict(request.POST))
140 repo_model.update(repo_name, form_result)
140 repo_model.update(repo_name, form_result)
141 invalidate_cache('get_repo_cached_%s' % repo_name)
141 invalidate_cache('get_repo_cached_%s' % repo_name)
142 h.flash(_('Repository %s updated successfully' % repo_name),
142 h.flash(_('Repository %s updated successfully' % repo_name),
143 category='success')
143 category='success')
144 changed_name = form_result['repo_name']
144 changed_name = form_result['repo_name']
145 action_logger(self.rhodecode_user, 'admin_updated_repo',
145 action_logger(self.rhodecode_user, 'admin_updated_repo',
146 changed_name, '', self.sa)
146 changed_name, '', self.sa)
147
147
148 except formencode.Invalid, errors:
148 except formencode.Invalid, errors:
149 c.repo_info = repo_model.get_by_repo_name(repo_name)
149 c.repo_info = repo_model.get_by_repo_name(repo_name)
150
150 if c.repo_info.stats:
151 if c.repo_info.stats:
151 last_rev = c.repo_info.stats.stat_on_revision
152 last_rev = c.repo_info.stats.stat_on_revision
152 else:
153 else:
153 last_rev = 0
154 last_rev = 0
154 c.stats_revision = last_rev
155 c.stats_revision = last_rev
155 r = ScmModel().get(repo_name)
156 r = ScmModel().get(repo_name)
156 c.repo_last_rev = r.revisions[-1] if r.revisions else 0
157 c.repo_last_rev = r.revisions[-1] if r.revisions else 0
157
158
158 if last_rev == 0:
159 if last_rev == 0:
159 c.stats_percentage = 0
160 c.stats_percentage = 0
160 else:
161 else:
161 c.stats_percentage = '%.2f' % ((float((last_rev)) /
162 c.stats_percentage = '%.2f' % ((float((last_rev)) /
162 c.repo_last_rev) * 100)
163 c.repo_last_rev) * 100)
163
164
164 c.users_array = repo_model.get_users_js()
165 c.users_array = repo_model.get_users_js()
165 c.users_groups_array = repo_model.get_users_groups_js()
166 c.users_groups_array = repo_model.get_users_groups_js()
166
167
167 errors.value.update({'user':c.repo_info.user.username})
168 errors.value.update({'user':c.repo_info.user.username})
168 return htmlfill.render(
169 return htmlfill.render(
169 render('admin/repos/repo_edit.html'),
170 render('admin/repos/repo_edit.html'),
170 defaults=errors.value,
171 defaults=errors.value,
171 errors=errors.error_dict or {},
172 errors=errors.error_dict or {},
172 prefix_error=False,
173 prefix_error=False,
173 encoding="UTF-8")
174 encoding="UTF-8")
174
175
175 except Exception:
176 except Exception:
176 log.error(traceback.format_exc())
177 log.error(traceback.format_exc())
177 h.flash(_('error occurred during update of repository %s') \
178 h.flash(_('error occurred during update of repository %s') \
178 % repo_name, category='error')
179 % repo_name, category='error')
179
180
180 return redirect(url('edit_repo', repo_name=changed_name))
181 return redirect(url('edit_repo', repo_name=changed_name))
181
182
182 @HasPermissionAllDecorator('hg.admin')
183 @HasPermissionAllDecorator('hg.admin')
183 def delete(self, repo_name):
184 def delete(self, repo_name):
184 """DELETE /repos/repo_name: Delete an existing item"""
185 """DELETE /repos/repo_name: Delete an existing item"""
185 # Forms posted to this method should contain a hidden field:
186 # Forms posted to this method should contain a hidden field:
186 # <input type="hidden" name="_method" value="DELETE" />
187 # <input type="hidden" name="_method" value="DELETE" />
187 # Or using helpers:
188 # Or using helpers:
188 # h.form(url('repo', repo_name=ID),
189 # h.form(url('repo', repo_name=ID),
189 # method='delete')
190 # method='delete')
190 # url('repo', repo_name=ID)
191 # url('repo', repo_name=ID)
191
192
192 repo_model = RepoModel()
193 repo_model = RepoModel()
193 repo = repo_model.get_by_repo_name(repo_name)
194 repo = repo_model.get_by_repo_name(repo_name)
194 if not repo:
195 if not repo:
195 h.flash(_('%s repository is not mapped to db perhaps'
196 h.flash(_('%s repository is not mapped to db perhaps'
196 ' it was moved or renamed from the filesystem'
197 ' it was moved or renamed from the filesystem'
197 ' please run the application again'
198 ' please run the application again'
198 ' in order to rescan repositories') % repo_name,
199 ' in order to rescan repositories') % repo_name,
199 category='error')
200 category='error')
200
201
201 return redirect(url('repos'))
202 return redirect(url('repos'))
202 try:
203 try:
203 action_logger(self.rhodecode_user, 'admin_deleted_repo',
204 action_logger(self.rhodecode_user, 'admin_deleted_repo',
204 repo_name, '', self.sa)
205 repo_name, '', self.sa)
205 repo_model.delete(repo)
206 repo_model.delete(repo)
206 invalidate_cache('get_repo_cached_%s' % repo_name)
207 invalidate_cache('get_repo_cached_%s' % repo_name)
207 h.flash(_('deleted repository %s') % repo_name, category='success')
208 h.flash(_('deleted repository %s') % repo_name, category='success')
208
209
209 except Exception, e:
210 except Exception, e:
210 log.error(traceback.format_exc())
211 log.error(traceback.format_exc())
211 h.flash(_('An error occurred during deletion of %s') % repo_name,
212 h.flash(_('An error occurred during deletion of %s') % repo_name,
212 category='error')
213 category='error')
213
214
214 return redirect(url('repos'))
215 return redirect(url('repos'))
215
216
216 @HasPermissionAllDecorator('hg.admin')
217 @HasPermissionAllDecorator('hg.admin')
217 def delete_perm_user(self, repo_name):
218 def delete_perm_user(self, repo_name):
218 """DELETE an existing repository permission user
219 """DELETE an existing repository permission user
219
220
220 :param repo_name:
221 :param repo_name:
221 """
222 """
222
223
223 try:
224 try:
224 repo_model = RepoModel()
225 repo_model = RepoModel()
225 repo_model.delete_perm_user(request.POST, repo_name)
226 repo_model.delete_perm_user(request.POST, repo_name)
226 except Exception, e:
227 except Exception, e:
227 h.flash(_('An error occurred during deletion of repository user'),
228 h.flash(_('An error occurred during deletion of repository user'),
228 category='error')
229 category='error')
229 raise HTTPInternalServerError()
230 raise HTTPInternalServerError()
230
231
231 @HasPermissionAllDecorator('hg.admin')
232 @HasPermissionAllDecorator('hg.admin')
232 def delete_perm_users_group(self, repo_name):
233 def delete_perm_users_group(self, repo_name):
233 """DELETE an existing repository permission users group
234 """DELETE an existing repository permission users group
234
235
235 :param repo_name:
236 :param repo_name:
236 """
237 """
237 try:
238 try:
238 repo_model = RepoModel()
239 repo_model = RepoModel()
239 repo_model.delete_perm_users_group(request.POST, repo_name)
240 repo_model.delete_perm_users_group(request.POST, repo_name)
240 except Exception, e:
241 except Exception, e:
241 h.flash(_('An error occurred during deletion of repository'
242 h.flash(_('An error occurred during deletion of repository'
242 ' users groups'),
243 ' users groups'),
243 category='error')
244 category='error')
244 raise HTTPInternalServerError()
245 raise HTTPInternalServerError()
245
246
246 @HasPermissionAllDecorator('hg.admin')
247 @HasPermissionAllDecorator('hg.admin')
247 def repo_stats(self, repo_name):
248 def repo_stats(self, repo_name):
248 """DELETE an existing repository statistics
249 """DELETE an existing repository statistics
249
250
250 :param repo_name:
251 :param repo_name:
251 """
252 """
252
253
253 try:
254 try:
254 repo_model = RepoModel()
255 repo_model = RepoModel()
255 repo_model.delete_stats(repo_name)
256 repo_model.delete_stats(repo_name)
256 except Exception, e:
257 except Exception, e:
257 h.flash(_('An error occurred during deletion of repository stats'),
258 h.flash(_('An error occurred during deletion of repository stats'),
258 category='error')
259 category='error')
259 return redirect(url('edit_repo', repo_name=repo_name))
260 return redirect(url('edit_repo', repo_name=repo_name))
260
261
261 @HasPermissionAllDecorator('hg.admin')
262 @HasPermissionAllDecorator('hg.admin')
262 def repo_cache(self, repo_name):
263 def repo_cache(self, repo_name):
263 """INVALIDATE existing repository cache
264 """INVALIDATE existing repository cache
264
265
265 :param repo_name:
266 :param repo_name:
266 """
267 """
267
268
268 try:
269 try:
269 ScmModel().mark_for_invalidation(repo_name)
270 ScmModel().mark_for_invalidation(repo_name)
270 except Exception, e:
271 except Exception, e:
271 h.flash(_('An error occurred during cache invalidation'),
272 h.flash(_('An error occurred during cache invalidation'),
272 category='error')
273 category='error')
273 return redirect(url('edit_repo', repo_name=repo_name))
274 return redirect(url('edit_repo', repo_name=repo_name))
274
275
275 @HasPermissionAllDecorator('hg.admin')
276 @HasPermissionAllDecorator('hg.admin')
276 def show(self, repo_name, format='html'):
277 def show(self, repo_name, format='html'):
277 """GET /repos/repo_name: Show a specific item"""
278 """GET /repos/repo_name: Show a specific item"""
278 # url('repo', repo_name=ID)
279 # url('repo', repo_name=ID)
279
280
280 @HasPermissionAllDecorator('hg.admin')
281 @HasPermissionAllDecorator('hg.admin')
281 def edit(self, repo_name, format='html'):
282 def edit(self, repo_name, format='html'):
282 """GET /repos/repo_name/edit: Form to edit an existing item"""
283 """GET /repos/repo_name/edit: Form to edit an existing item"""
283 # url('edit_repo', repo_name=ID)
284 # url('edit_repo', repo_name=ID)
285 r = ScmModel().get(repo_name)[0]
286
284 repo_model = RepoModel()
287 repo_model = RepoModel()
285 r = ScmModel().get(repo_name)
286 c.repo_info = repo_model.get_by_repo_name(repo_name)
288 c.repo_info = repo_model.get_by_repo_name(repo_name)
287
289
288 if c.repo_info is None:
290 if c.repo_info is None:
289 h.flash(_('%s repository is not mapped to db perhaps'
291 h.flash(_('%s repository is not mapped to db perhaps'
290 ' it was created or renamed from the filesystem'
292 ' it was created or renamed from the filesystem'
291 ' please run the application again'
293 ' please run the application again'
292 ' in order to rescan repositories') % repo_name,
294 ' in order to rescan repositories') % repo_name,
293 category='error')
295 category='error')
294
296
295 return redirect(url('repos'))
297 return redirect(url('repos'))
296
298
297 if c.repo_info.stats:
299 if c.repo_info.stats:
298 last_rev = c.repo_info.stats.stat_on_revision
300 last_rev = c.repo_info.stats.stat_on_revision
299 else:
301 else:
300 last_rev = 0
302 last_rev = 0
301 c.stats_revision = last_rev
303 c.stats_revision = last_rev
302
304
303 c.repo_last_rev = r.revisions[-1] if r.revisions else 0
305 c.repo_last_rev = r.revisions[-1] if r.revisions else 0
304
306
305 if last_rev == 0 or c.repo_last_rev == 0:
307 if last_rev == 0 or c.repo_last_rev == 0:
306 c.stats_percentage = 0
308 c.stats_percentage = 0
307 else:
309 else:
308 c.stats_percentage = '%.2f' % ((float((last_rev)) /
310 c.stats_percentage = '%.2f' % ((float((last_rev)) /
309 c.repo_last_rev) * 100)
311 c.repo_last_rev) * 100)
310
312
311 c.users_array = repo_model.get_users_js()
313 c.users_array = repo_model.get_users_js()
312 c.users_groups_array = repo_model.get_users_groups_js()
314 c.users_groups_array = repo_model.get_users_groups_js()
313
315
314 defaults = c.repo_info.get_dict()
316 defaults = c.repo_info.get_dict()
315
317
316 #fill owner
318 #fill owner
317 if c.repo_info.user:
319 if c.repo_info.user:
318 defaults.update({'user':c.repo_info.user.username})
320 defaults.update({'user':c.repo_info.user.username})
319 else:
321 else:
320 replacement_user = self.sa.query(User)\
322 replacement_user = self.sa.query(User)\
321 .filter(User.admin == True).first().username
323 .filter(User.admin == True).first().username
322 defaults.update({'user':replacement_user})
324 defaults.update({'user':replacement_user})
323
325
324
326
325 #fill repository users
327 #fill repository users
326 for p in c.repo_info.repo_to_perm:
328 for p in c.repo_info.repo_to_perm:
327 defaults.update({'u_perm_%s' % p.user.username:
329 defaults.update({'u_perm_%s' % p.user.username:
328 p.permission.permission_name})
330 p.permission.permission_name})
329
331
330 #fill repository groups
332 #fill repository groups
331 for p in c.repo_info.users_group_to_perm:
333 for p in c.repo_info.users_group_to_perm:
332 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
334 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
333 p.permission.permission_name})
335 p.permission.permission_name})
334
336
335 return htmlfill.render(
337 return htmlfill.render(
336 render('admin/repos/repo_edit.html'),
338 render('admin/repos/repo_edit.html'),
337 defaults=defaults,
339 defaults=defaults,
338 encoding="UTF-8",
340 encoding="UTF-8",
339 force_defaults=False
341 force_defaults=False
340 )
342 )
@@ -1,54 +1,53 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.branches
3 rhodecode.controllers.branches
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 branches controller for rhodecode
6 branches controller for rhodecode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import logging
28 import logging
29
29
30 from pylons import tmpl_context as c
30 from pylons import tmpl_context as c
31
31
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.base import BaseController, render
33 from rhodecode.lib.base import BaseController, render
34 from rhodecode.lib.utils import OrderedDict
34 from rhodecode.lib.utils import OrderedDict
35 from rhodecode.model.scm import ScmModel
35 from rhodecode.model.scm import ScmModel
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39 class BranchesController(BaseController):
39 class BranchesController(BaseController):
40
40
41 @LoginRequired()
41 @LoginRequired()
42 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
43 'repository.admin')
43 'repository.admin')
44 def __before__(self):
44 def __before__(self):
45 super(BranchesController, self).__before__()
45 super(BranchesController, self).__before__()
46
46
47 def index(self):
47 def index(self):
48 hg_model = ScmModel()
48 c.repo_info, dbrepo = ScmModel().get(c.repo_name, retval='repo')
49 c.repo_info = hg_model.get_repo(c.repo_name)
50 c.repo_branches = OrderedDict()
49 c.repo_branches = OrderedDict()
51 for name, hash_ in c.repo_info.branches.items():
50 for name, hash_ in c.repo_info.branches.items():
52 c.repo_branches[name] = c.repo_info.get_changeset(hash_)
51 c.repo_branches[name] = c.repo_info.get_changeset(hash_)
53
52
54 return render('branches/branches.html')
53 return render('branches/branches.html')
@@ -1,106 +1,106 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changelog
3 rhodecode.controllers.changelog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changelog controller for rhodecode
6 changelog controller for rhodecode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import logging
28 import logging
29
29
30 try:
30 try:
31 import json
31 import json
32 except ImportError:
32 except ImportError:
33 #python 2.5 compatibility
33 #python 2.5 compatibility
34 import simplejson as json
34 import simplejson as json
35
35
36 from mercurial.graphmod import colored, CHANGESET, revisions as graph_rev
36 from mercurial.graphmod import colored, CHANGESET, revisions as graph_rev
37 from pylons import request, session, tmpl_context as c
37 from pylons import request, session, tmpl_context as c
38
38
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
40 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.base import BaseController, render
41 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.scm import ScmModel
42
42
43 from webhelpers.paginate import Page
43 from webhelpers.paginate import Page
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47 class ChangelogController(BaseController):
47 class ChangelogController(BaseController):
48
48
49 @LoginRequired()
49 @LoginRequired()
50 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
50 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
51 'repository.admin')
51 'repository.admin')
52 def __before__(self):
52 def __before__(self):
53 super(ChangelogController, self).__before__()
53 super(ChangelogController, self).__before__()
54
54
55 def index(self):
55 def index(self):
56 limit = 100
56 limit = 100
57 default = 20
57 default = 20
58 if request.params.get('size'):
58 if request.params.get('size'):
59 try:
59 try:
60 int_size = int(request.params.get('size'))
60 int_size = int(request.params.get('size'))
61 except ValueError:
61 except ValueError:
62 int_size = default
62 int_size = default
63 int_size = int_size if int_size <= limit else limit
63 int_size = int_size if int_size <= limit else limit
64 c.size = int_size
64 c.size = int_size
65 session['changelog_size'] = c.size
65 session['changelog_size'] = c.size
66 session.save()
66 session.save()
67 else:
67 else:
68 c.size = int(session.get('changelog_size', default))
68 c.size = int(session.get('changelog_size', default))
69
69
70 changesets = ScmModel().get_repo(c.repo_name)
70 repo, dbrepo = ScmModel().get(c.repo_name, retval='repo')
71
71
72 p = int(request.params.get('page', 1))
72 p = int(request.params.get('page', 1))
73 c.total_cs = len(changesets)
73 c.total_cs = len(repo)
74 c.pagination = Page(changesets, page=p, item_count=c.total_cs,
74 c.pagination = Page(repo, page=p, item_count=c.total_cs,
75 items_per_page=c.size)
75 items_per_page=c.size)
76
76
77 self._graph(changesets, c.size, p)
77 self._graph(repo, c.size, p)
78
78
79 return render('changelog/changelog.html')
79 return render('changelog/changelog.html')
80
80
81
81
82 def _graph(self, repo, size, p):
82 def _graph(self, repo, size, p):
83 revcount = size
83 revcount = size
84 if not repo.revisions or repo.alias == 'git':
84 if not repo.revisions or repo.alias == 'git':
85 c.jsdata = json.dumps([])
85 c.jsdata = json.dumps([])
86 return
86 return
87
87
88 max_rev = repo.revisions[-1]
88 max_rev = repo.revisions[-1]
89
89
90 offset = 1 if p == 1 else ((p - 1) * revcount + 1)
90 offset = 1 if p == 1 else ((p - 1) * revcount + 1)
91
91
92 rev_start = repo.revisions[(-1 * offset)]
92 rev_start = repo.revisions[(-1 * offset)]
93
93
94 revcount = min(max_rev, revcount)
94 revcount = min(max_rev, revcount)
95 rev_end = max(0, rev_start - revcount)
95 rev_end = max(0, rev_start - revcount)
96 dag = graph_rev(repo._repo, rev_start, rev_end)
96 dag = graph_rev(repo._repo, rev_start, rev_end)
97
97
98 c.dag = tree = list(colored(dag))
98 c.dag = tree = list(colored(dag))
99 data = []
99 data = []
100 for (id, type, ctx, vtx, edges) in tree:
100 for (id, type, ctx, vtx, edges) in tree:
101 if type != CHANGESET:
101 if type != CHANGESET:
102 continue
102 continue
103 data.append(('', vtx, edges))
103 data.append(('', vtx, edges))
104
104
105 c.jsdata = json.dumps(data)
105 c.jsdata = json.dumps(data)
106
106
@@ -1,218 +1,216 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changeset
3 rhodecode.controllers.changeset
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changeset controller for pylons showoing changes beetween
6 changeset controller for pylons showoing changes beetween
7 revisions
7 revisions
8
8
9 :created_on: Apr 25, 2010
9 :created_on: Apr 25, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 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
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; version 2
16 # as published by the Free Software Foundation; version 2
17 # of the License or (at your opinion) any later version of the license.
17 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
27 # MA 02110-1301, USA.
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from pylons import tmpl_context as c, url, request, response
31 from pylons import tmpl_context as c, url, request, response
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34
34
35 import rhodecode.lib.helpers as h
35 import rhodecode.lib.helpers as h
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.base import BaseController, render
38 from rhodecode.lib.utils import EmptyChangeset
38 from rhodecode.lib.utils import EmptyChangeset
39 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.scm import ScmModel
40
40
41 from vcs.exceptions import RepositoryError, ChangesetError, \
41 from vcs.exceptions import RepositoryError, ChangesetError, \
42 ChangesetDoesNotExistError
42 ChangesetDoesNotExistError
43 from vcs.nodes import FileNode
43 from vcs.nodes import FileNode
44 from vcs.utils import diffs as differ
44 from vcs.utils import diffs as differ
45 from vcs.utils.ordered_dict import OrderedDict
45 from vcs.utils.ordered_dict import OrderedDict
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49 class ChangesetController(BaseController):
49 class ChangesetController(BaseController):
50
50
51 @LoginRequired()
51 @LoginRequired()
52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
53 'repository.admin')
53 'repository.admin')
54 def __before__(self):
54 def __before__(self):
55 super(ChangesetController, self).__before__()
55 super(ChangesetController, self).__before__()
56
56
57 def index(self, revision):
57 def index(self, revision):
58 hg_model = ScmModel()
59
58
60 def wrap_to_table(str):
59 def wrap_to_table(str):
61
60
62 return '''<table class="code-difftable">
61 return '''<table class="code-difftable">
63 <tr class="line">
62 <tr class="line">
64 <td class="lineno new"></td>
63 <td class="lineno new"></td>
65 <td class="code"><pre>%s</pre></td>
64 <td class="code"><pre>%s</pre></td>
66 </tr>
65 </tr>
67 </table>''' % str
66 </table>''' % str
68
67
69 #get ranges of revisions if preset
68 #get ranges of revisions if preset
70 rev_range = revision.split('...')[:2]
69 rev_range = revision.split('...')[:2]
71 range_limit = 50
70 range_limit = 50
72 try:
71 try:
73 repo = hg_model.get_repo(c.repo_name)
72 repo, dbrepo = ScmModel().get(c.repo_name, retval='repo')
74 if len(rev_range) == 2:
73 if len(rev_range) == 2:
75 rev_start = rev_range[0]
74 rev_start = rev_range[0]
76 rev_end = rev_range[1]
75 rev_end = rev_range[1]
77 rev_ranges = repo.get_changesets_ranges(rev_start, rev_end,
76 rev_ranges = repo.get_changesets_ranges(rev_start, rev_end,
78 range_limit)
77 range_limit)
79 else:
78 else:
80 rev_ranges = [repo.get_changeset(revision)]
79 rev_ranges = [repo.get_changeset(revision)]
81
80
82 c.cs_ranges = list(rev_ranges)
81 c.cs_ranges = list(rev_ranges)
83
82
84 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
83 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
85 log.error(traceback.format_exc())
84 log.error(traceback.format_exc())
86 h.flash(str(e), category='warning')
85 h.flash(str(e), category='warning')
87 return redirect(url('home'))
86 return redirect(url('home'))
88
87
89 c.changes = OrderedDict()
88 c.changes = OrderedDict()
90 c.sum_added = 0
89 c.sum_added = 0
91 c.sum_removed = 0
90 c.sum_removed = 0
92
91
93
92
94 for changeset in c.cs_ranges:
93 for changeset in c.cs_ranges:
95 c.changes[changeset.raw_id] = []
94 c.changes[changeset.raw_id] = []
96 try:
95 try:
97 changeset_parent = changeset.parents[0]
96 changeset_parent = changeset.parents[0]
98 except IndexError:
97 except IndexError:
99 changeset_parent = None
98 changeset_parent = None
100
99
101
100
102 #==================================================================
101 #==================================================================
103 # ADDED FILES
102 # ADDED FILES
104 #==================================================================
103 #==================================================================
105 for node in changeset.added:
104 for node in changeset.added:
106 filenode_old = FileNode(node.path, '', EmptyChangeset())
105 filenode_old = FileNode(node.path, '', EmptyChangeset())
107 if filenode_old.is_binary or node.is_binary:
106 if filenode_old.is_binary or node.is_binary:
108 diff = wrap_to_table(_('binary file'))
107 diff = wrap_to_table(_('binary file'))
109 else:
108 else:
110 c.sum_added += node.size
109 c.sum_added += node.size
111 if c.sum_added < self.cut_off_limit:
110 if c.sum_added < self.cut_off_limit:
112 f_udiff = differ.get_udiff(filenode_old, node)
111 f_udiff = differ.get_udiff(filenode_old, node)
113 diff = differ.DiffProcessor(f_udiff).as_html()
112 diff = differ.DiffProcessor(f_udiff).as_html()
114
113
115 else:
114 else:
116 diff = wrap_to_table(_('Changeset is to big and was cut'
115 diff = wrap_to_table(_('Changeset is to big and was cut'
117 ' off, see raw changeset instead'))
116 ' off, see raw changeset instead'))
118
117
119 cs1 = None
118 cs1 = None
120 cs2 = node.last_changeset.raw_id
119 cs2 = node.last_changeset.raw_id
121 c.changes[changeset.raw_id].append(('added', node, diff, cs1, cs2))
120 c.changes[changeset.raw_id].append(('added', node, diff, cs1, cs2))
122
121
123 #==================================================================
122 #==================================================================
124 # CHANGED FILES
123 # CHANGED FILES
125 #==================================================================
124 #==================================================================
126 for node in changeset.changed:
125 for node in changeset.changed:
127 try:
126 try:
128 filenode_old = changeset_parent.get_node(node.path)
127 filenode_old = changeset_parent.get_node(node.path)
129 except ChangesetError:
128 except ChangesetError:
130 filenode_old = FileNode(node.path, '', EmptyChangeset())
129 filenode_old = FileNode(node.path, '', EmptyChangeset())
131
130
132 if filenode_old.is_binary or node.is_binary:
131 if filenode_old.is_binary or node.is_binary:
133 diff = wrap_to_table(_('binary file'))
132 diff = wrap_to_table(_('binary file'))
134 else:
133 else:
135
134
136 if c.sum_removed < self.cut_off_limit:
135 if c.sum_removed < self.cut_off_limit:
137 f_udiff = differ.get_udiff(filenode_old, node)
136 f_udiff = differ.get_udiff(filenode_old, node)
138 diff = differ.DiffProcessor(f_udiff).as_html()
137 diff = differ.DiffProcessor(f_udiff).as_html()
139 if diff:
138 if diff:
140 c.sum_removed += len(diff)
139 c.sum_removed += len(diff)
141 else:
140 else:
142 diff = wrap_to_table(_('Changeset is to big and was cut'
141 diff = wrap_to_table(_('Changeset is to big and was cut'
143 ' off, see raw changeset instead'))
142 ' off, see raw changeset instead'))
144
143
145
144
146 cs1 = filenode_old.last_changeset.raw_id
145 cs1 = filenode_old.last_changeset.raw_id
147 cs2 = node.last_changeset.raw_id
146 cs2 = node.last_changeset.raw_id
148 c.changes[changeset.raw_id].append(('changed', node, diff, cs1, cs2))
147 c.changes[changeset.raw_id].append(('changed', node, diff, cs1, cs2))
149
148
150 #==================================================================
149 #==================================================================
151 # REMOVED FILES
150 # REMOVED FILES
152 #==================================================================
151 #==================================================================
153 for node in changeset.removed:
152 for node in changeset.removed:
154 c.changes[changeset.raw_id].append(('removed', node, None, None, None))
153 c.changes[changeset.raw_id].append(('removed', node, None, None, None))
155
154
156 if len(c.cs_ranges) == 1:
155 if len(c.cs_ranges) == 1:
157 c.changeset = c.cs_ranges[0]
156 c.changeset = c.cs_ranges[0]
158 c.changes = c.changes[c.changeset.raw_id]
157 c.changes = c.changes[c.changeset.raw_id]
159
158
160 return render('changeset/changeset.html')
159 return render('changeset/changeset.html')
161 else:
160 else:
162 return render('changeset/changeset_range.html')
161 return render('changeset/changeset_range.html')
163
162
164 def raw_changeset(self, revision):
163 def raw_changeset(self, revision):
165
164
166 hg_model = ScmModel()
167 method = request.GET.get('diff', 'show')
165 method = request.GET.get('diff', 'show')
168 try:
166 try:
169 r = hg_model.get_repo(c.repo_name)
167 repo, dbrepo = ScmModel().get(c.repo_name, retval='repo')
170 c.scm_type = r.alias
168 c.scm_type = repo.alias
171 c.changeset = r.get_changeset(revision)
169 c.changeset = repo.get_changeset(revision)
172 except RepositoryError:
170 except RepositoryError:
173 log.error(traceback.format_exc())
171 log.error(traceback.format_exc())
174 return redirect(url('home'))
172 return redirect(url('home'))
175 else:
173 else:
176 try:
174 try:
177 c.changeset_parent = c.changeset.parents[0]
175 c.changeset_parent = c.changeset.parents[0]
178 except IndexError:
176 except IndexError:
179 c.changeset_parent = None
177 c.changeset_parent = None
180 c.changes = []
178 c.changes = []
181
179
182 for node in c.changeset.added:
180 for node in c.changeset.added:
183 filenode_old = FileNode(node.path, '')
181 filenode_old = FileNode(node.path, '')
184 if filenode_old.is_binary or node.is_binary:
182 if filenode_old.is_binary or node.is_binary:
185 diff = _('binary file') + '\n'
183 diff = _('binary file') + '\n'
186 else:
184 else:
187 f_udiff = differ.get_udiff(filenode_old, node)
185 f_udiff = differ.get_udiff(filenode_old, node)
188 diff = differ.DiffProcessor(f_udiff).raw_diff()
186 diff = differ.DiffProcessor(f_udiff).raw_diff()
189
187
190 cs1 = None
188 cs1 = None
191 cs2 = node.last_changeset.raw_id
189 cs2 = node.last_changeset.raw_id
192 c.changes.append(('added', node, diff, cs1, cs2))
190 c.changes.append(('added', node, diff, cs1, cs2))
193
191
194 for node in c.changeset.changed:
192 for node in c.changeset.changed:
195 filenode_old = c.changeset_parent.get_node(node.path)
193 filenode_old = c.changeset_parent.get_node(node.path)
196 if filenode_old.is_binary or node.is_binary:
194 if filenode_old.is_binary or node.is_binary:
197 diff = _('binary file')
195 diff = _('binary file')
198 else:
196 else:
199 f_udiff = differ.get_udiff(filenode_old, node)
197 f_udiff = differ.get_udiff(filenode_old, node)
200 diff = differ.DiffProcessor(f_udiff).raw_diff()
198 diff = differ.DiffProcessor(f_udiff).raw_diff()
201
199
202 cs1 = filenode_old.last_changeset.raw_id
200 cs1 = filenode_old.last_changeset.raw_id
203 cs2 = node.last_changeset.raw_id
201 cs2 = node.last_changeset.raw_id
204 c.changes.append(('changed', node, diff, cs1, cs2))
202 c.changes.append(('changed', node, diff, cs1, cs2))
205
203
206 response.content_type = 'text/plain'
204 response.content_type = 'text/plain'
207
205
208 if method == 'download':
206 if method == 'download':
209 response.content_disposition = 'attachment; filename=%s.patch' % revision
207 response.content_disposition = 'attachment; filename=%s.patch' % revision
210
208
211 parent = True if len(c.changeset.parents) > 0 else False
209 parent = True if len(c.changeset.parents) > 0 else False
212 c.parent_tmpl = 'Parent %s' % c.changeset.parents[0].raw_id if parent else ''
210 c.parent_tmpl = 'Parent %s' % c.changeset.parents[0].raw_id if parent else ''
213
211
214 c.diffs = ''
212 c.diffs = ''
215 for x in c.changes:
213 for x in c.changes:
216 c.diffs += x[2]
214 c.diffs += x[2]
217
215
218 return render('changeset/raw_changeset.html')
216 return render('changeset/raw_changeset.html')
@@ -1,90 +1,91 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.feed
3 rhodecode.controllers.feed
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Feed controller for rhodecode
6 Feed controller for rhodecode
7
7
8 :created_on: Apr 23, 2010
8 :created_on: Apr 23, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import logging
28 import logging
29
29
30 from pylons import url, response
30 from pylons import url, response
31 from pylons.i18n.translation import _
31
32
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.base import BaseController
34 from rhodecode.lib.base import BaseController
34 from rhodecode.model.scm import ScmModel
35 from rhodecode.model.scm import ScmModel
35
36
36 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
37 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
37
38
38 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
39
40
40 class FeedController(BaseController):
41 class FeedController(BaseController):
41
42
42 @LoginRequired()
43 @LoginRequired()
43 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
44 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
44 'repository.admin')
45 'repository.admin')
45 def __before__(self):
46 def __before__(self):
46 super(FeedController, self).__before__()
47 super(FeedController, self).__before__()
47 #common values for feeds
48 #common values for feeds
48 self.description = 'Changes on %s repository'
49 self.description = _('Changes on %s repository')
49 self.title = "%s feed"
50 self.title = "%s feed"
50 self.language = 'en-us'
51 self.language = 'en-us'
51 self.ttl = "5"
52 self.ttl = "5"
52 self.feed_nr = 10
53 self.feed_nr = 10
53
54
54 def atom(self, repo_name):
55 def atom(self, repo_name):
55 """Produce an atom-1.0 feed via feedgenerator module"""
56 """Produce an atom-1.0 feed via feedgenerator module"""
56 feed = Atom1Feed(title=self.title % repo_name,
57 feed = Atom1Feed(title=self.title % repo_name,
57 link=url('summary_home', repo_name=repo_name, qualified=True),
58 link=url('summary_home', repo_name=repo_name, qualified=True),
58 description=self.description % repo_name,
59 description=self.description % repo_name,
59 language=self.language,
60 language=self.language,
60 ttl=self.ttl)
61 ttl=self.ttl)
61
62
62 changesets = ScmModel().get_repo(repo_name)
63 repo, dbrepo = ScmModel().get(repo_name, retval='repo')
63
64
64 for cs in changesets[:self.feed_nr]:
65 for cs in repo[:self.feed_nr]:
65 feed.add_item(title=cs.message,
66 feed.add_item(title=cs.message,
66 link=url('changeset_home', repo_name=repo_name,
67 link=url('changeset_home', repo_name=repo_name,
67 revision=cs.raw_id, qualified=True),
68 revision=cs.raw_id, qualified=True),
68 description=str(cs.date))
69 description=str(cs.date))
69
70
70 response.content_type = feed.mime_type
71 response.content_type = feed.mime_type
71 return feed.writeString('utf-8')
72 return feed.writeString('utf-8')
72
73
73
74
74 def rss(self, repo_name):
75 def rss(self, repo_name):
75 """Produce an rss2 feed via feedgenerator module"""
76 """Produce an rss2 feed via feedgenerator module"""
76 feed = Rss201rev2Feed(title=self.title % repo_name,
77 feed = Rss201rev2Feed(title=self.title % repo_name,
77 link=url('summary_home', repo_name=repo_name, qualified=True),
78 link=url('summary_home', repo_name=repo_name, qualified=True),
78 description=self.description % repo_name,
79 description=self.description % repo_name,
79 language=self.language,
80 language=self.language,
80 ttl=self.ttl)
81 ttl=self.ttl)
81
82
82 changesets = ScmModel().get_repo(repo_name)
83 repo, dbrepo = ScmModel().get(repo_name, retval='repo')
83 for cs in changesets[:self.feed_nr]:
84 for cs in repo[:self.feed_nr]:
84 feed.add_item(title=cs.message,
85 feed.add_item(title=cs.message,
85 link=url('changeset_home', repo_name=repo_name,
86 link=url('changeset_home', repo_name=repo_name,
86 revision=cs.raw_id, qualified=True),
87 revision=cs.raw_id, qualified=True),
87 description=str(cs.date))
88 description=str(cs.date))
88
89
89 response.content_type = feed.mime_type
90 response.content_type = feed.mime_type
90 return feed.writeString('utf-8')
91 return feed.writeString('utf-8')
@@ -1,271 +1,267 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.files
3 rhodecode.controllers.files
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Files controller for RhodeCode
6 Files controller for RhodeCode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27 import tempfile
27 import tempfile
28 import logging
28 import logging
29 import rhodecode.lib.helpers as h
29 import rhodecode.lib.helpers as h
30
30
31 from mercurial import archival
31 from mercurial import archival
32
32
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from pylons.controllers.util import redirect
35 from pylons.controllers.util import redirect
36
36
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 from rhodecode.lib.base import BaseController, render
38 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.utils import EmptyChangeset
39 from rhodecode.lib.utils import EmptyChangeset
40 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.scm import ScmModel
41
41
42 from vcs.backends import ARCHIVE_SPECS
42 from vcs.backends import ARCHIVE_SPECS
43 from vcs.exceptions import RepositoryError, ChangesetError, \
43 from vcs.exceptions import RepositoryError, ChangesetError, \
44 ChangesetDoesNotExistError, EmptyRepositoryError, ImproperArchiveTypeError
44 ChangesetDoesNotExistError, EmptyRepositoryError, ImproperArchiveTypeError
45 from vcs.nodes import FileNode
45 from vcs.nodes import FileNode
46 from vcs.utils import diffs as differ
46 from vcs.utils import diffs as differ
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50 class FilesController(BaseController):
50 class FilesController(BaseController):
51
51
52 @LoginRequired()
52 @LoginRequired()
53 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
53 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
54 'repository.admin')
54 'repository.admin')
55 def __before__(self):
55 def __before__(self):
56 super(FilesController, self).__before__()
56 super(FilesController, self).__before__()
57 c.cut_off_limit = self.cut_off_limit
57 c.cut_off_limit = self.cut_off_limit
58
58
59 def index(self, repo_name, revision, f_path):
59 def index(self, repo_name, revision, f_path):
60 hg_model = ScmModel()
60 c.repo, dbrepo = ScmModel().get(c.repo_name, retval='repo')
61 c.repo = hg_model.get_repo(c.repo_name)
61
62
62
63 try:
63 try:
64 #reditect to given revision from form
64 #reditect to given revision from form
65 post_revision = request.POST.get('at_rev', None)
65 post_revision = request.POST.get('at_rev', None)
66 if post_revision:
66 if post_revision:
67 post_revision = c.repo.get_changeset(post_revision).raw_id
67 post_revision = c.repo.get_changeset(post_revision).raw_id
68 redirect(url('files_home', repo_name=c.repo_name,
68 redirect(url('files_home', repo_name=c.repo_name,
69 revision=post_revision, f_path=f_path))
69 revision=post_revision, f_path=f_path))
70
70
71 c.branch = request.GET.get('branch', None)
71 c.branch = request.GET.get('branch', None)
72
72
73 c.f_path = f_path
73 c.f_path = f_path
74
74
75 c.changeset = c.repo.get_changeset(revision)
75 c.changeset = c.repo.get_changeset(revision)
76 cur_rev = c.changeset.revision
76 cur_rev = c.changeset.revision
77
77
78 #prev link
78 #prev link
79 try:
79 try:
80 prev_rev = c.repo.get_changeset(cur_rev).prev(c.branch).raw_id
80 prev_rev = c.repo.get_changeset(cur_rev).prev(c.branch).raw_id
81 c.url_prev = url('files_home', repo_name=c.repo_name,
81 c.url_prev = url('files_home', repo_name=c.repo_name,
82 revision=prev_rev, f_path=f_path)
82 revision=prev_rev, f_path=f_path)
83 if c.branch:
83 if c.branch:
84 c.url_prev += '?branch=%s' % c.branch
84 c.url_prev += '?branch=%s' % c.branch
85 except ChangesetDoesNotExistError:
85 except ChangesetDoesNotExistError:
86 c.url_prev = '#'
86 c.url_prev = '#'
87
87
88 #next link
88 #next link
89 try:
89 try:
90 next_rev = c.repo.get_changeset(cur_rev).next(c.branch).raw_id
90 next_rev = c.repo.get_changeset(cur_rev).next(c.branch).raw_id
91 c.url_next = url('files_home', repo_name=c.repo_name,
91 c.url_next = url('files_home', repo_name=c.repo_name,
92 revision=next_rev, f_path=f_path)
92 revision=next_rev, f_path=f_path)
93 if c.branch:
93 if c.branch:
94 c.url_next += '?branch=%s' % c.branch
94 c.url_next += '?branch=%s' % c.branch
95 except ChangesetDoesNotExistError:
95 except ChangesetDoesNotExistError:
96 c.url_next = '#'
96 c.url_next = '#'
97
97
98 #files
98 #files
99 try:
99 try:
100 c.files_list = c.changeset.get_node(f_path)
100 c.files_list = c.changeset.get_node(f_path)
101 c.file_history = self._get_history(c.repo, c.files_list, f_path)
101 c.file_history = self._get_history(c.repo, c.files_list, f_path)
102 except RepositoryError, e:
102 except RepositoryError, e:
103 h.flash(str(e), category='warning')
103 h.flash(str(e), category='warning')
104 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
104 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
105
105
106 except EmptyRepositoryError, e:
106 except EmptyRepositoryError, e:
107 h.flash(_('There are no files yet'), category='warning')
107 h.flash(_('There are no files yet'), category='warning')
108 redirect(h.url('summary_home', repo_name=repo_name))
108 redirect(h.url('summary_home', repo_name=repo_name))
109
109
110 except RepositoryError, e:
110 except RepositoryError, e:
111 h.flash(str(e), category='warning')
111 h.flash(str(e), category='warning')
112 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
112 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
113
113
114
114
115
115
116 return render('files/files.html')
116 return render('files/files.html')
117
117
118 def rawfile(self, repo_name, revision, f_path):
118 def rawfile(self, repo_name, revision, f_path):
119 hg_model = ScmModel()
119 c.repo, dbrepo = ScmModel().get(c.repo_name, retval='repo')
120 c.repo = hg_model.get_repo(c.repo_name)
121 file_node = c.repo.get_changeset(revision).get_node(f_path)
120 file_node = c.repo.get_changeset(revision).get_node(f_path)
122 response.content_type = file_node.mimetype
121 response.content_type = file_node.mimetype
123 response.content_disposition = 'attachment; filename=%s' \
122 response.content_disposition = 'attachment; filename=%s' \
124 % f_path.split('/')[-1]
123 % f_path.split('/')[-1]
125 return file_node.content
124 return file_node.content
126
125
127 def raw(self, repo_name, revision, f_path):
126 def raw(self, repo_name, revision, f_path):
128 hg_model = ScmModel()
127 c.repo, dbrepo = ScmModel().get(c.repo_name, retval='repo')
129 c.repo = hg_model.get_repo(c.repo_name)
130 file_node = c.repo.get_changeset(revision).get_node(f_path)
128 file_node = c.repo.get_changeset(revision).get_node(f_path)
131 response.content_type = 'text/plain'
129 response.content_type = 'text/plain'
132
130
133 return file_node.content
131 return file_node.content
134
132
135 def annotate(self, repo_name, revision, f_path):
133 def annotate(self, repo_name, revision, f_path):
136 hg_model = ScmModel()
134 c.repo, dbrepo = ScmModel().get(c.repo_name, retval='repo')
137 c.repo = hg_model.get_repo(c.repo_name)
138
135
139 try:
136 try:
140 c.cs = c.repo.get_changeset(revision)
137 c.cs = c.repo.get_changeset(revision)
141 c.file = c.cs.get_node(f_path)
138 c.file = c.cs.get_node(f_path)
142 except RepositoryError, e:
139 except RepositoryError, e:
143 h.flash(str(e), category='warning')
140 h.flash(str(e), category='warning')
144 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
141 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
145
142
146 c.file_history = self._get_history(c.repo, c.file, f_path)
143 c.file_history = self._get_history(c.repo, c.file, f_path)
147
144
148 c.f_path = f_path
145 c.f_path = f_path
149
146
150 return render('files/files_annotate.html')
147 return render('files/files_annotate.html')
151
148
152 def archivefile(self, repo_name, fname):
149 def archivefile(self, repo_name, fname):
153
150
154 fileformat = None
151 fileformat = None
155 revision = None
152 revision = None
156 ext = None
153 ext = None
157
154
158 for a_type, ext_data in ARCHIVE_SPECS.items():
155 for a_type, ext_data in ARCHIVE_SPECS.items():
159 archive_spec = fname.split(ext_data[1])
156 archive_spec = fname.split(ext_data[1])
160 if len(archive_spec) == 2 and archive_spec[1] == '':
157 if len(archive_spec) == 2 and archive_spec[1] == '':
161 fileformat = a_type or ext_data[1]
158 fileformat = a_type or ext_data[1]
162 revision = archive_spec[0]
159 revision = archive_spec[0]
163 ext = ext_data[1]
160 ext = ext_data[1]
164
161
165 try:
162 try:
166 repo = ScmModel().get_repo(repo_name)
163 repo, dbrepo = ScmModel().get(repo_name)
167
164
168 if repo.dbrepo.enable_downloads is False:
165 if dbrepo.enable_downloads is False:
169 return _('downloads disabled')
166 return _('downloads disabled')
170
167
171 cs = repo.get_changeset(revision)
168 cs = repo.get_changeset(revision)
172 content_type = ARCHIVE_SPECS[fileformat][0]
169 content_type = ARCHIVE_SPECS[fileformat][0]
173 except ChangesetDoesNotExistError:
170 except ChangesetDoesNotExistError:
174 return _('Unknown revision %s') % revision
171 return _('Unknown revision %s') % revision
175 except EmptyRepositoryError:
172 except EmptyRepositoryError:
176 return _('Empty repository')
173 return _('Empty repository')
177 except (ImproperArchiveTypeError, KeyError):
174 except (ImproperArchiveTypeError, KeyError):
178 return _('Unknown archive type')
175 return _('Unknown archive type')
179
176
180 response.content_type = content_type
177 response.content_type = content_type
181 response.content_disposition = 'attachment; filename=%s-%s%s' \
178 response.content_disposition = 'attachment; filename=%s-%s%s' \
182 % (repo_name, revision, ext)
179 % (repo_name, revision, ext)
183
180
184 return cs.get_chunked_archive(kind=fileformat)
181 return cs.get_chunked_archive(kind=fileformat)
185
182
186
183
187 def diff(self, repo_name, f_path):
184 def diff(self, repo_name, f_path):
188 hg_model = ScmModel()
189 diff1 = request.GET.get('diff1')
185 diff1 = request.GET.get('diff1')
190 diff2 = request.GET.get('diff2')
186 diff2 = request.GET.get('diff2')
191 c.action = request.GET.get('diff')
187 c.action = request.GET.get('diff')
192 c.no_changes = diff1 == diff2
188 c.no_changes = diff1 == diff2
193 c.f_path = f_path
189 c.f_path = f_path
194 c.repo = hg_model.get_repo(c.repo_name)
190 c.repo, dbrepo = ScmModel().get(c.repo_name, retval='repo')
195
191
196 try:
192 try:
197 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
193 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
198 c.changeset_1 = c.repo.get_changeset(diff1)
194 c.changeset_1 = c.repo.get_changeset(diff1)
199 node1 = c.changeset_1.get_node(f_path)
195 node1 = c.changeset_1.get_node(f_path)
200 else:
196 else:
201 c.changeset_1 = EmptyChangeset()
197 c.changeset_1 = EmptyChangeset()
202 node1 = FileNode('.', '', changeset=c.changeset_1)
198 node1 = FileNode('.', '', changeset=c.changeset_1)
203
199
204 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
200 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
205 c.changeset_2 = c.repo.get_changeset(diff2)
201 c.changeset_2 = c.repo.get_changeset(diff2)
206 node2 = c.changeset_2.get_node(f_path)
202 node2 = c.changeset_2.get_node(f_path)
207 else:
203 else:
208 c.changeset_2 = EmptyChangeset()
204 c.changeset_2 = EmptyChangeset()
209 node2 = FileNode('.', '', changeset=c.changeset_2)
205 node2 = FileNode('.', '', changeset=c.changeset_2)
210 except RepositoryError:
206 except RepositoryError:
211 return redirect(url('files_home',
207 return redirect(url('files_home',
212 repo_name=c.repo_name, f_path=f_path))
208 repo_name=c.repo_name, f_path=f_path))
213
209
214 f_udiff = differ.get_udiff(node1, node2)
210 f_udiff = differ.get_udiff(node1, node2)
215 diff = differ.DiffProcessor(f_udiff)
211 diff = differ.DiffProcessor(f_udiff)
216
212
217 if c.action == 'download':
213 if c.action == 'download':
218 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
214 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
219 response.content_type = 'text/plain'
215 response.content_type = 'text/plain'
220 response.content_disposition = 'attachment; filename=%s' \
216 response.content_disposition = 'attachment; filename=%s' \
221 % diff_name
217 % diff_name
222 return diff.raw_diff()
218 return diff.raw_diff()
223
219
224 elif c.action == 'raw':
220 elif c.action == 'raw':
225 response.content_type = 'text/plain'
221 response.content_type = 'text/plain'
226 return diff.raw_diff()
222 return diff.raw_diff()
227
223
228 elif c.action == 'diff':
224 elif c.action == 'diff':
229 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
225 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
230 c.cur_diff = _('Diff is to big to display')
226 c.cur_diff = _('Diff is to big to display')
231 else:
227 else:
232 c.cur_diff = diff.as_html()
228 c.cur_diff = diff.as_html()
233 else:
229 else:
234 #default option
230 #default option
235 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
231 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
236 c.cur_diff = _('Diff is to big to display')
232 c.cur_diff = _('Diff is to big to display')
237 else:
233 else:
238 c.cur_diff = diff.as_html()
234 c.cur_diff = diff.as_html()
239
235
240 if not c.cur_diff: c.no_changes = True
236 if not c.cur_diff: c.no_changes = True
241 return render('files/file_diff.html')
237 return render('files/file_diff.html')
242
238
243 def _get_history(self, repo, node, f_path):
239 def _get_history(self, repo, node, f_path):
244 from vcs.nodes import NodeKind
240 from vcs.nodes import NodeKind
245 if not node.kind is NodeKind.FILE:
241 if not node.kind is NodeKind.FILE:
246 return []
242 return []
247 changesets = node.history
243 changesets = node.history
248 hist_l = []
244 hist_l = []
249
245
250 changesets_group = ([], _("Changesets"))
246 changesets_group = ([], _("Changesets"))
251 branches_group = ([], _("Branches"))
247 branches_group = ([], _("Branches"))
252 tags_group = ([], _("Tags"))
248 tags_group = ([], _("Tags"))
253
249
254 for chs in changesets:
250 for chs in changesets:
255 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
251 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
256 changesets_group[0].append((chs.raw_id, n_desc,))
252 changesets_group[0].append((chs.raw_id, n_desc,))
257
253
258 hist_l.append(changesets_group)
254 hist_l.append(changesets_group)
259
255
260 for name, chs in c.repository_branches.items():
256 for name, chs in c.repository_branches.items():
261 #chs = chs.split(':')[-1]
257 #chs = chs.split(':')[-1]
262 branches_group[0].append((chs, name),)
258 branches_group[0].append((chs, name),)
263 hist_l.append(branches_group)
259 hist_l.append(branches_group)
264
260
265 for name, chs in c.repository_tags.items():
261 for name, chs in c.repository_tags.items():
266 #chs = chs.split(':')[-1]
262 #chs = chs.split(':')[-1]
267 tags_group[0].append((chs, name),)
263 tags_group[0].append((chs, name),)
268 hist_l.append(tags_group)
264 hist_l.append(tags_group)
269
265
270 return hist_l
266 return hist_l
271
267
@@ -1,56 +1,56 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.shortlog
3 rhodecode.controllers.shortlog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Shortlog controller for rhodecode
6 Shortlog controller for rhodecode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import logging
28 import logging
29
29
30 from pylons import tmpl_context as c, request
30 from pylons import tmpl_context as c, request
31
31
32 from webhelpers.paginate import Page
32 from webhelpers.paginate import Page
33
33
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.base import BaseController, render
35 from rhodecode.lib.base import BaseController, render
36 from rhodecode.model.scm import ScmModel
36 from rhodecode.model.scm import ScmModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40 class ShortlogController(BaseController):
40 class ShortlogController(BaseController):
41
41
42 @LoginRequired()
42 @LoginRequired()
43 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
43 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
44 'repository.admin')
44 'repository.admin')
45 def __before__(self):
45 def __before__(self):
46 super(ShortlogController, self).__before__()
46 super(ShortlogController, self).__before__()
47
47
48 def index(self):
48 def index(self):
49 p = int(request.params.get('page', 1))
49 p = int(request.params.get('page', 1))
50 repo = ScmModel().get_repo(c.repo_name)
50 repo, dbrepo = ScmModel().get(c.repo_name, 'repo')
51 c.repo_changesets = Page(repo, page=p, items_per_page=20)
51 c.repo_changesets = Page(repo, page=p, items_per_page=20)
52 c.shortlog_data = render('shortlog/shortlog_data.html')
52 c.shortlog_data = render('shortlog/shortlog_data.html')
53 if request.params.get('partial'):
53 if request.params.get('partial'):
54 return c.shortlog_data
54 return c.shortlog_data
55 r = render('shortlog/shortlog.html')
55 r = render('shortlog/shortlog.html')
56 return r
56 return r
@@ -1,170 +1,171 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.summary
3 rhodecode.controllers.summary
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Summary controller for Rhodecode
6 Summary controller for Rhodecode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import calendar
28 import calendar
29 import logging
29 import logging
30 from time import mktime
30 from time import mktime
31 from datetime import datetime, timedelta, date
31 from datetime import datetime, timedelta, date
32
32
33 from vcs.exceptions import ChangesetError
33 from vcs.exceptions import ChangesetError
34
34
35 from pylons import tmpl_context as c, request, url
35 from pylons import tmpl_context as c, request, url
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.db import Statistics
39 from rhodecode.model.db import Statistics
40
40
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.utils import OrderedDict, EmptyChangeset
43 from rhodecode.lib.utils import OrderedDict, EmptyChangeset
44
44
45 from rhodecode.lib.celerylib import run_task
45 from rhodecode.lib.celerylib import run_task
46 from rhodecode.lib.celerylib.tasks import get_commits_stats
46 from rhodecode.lib.celerylib.tasks import get_commits_stats
47
47
48 from webhelpers.paginate import Page
48 from webhelpers.paginate import Page
49
49
50 try:
50 try:
51 import json
51 import json
52 except ImportError:
52 except ImportError:
53 #python 2.5 compatibility
53 #python 2.5 compatibility
54 import simplejson as json
54 import simplejson as json
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57 class SummaryController(BaseController):
57 class SummaryController(BaseController):
58
58
59 @LoginRequired()
59 @LoginRequired()
60 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
60 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
61 'repository.admin')
61 'repository.admin')
62 def __before__(self):
62 def __before__(self):
63 super(SummaryController, self).__before__()
63 super(SummaryController, self).__before__()
64
64
65 def index(self):
65 def index(self):
66 scm_model = ScmModel()
66 scm_model = ScmModel()
67 c.repo_info = scm_model.get_repo(c.repo_name)
67 c.repo, dbrepo = scm_model.get(c.repo_name)
68 c.dbrepo = dbrepo
68 c.following = scm_model.is_following_repo(c.repo_name,
69 c.following = scm_model.is_following_repo(c.repo_name,
69 c.rhodecode_user.user_id)
70 c.rhodecode_user.user_id)
70 def url_generator(**kw):
71 def url_generator(**kw):
71 return url('shortlog_home', repo_name=c.repo_name, **kw)
72 return url('shortlog_home', repo_name=c.repo_name, **kw)
72
73
73 c.repo_changesets = Page(c.repo_info, page=1, items_per_page=10,
74 c.repo_changesets = Page(c.repo, page=1, items_per_page=10,
74 url=url_generator)
75 url=url_generator)
75
76
76 e = request.environ
77 e = request.environ
77
78
78 if self.rhodecode_user.username == 'default':
79 if self.rhodecode_user.username == 'default':
79 #for default(anonymous) user we don't need to pass credentials
80 #for default(anonymous) user we don't need to pass credentials
80 username = ''
81 username = ''
81 password = ''
82 password = ''
82 else:
83 else:
83 username = str(c.rhodecode_user.username)
84 username = str(c.rhodecode_user.username)
84 password = '@'
85 password = '@'
85
86
86 uri = u'%(protocol)s://%(user)s%(password)s%(host)s%(prefix)s/%(repo_name)s' % {
87 uri = u'%(protocol)s://%(user)s%(password)s%(host)s%(prefix)s/%(repo_name)s' % {
87 'protocol': e.get('wsgi.url_scheme'),
88 'protocol': e.get('wsgi.url_scheme'),
88 'user':username,
89 'user':username,
89 'password':password,
90 'password':password,
90 'host':e.get('HTTP_HOST'),
91 'host':e.get('HTTP_HOST'),
91 'prefix':e.get('SCRIPT_NAME'),
92 'prefix':e.get('SCRIPT_NAME'),
92 'repo_name':c.repo_name, }
93 'repo_name':c.repo_name, }
93 c.clone_repo_url = uri
94 c.clone_repo_url = uri
94 c.repo_tags = OrderedDict()
95 c.repo_tags = OrderedDict()
95 for name, hash in c.repo_info.tags.items()[:10]:
96 for name, hash in c.repo.tags.items()[:10]:
96 try:
97 try:
97 c.repo_tags[name] = c.repo_info.get_changeset(hash)
98 c.repo_tags[name] = c.repo.get_changeset(hash)
98 except ChangesetError:
99 except ChangesetError:
99 c.repo_tags[name] = EmptyChangeset(hash)
100 c.repo_tags[name] = EmptyChangeset(hash)
100
101
101 c.repo_branches = OrderedDict()
102 c.repo_branches = OrderedDict()
102 for name, hash in c.repo_info.branches.items()[:10]:
103 for name, hash in c.repo.branches.items()[:10]:
103 try:
104 try:
104 c.repo_branches[name] = c.repo_info.get_changeset(hash)
105 c.repo_branches[name] = c.repo.get_changeset(hash)
105 except ChangesetError:
106 except ChangesetError:
106 c.repo_branches[name] = EmptyChangeset(hash)
107 c.repo_branches[name] = EmptyChangeset(hash)
107
108
108 td = date.today() + timedelta(days=1)
109 td = date.today() + timedelta(days=1)
109 td_1m = td - timedelta(days=calendar.mdays[td.month])
110 td_1m = td - timedelta(days=calendar.mdays[td.month])
110 td_1y = td - timedelta(days=365)
111 td_1y = td - timedelta(days=365)
111
112
112 ts_min_m = mktime(td_1m.timetuple())
113 ts_min_m = mktime(td_1m.timetuple())
113 ts_min_y = mktime(td_1y.timetuple())
114 ts_min_y = mktime(td_1y.timetuple())
114 ts_max_y = mktime(td.timetuple())
115 ts_max_y = mktime(td.timetuple())
115
116
116 if c.repo_info.dbrepo.enable_statistics:
117 if dbrepo.enable_statistics:
117 c.no_data_msg = _('No data loaded yet')
118 c.no_data_msg = _('No data loaded yet')
118 run_task(get_commits_stats, c.repo_info.name, ts_min_y, ts_max_y)
119 run_task(get_commits_stats, c.repo.name, ts_min_y, ts_max_y)
119 else:
120 else:
120 c.no_data_msg = _('Statistics update are disabled for this repository')
121 c.no_data_msg = _('Statistics update are disabled for this repository')
121 c.ts_min = ts_min_m
122 c.ts_min = ts_min_m
122 c.ts_max = ts_max_y
123 c.ts_max = ts_max_y
123
124
124 stats = self.sa.query(Statistics)\
125 stats = self.sa.query(Statistics)\
125 .filter(Statistics.repository == c.repo_info.dbrepo)\
126 .filter(Statistics.repository == dbrepo)\
126 .scalar()
127 .scalar()
127
128
128
129
129 if stats and stats.languages:
130 if stats and stats.languages:
130 c.no_data = False is c.repo_info.dbrepo.enable_statistics
131 c.no_data = False is dbrepo.enable_statistics
131 lang_stats = json.loads(stats.languages)
132 lang_stats = json.loads(stats.languages)
132 c.commit_data = stats.commit_activity
133 c.commit_data = stats.commit_activity
133 c.overview_data = stats.commit_activity_combined
134 c.overview_data = stats.commit_activity_combined
134 c.trending_languages = json.dumps(OrderedDict(
135 c.trending_languages = json.dumps(OrderedDict(
135 sorted(lang_stats.items(), reverse=True,
136 sorted(lang_stats.items(), reverse=True,
136 key=lambda k: k[1])[:10]
137 key=lambda k: k[1])[:10]
137 )
138 )
138 )
139 )
139 else:
140 else:
140 c.commit_data = json.dumps({})
141 c.commit_data = json.dumps({})
141 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10] ])
142 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10] ])
142 c.trending_languages = json.dumps({})
143 c.trending_languages = json.dumps({})
143 c.no_data = True
144 c.no_data = True
144
145
145 c.enable_downloads = c.repo_info.dbrepo.enable_downloads
146 c.enable_downloads = dbrepo.enable_downloads
146 if c.enable_downloads:
147 if c.enable_downloads:
147 c.download_options = self._get_download_links(c.repo_info)
148 c.download_options = self._get_download_links(c.repo)
148
149
149 return render('summary/summary.html')
150 return render('summary/summary.html')
150
151
151
152
152
153
153 def _get_download_links(self, repo):
154 def _get_download_links(self, repo):
154
155
155 download_l = []
156 download_l = []
156
157
157 branches_group = ([], _("Branches"))
158 branches_group = ([], _("Branches"))
158 tags_group = ([], _("Tags"))
159 tags_group = ([], _("Tags"))
159
160
160 for name, chs in c.repository_branches.items():
161 for name, chs in c.repository_branches.items():
161 #chs = chs.split(':')[-1]
162 #chs = chs.split(':')[-1]
162 branches_group[0].append((chs, name),)
163 branches_group[0].append((chs, name),)
163 download_l.append(branches_group)
164 download_l.append(branches_group)
164
165
165 for name, chs in c.repository_tags.items():
166 for name, chs in c.repository_tags.items():
166 #chs = chs.split(':')[-1]
167 #chs = chs.split(':')[-1]
167 tags_group[0].append((chs, name),)
168 tags_group[0].append((chs, name),)
168 download_l.append(tags_group)
169 download_l.append(tags_group)
169
170
170 return download_l
171 return download_l
@@ -1,53 +1,52 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.tags
3 rhodecode.controllers.tags
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Tags controller for rhodecode
6 Tags controller for rhodecode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27 import logging
27 import logging
28
28
29 from pylons import tmpl_context as c
29 from pylons import tmpl_context as c
30
30
31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 from rhodecode.lib.base import BaseController, render
32 from rhodecode.lib.base import BaseController, render
33 from rhodecode.lib.utils import OrderedDict
33 from rhodecode.lib.utils import OrderedDict
34 from rhodecode.model.scm import ScmModel
34 from rhodecode.model.scm import ScmModel
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38 class TagsController(BaseController):
38 class TagsController(BaseController):
39
39
40 @LoginRequired()
40 @LoginRequired()
41 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 'repository.admin')
42 'repository.admin')
43 def __before__(self):
43 def __before__(self):
44 super(TagsController, self).__before__()
44 super(TagsController, self).__before__()
45
45
46 def index(self):
46 def index(self):
47 hg_model = ScmModel()
47 c.repo_info, dbrepo = ScmModel().get(c.repo_name, retval='repo')
48 c.repo_info = hg_model.get_repo(c.repo_name)
49 c.repo_tags = OrderedDict()
48 c.repo_tags = OrderedDict()
50 for name, hash_ in c.repo_info.tags.items():
49 for name, hash_ in c.repo_info.tags.items():
51 c.repo_tags[name] = c.repo_info.get_changeset(hash_)
50 c.repo_tags[name] = c.repo_info.get_changeset(hash_)
52
51
53 return render('tags/tags.html')
52 return render('tags/tags.html')
@@ -1,54 +1,54 b''
1 """The base Controller API
1 """The base Controller API
2
2
3 Provides the BaseController class for subclassing.
3 Provides the BaseController class for subclassing.
4 """
4 """
5 from pylons import config, tmpl_context as c, request, session
5 from pylons import config, tmpl_context as c, request, session
6 from pylons.controllers import WSGIController
6 from pylons.controllers import WSGIController
7 from pylons.templating import render_mako as render
7 from pylons.templating import render_mako as render
8 from rhodecode import __version__
8 from rhodecode import __version__
9 from rhodecode.lib import auth
9 from rhodecode.lib import auth
10 from rhodecode.lib.utils import get_repo_slug
10 from rhodecode.lib.utils import get_repo_slug
11 from rhodecode.model import meta
11 from rhodecode.model import meta
12 from rhodecode.model.scm import ScmModel
12 from rhodecode.model.scm import ScmModel
13 from rhodecode import BACKENDS
13 from rhodecode import BACKENDS
14
14
15 class BaseController(WSGIController):
15 class BaseController(WSGIController):
16
16
17 def __before__(self):
17 def __before__(self):
18 c.rhodecode_version = __version__
18 c.rhodecode_version = __version__
19 c.rhodecode_name = config.get('rhodecode_title')
19 c.rhodecode_name = config.get('rhodecode_title')
20 c.ga_code = config.get('rhodecode_ga_code')
20 c.ga_code = config.get('rhodecode_ga_code')
21 c.repo_name = get_repo_slug(request)
21 c.repo_name = get_repo_slug(request)
22 c.backends = BACKENDS.keys()
22 c.backends = BACKENDS.keys()
23 self.cut_off_limit = int(config.get('cut_off_limit'))
23 self.cut_off_limit = int(config.get('cut_off_limit'))
24
24
25 self.sa = meta.Session()
25 self.sa = meta.Session()
26 scm_model = ScmModel(self.sa)
26 scm_model = ScmModel(self.sa)
27 c.cached_repo_list = scm_model.get_repos()
27 c.cached_repo_list = scm_model.get_repos()
28 #c.unread_journal = scm_model.get_unread_journal()
28 #c.unread_journal = scm_model.get_unread_journal()
29
29
30 if c.repo_name:
30 if c.repo_name:
31 cached_repo = scm_model.get(c.repo_name)
31 repo, dbrepo = scm_model.get(c.repo_name)
32 if cached_repo:
32 if repo:
33 c.repository_tags = cached_repo.tags
33 c.repository_tags = repo.tags
34 c.repository_branches = cached_repo.branches
34 c.repository_branches = repo.branches
35 c.repository_followers = scm_model.get_followers(cached_repo.dbrepo.repo_id)
35 c.repository_followers = scm_model.get_followers(dbrepo.repo_id)
36 c.repository_forks = scm_model.get_forks(cached_repo.dbrepo.repo_id)
36 c.repository_forks = scm_model.get_forks(dbrepo.repo_id)
37 else:
37 else:
38 c.repository_tags = {}
38 c.repository_tags = {}
39 c.repository_branches = {}
39 c.repository_branches = {}
40 c.repository_followers = 0
40 c.repository_followers = 0
41 c.repository_forks = 0
41 c.repository_forks = 0
42
42
43
43
44 def __call__(self, environ, start_response):
44 def __call__(self, environ, start_response):
45 """Invoke the Controller"""
45 """Invoke the Controller"""
46 # WSGIController.__call__ dispatches to the Controller method
46 # WSGIController.__call__ dispatches to the Controller method
47 # the request is routed to. This routing information is
47 # the request is routed to. This routing information is
48 # available in environ['pylons.routes_dict']
48 # available in environ['pylons.routes_dict']
49 try:
49 try:
50 #putting this here makes sure that we update permissions every time
50 #putting this here makes sure that we update permissions every time
51 self.rhodecode_user = c.rhodecode_user = auth.get_user(session)
51 self.rhodecode_user = c.rhodecode_user = auth.get_user(session)
52 return WSGIController.__call__(self, environ, start_response)
52 return WSGIController.__call__(self, environ, start_response)
53 finally:
53 finally:
54 meta.Session.remove()
54 meta.Session.remove()
@@ -1,591 +1,591 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 from pygments.formatters import HtmlFormatter
9 from pygments.formatters import HtmlFormatter
10 from pygments import highlight as code_highlight
10 from pygments import highlight as code_highlight
11 from pylons import url
11 from pylons import url
12 from pylons.i18n.translation import _, ungettext
12 from pylons.i18n.translation import _, ungettext
13 from vcs.utils.annotate import annotate_highlight
13 from vcs.utils.annotate import annotate_highlight
14 from rhodecode.lib.utils import repo_name_slug
14 from rhodecode.lib.utils import repo_name_slug
15
15
16 from webhelpers.html import literal, HTML, escape
16 from webhelpers.html import literal, HTML, escape
17 from webhelpers.html.tools import *
17 from webhelpers.html.tools import *
18 from webhelpers.html.builder import make_tag
18 from webhelpers.html.builder import make_tag
19 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
19 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
20 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
20 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
21 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
21 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
22 password, textarea, title, ul, xml_declaration, radio
22 password, textarea, title, ul, xml_declaration, radio
23 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
23 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
24 mail_to, strip_links, strip_tags, tag_re
24 mail_to, strip_links, strip_tags, tag_re
25 from webhelpers.number import format_byte_size, format_bit_size
25 from webhelpers.number import format_byte_size, format_bit_size
26 from webhelpers.pylonslib import Flash as _Flash
26 from webhelpers.pylonslib import Flash as _Flash
27 from webhelpers.pylonslib.secure_form import secure_form
27 from webhelpers.pylonslib.secure_form import secure_form
28 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
28 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
29 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
29 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
30 replace_whitespace, urlify, truncate, wrap_paragraphs
30 replace_whitespace, urlify, truncate, wrap_paragraphs
31 from webhelpers.date import time_ago_in_words
31 from webhelpers.date import time_ago_in_words
32
32
33 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
33 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
34 convert_boolean_attrs, NotGiven
34 convert_boolean_attrs, NotGiven
35
35
36 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
36 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
37 """Reset button
37 """Reset button
38 """
38 """
39 _set_input_attrs(attrs, type, name, value)
39 _set_input_attrs(attrs, type, name, value)
40 _set_id_attr(attrs, id, name)
40 _set_id_attr(attrs, id, name)
41 convert_boolean_attrs(attrs, ["disabled"])
41 convert_boolean_attrs(attrs, ["disabled"])
42 return HTML.input(**attrs)
42 return HTML.input(**attrs)
43
43
44 reset = _reset
44 reset = _reset
45
45
46
46
47 def get_token():
47 def get_token():
48 """Return the current authentication token, creating one if one doesn't
48 """Return the current authentication token, creating one if one doesn't
49 already exist.
49 already exist.
50 """
50 """
51 token_key = "_authentication_token"
51 token_key = "_authentication_token"
52 from pylons import session
52 from pylons import session
53 if not token_key in session:
53 if not token_key in session:
54 try:
54 try:
55 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
55 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
56 except AttributeError: # Python < 2.4
56 except AttributeError: # Python < 2.4
57 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
57 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
58 session[token_key] = token
58 session[token_key] = token
59 if hasattr(session, 'save'):
59 if hasattr(session, 'save'):
60 session.save()
60 session.save()
61 return session[token_key]
61 return session[token_key]
62
62
63 class _GetError(object):
63 class _GetError(object):
64 """Get error from form_errors, and represent it as span wrapped error
64 """Get error from form_errors, and represent it as span wrapped error
65 message
65 message
66
66
67 :param field_name: field to fetch errors for
67 :param field_name: field to fetch errors for
68 :param form_errors: form errors dict
68 :param form_errors: form errors dict
69 """
69 """
70
70
71 def __call__(self, field_name, form_errors):
71 def __call__(self, field_name, form_errors):
72 tmpl = """<span class="error_msg">%s</span>"""
72 tmpl = """<span class="error_msg">%s</span>"""
73 if form_errors and form_errors.has_key(field_name):
73 if form_errors and form_errors.has_key(field_name):
74 return literal(tmpl % form_errors.get(field_name))
74 return literal(tmpl % form_errors.get(field_name))
75
75
76 get_error = _GetError()
76 get_error = _GetError()
77
77
78 class _ToolTip(object):
78 class _ToolTip(object):
79
79
80 def __call__(self, tooltip_title, trim_at=50):
80 def __call__(self, tooltip_title, trim_at=50):
81 """Special function just to wrap our text into nice formatted
81 """Special function just to wrap our text into nice formatted
82 autowrapped text
82 autowrapped text
83
83
84 :param tooltip_title:
84 :param tooltip_title:
85 """
85 """
86
86
87 return wrap_paragraphs(escape(tooltip_title), trim_at)\
87 return wrap_paragraphs(escape(tooltip_title), trim_at)\
88 .replace('\n', '<br/>')
88 .replace('\n', '<br/>')
89
89
90 def activate(self):
90 def activate(self):
91 """Adds tooltip mechanism to the given Html all tooltips have to have
91 """Adds tooltip mechanism to the given Html all tooltips have to have
92 set class `tooltip` and set attribute `tooltip_title`.
92 set class `tooltip` and set attribute `tooltip_title`.
93 Then a tooltip will be generated based on that. All with yui js tooltip
93 Then a tooltip will be generated based on that. All with yui js tooltip
94 """
94 """
95
95
96 js = '''
96 js = '''
97 YAHOO.util.Event.onDOMReady(function(){
97 YAHOO.util.Event.onDOMReady(function(){
98 function toolTipsId(){
98 function toolTipsId(){
99 var ids = [];
99 var ids = [];
100 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
100 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
101
101
102 for (var i = 0; i < tts.length; i++) {
102 for (var i = 0; i < tts.length; i++) {
103 //if element doesn't not have and id autogenerate one for tooltip
103 //if element doesn't not have and id autogenerate one for tooltip
104
104
105 if (!tts[i].id){
105 if (!tts[i].id){
106 tts[i].id='tt'+i*100;
106 tts[i].id='tt'+i*100;
107 }
107 }
108 ids.push(tts[i].id);
108 ids.push(tts[i].id);
109 }
109 }
110 return ids
110 return ids
111 };
111 };
112 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
112 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
113 context: toolTipsId(),
113 context: toolTipsId(),
114 monitorresize:false,
114 monitorresize:false,
115 xyoffset :[0,0],
115 xyoffset :[0,0],
116 autodismissdelay:300000,
116 autodismissdelay:300000,
117 hidedelay:5,
117 hidedelay:5,
118 showdelay:20,
118 showdelay:20,
119 });
119 });
120
120
121 // Set the text for the tooltip just before we display it. Lazy method
121 // Set the text for the tooltip just before we display it. Lazy method
122 myToolTips.contextTriggerEvent.subscribe(
122 myToolTips.contextTriggerEvent.subscribe(
123 function(type, args) {
123 function(type, args) {
124
124
125 var context = args[0];
125 var context = args[0];
126
126
127 //positioning of tooltip
127 //positioning of tooltip
128 var tt_w = this.element.clientWidth;//tooltip width
128 var tt_w = this.element.clientWidth;//tooltip width
129 var tt_h = this.element.clientHeight;//tooltip height
129 var tt_h = this.element.clientHeight;//tooltip height
130
130
131 var context_w = context.offsetWidth;
131 var context_w = context.offsetWidth;
132 var context_h = context.offsetHeight;
132 var context_h = context.offsetHeight;
133
133
134 var pos_x = YAHOO.util.Dom.getX(context);
134 var pos_x = YAHOO.util.Dom.getX(context);
135 var pos_y = YAHOO.util.Dom.getY(context);
135 var pos_y = YAHOO.util.Dom.getY(context);
136
136
137 var display_strategy = 'right';
137 var display_strategy = 'right';
138 var xy_pos = [0,0];
138 var xy_pos = [0,0];
139 switch (display_strategy){
139 switch (display_strategy){
140
140
141 case 'top':
141 case 'top':
142 var cur_x = (pos_x+context_w/2)-(tt_w/2);
142 var cur_x = (pos_x+context_w/2)-(tt_w/2);
143 var cur_y = (pos_y-tt_h-4);
143 var cur_y = (pos_y-tt_h-4);
144 xy_pos = [cur_x,cur_y];
144 xy_pos = [cur_x,cur_y];
145 break;
145 break;
146 case 'bottom':
146 case 'bottom':
147 var cur_x = (pos_x+context_w/2)-(tt_w/2);
147 var cur_x = (pos_x+context_w/2)-(tt_w/2);
148 var cur_y = pos_y+context_h+4;
148 var cur_y = pos_y+context_h+4;
149 xy_pos = [cur_x,cur_y];
149 xy_pos = [cur_x,cur_y];
150 break;
150 break;
151 case 'left':
151 case 'left':
152 var cur_x = (pos_x-tt_w-4);
152 var cur_x = (pos_x-tt_w-4);
153 var cur_y = pos_y-((tt_h/2)-context_h/2);
153 var cur_y = pos_y-((tt_h/2)-context_h/2);
154 xy_pos = [cur_x,cur_y];
154 xy_pos = [cur_x,cur_y];
155 break;
155 break;
156 case 'right':
156 case 'right':
157 var cur_x = (pos_x+context_w+4);
157 var cur_x = (pos_x+context_w+4);
158 var cur_y = pos_y-((tt_h/2)-context_h/2);
158 var cur_y = pos_y-((tt_h/2)-context_h/2);
159 xy_pos = [cur_x,cur_y];
159 xy_pos = [cur_x,cur_y];
160 break;
160 break;
161 default:
161 default:
162 var cur_x = (pos_x+context_w/2)-(tt_w/2);
162 var cur_x = (pos_x+context_w/2)-(tt_w/2);
163 var cur_y = pos_y-tt_h-4;
163 var cur_y = pos_y-tt_h-4;
164 xy_pos = [cur_x,cur_y];
164 xy_pos = [cur_x,cur_y];
165 break;
165 break;
166
166
167 }
167 }
168
168
169 this.cfg.setProperty("xy",xy_pos);
169 this.cfg.setProperty("xy",xy_pos);
170
170
171 });
171 });
172
172
173 //Mouse out
173 //Mouse out
174 myToolTips.contextMouseOutEvent.subscribe(
174 myToolTips.contextMouseOutEvent.subscribe(
175 function(type, args) {
175 function(type, args) {
176 var context = args[0];
176 var context = args[0];
177
177
178 });
178 });
179 });
179 });
180 '''
180 '''
181 return literal(js)
181 return literal(js)
182
182
183 tooltip = _ToolTip()
183 tooltip = _ToolTip()
184
184
185 class _FilesBreadCrumbs(object):
185 class _FilesBreadCrumbs(object):
186
186
187 def __call__(self, repo_name, rev, paths):
187 def __call__(self, repo_name, rev, paths):
188 if isinstance(paths, str):
188 if isinstance(paths, str):
189 paths = paths.decode('utf-8')
189 paths = paths.decode('utf-8')
190 url_l = [link_to(repo_name, url('files_home',
190 url_l = [link_to(repo_name, url('files_home',
191 repo_name=repo_name,
191 repo_name=repo_name,
192 revision=rev, f_path=''))]
192 revision=rev, f_path=''))]
193 paths_l = paths.split('/')
193 paths_l = paths.split('/')
194 for cnt, p in enumerate(paths_l):
194 for cnt, p in enumerate(paths_l):
195 if p != '':
195 if p != '':
196 url_l.append(link_to(p, url('files_home',
196 url_l.append(link_to(p, url('files_home',
197 repo_name=repo_name,
197 repo_name=repo_name,
198 revision=rev,
198 revision=rev,
199 f_path='/'.join(paths_l[:cnt + 1]))))
199 f_path='/'.join(paths_l[:cnt + 1]))))
200
200
201 return literal('/'.join(url_l))
201 return literal('/'.join(url_l))
202
202
203 files_breadcrumbs = _FilesBreadCrumbs()
203 files_breadcrumbs = _FilesBreadCrumbs()
204
204
205 class CodeHtmlFormatter(HtmlFormatter):
205 class CodeHtmlFormatter(HtmlFormatter):
206 """My code Html Formatter for source codes
206 """My code Html Formatter for source codes
207 """
207 """
208
208
209 def wrap(self, source, outfile):
209 def wrap(self, source, outfile):
210 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
210 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
211
211
212 def _wrap_code(self, source):
212 def _wrap_code(self, source):
213 for cnt, it in enumerate(source):
213 for cnt, it in enumerate(source):
214 i, t = it
214 i, t = it
215 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
215 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
216 yield i, t
216 yield i, t
217
217
218 def _wrap_tablelinenos(self, inner):
218 def _wrap_tablelinenos(self, inner):
219 dummyoutfile = StringIO.StringIO()
219 dummyoutfile = StringIO.StringIO()
220 lncount = 0
220 lncount = 0
221 for t, line in inner:
221 for t, line in inner:
222 if t:
222 if t:
223 lncount += 1
223 lncount += 1
224 dummyoutfile.write(line)
224 dummyoutfile.write(line)
225
225
226 fl = self.linenostart
226 fl = self.linenostart
227 mw = len(str(lncount + fl - 1))
227 mw = len(str(lncount + fl - 1))
228 sp = self.linenospecial
228 sp = self.linenospecial
229 st = self.linenostep
229 st = self.linenostep
230 la = self.lineanchors
230 la = self.lineanchors
231 aln = self.anchorlinenos
231 aln = self.anchorlinenos
232 nocls = self.noclasses
232 nocls = self.noclasses
233 if sp:
233 if sp:
234 lines = []
234 lines = []
235
235
236 for i in range(fl, fl + lncount):
236 for i in range(fl, fl + lncount):
237 if i % st == 0:
237 if i % st == 0:
238 if i % sp == 0:
238 if i % sp == 0:
239 if aln:
239 if aln:
240 lines.append('<a href="#%s%d" class="special">%*d</a>' %
240 lines.append('<a href="#%s%d" class="special">%*d</a>' %
241 (la, i, mw, i))
241 (la, i, mw, i))
242 else:
242 else:
243 lines.append('<span class="special">%*d</span>' % (mw, i))
243 lines.append('<span class="special">%*d</span>' % (mw, i))
244 else:
244 else:
245 if aln:
245 if aln:
246 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
246 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
247 else:
247 else:
248 lines.append('%*d' % (mw, i))
248 lines.append('%*d' % (mw, i))
249 else:
249 else:
250 lines.append('')
250 lines.append('')
251 ls = '\n'.join(lines)
251 ls = '\n'.join(lines)
252 else:
252 else:
253 lines = []
253 lines = []
254 for i in range(fl, fl + lncount):
254 for i in range(fl, fl + lncount):
255 if i % st == 0:
255 if i % st == 0:
256 if aln:
256 if aln:
257 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
257 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
258 else:
258 else:
259 lines.append('%*d' % (mw, i))
259 lines.append('%*d' % (mw, i))
260 else:
260 else:
261 lines.append('')
261 lines.append('')
262 ls = '\n'.join(lines)
262 ls = '\n'.join(lines)
263
263
264 # in case you wonder about the seemingly redundant <div> here: since the
264 # in case you wonder about the seemingly redundant <div> here: since the
265 # content in the other cell also is wrapped in a div, some browsers in
265 # content in the other cell also is wrapped in a div, some browsers in
266 # some configurations seem to mess up the formatting...
266 # some configurations seem to mess up the formatting...
267 if nocls:
267 if nocls:
268 yield 0, ('<table class="%stable">' % self.cssclass +
268 yield 0, ('<table class="%stable">' % self.cssclass +
269 '<tr><td><div class="linenodiv" '
269 '<tr><td><div class="linenodiv" '
270 'style="background-color: #f0f0f0; padding-right: 10px">'
270 'style="background-color: #f0f0f0; padding-right: 10px">'
271 '<pre style="line-height: 125%">' +
271 '<pre style="line-height: 125%">' +
272 ls + '</pre></div></td><td class="code">')
272 ls + '</pre></div></td><td class="code">')
273 else:
273 else:
274 yield 0, ('<table class="%stable">' % self.cssclass +
274 yield 0, ('<table class="%stable">' % self.cssclass +
275 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
275 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
276 ls + '</pre></div></td><td class="code">')
276 ls + '</pre></div></td><td class="code">')
277 yield 0, dummyoutfile.getvalue()
277 yield 0, dummyoutfile.getvalue()
278 yield 0, '</td></tr></table>'
278 yield 0, '</td></tr></table>'
279
279
280
280
281 def pygmentize(filenode, **kwargs):
281 def pygmentize(filenode, **kwargs):
282 """pygmentize function using pygments
282 """pygmentize function using pygments
283
283
284 :param filenode:
284 :param filenode:
285 """
285 """
286
286
287 return literal(code_highlight(filenode.content,
287 return literal(code_highlight(filenode.content,
288 filenode.lexer, CodeHtmlFormatter(**kwargs)))
288 filenode.lexer, CodeHtmlFormatter(**kwargs)))
289
289
290 def pygmentize_annotation(filenode, **kwargs):
290 def pygmentize_annotation(filenode, **kwargs):
291 """pygmentize function for annotation
291 """pygmentize function for annotation
292
292
293 :param filenode:
293 :param filenode:
294 """
294 """
295
295
296 color_dict = {}
296 color_dict = {}
297 def gen_color(n=10000):
297 def gen_color(n=10000):
298 """generator for getting n of evenly distributed colors using
298 """generator for getting n of evenly distributed colors using
299 hsv color and golden ratio. It always return same order of colors
299 hsv color and golden ratio. It always return same order of colors
300
300
301 :returns: RGB tuple
301 :returns: RGB tuple
302 """
302 """
303 import colorsys
303 import colorsys
304 golden_ratio = 0.618033988749895
304 golden_ratio = 0.618033988749895
305 h = 0.22717784590367374
305 h = 0.22717784590367374
306
306
307 for c in xrange(n):
307 for c in xrange(n):
308 h += golden_ratio
308 h += golden_ratio
309 h %= 1
309 h %= 1
310 HSV_tuple = [h, 0.95, 0.95]
310 HSV_tuple = [h, 0.95, 0.95]
311 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
311 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
312 yield map(lambda x:str(int(x * 256)), RGB_tuple)
312 yield map(lambda x:str(int(x * 256)), RGB_tuple)
313
313
314 cgenerator = gen_color()
314 cgenerator = gen_color()
315
315
316 def get_color_string(cs):
316 def get_color_string(cs):
317 if color_dict.has_key(cs):
317 if color_dict.has_key(cs):
318 col = color_dict[cs]
318 col = color_dict[cs]
319 else:
319 else:
320 col = color_dict[cs] = cgenerator.next()
320 col = color_dict[cs] = cgenerator.next()
321 return "color: rgb(%s)! important;" % (', '.join(col))
321 return "color: rgb(%s)! important;" % (', '.join(col))
322
322
323 def url_func(changeset):
323 def url_func(changeset):
324 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
324 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
325 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
325 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
326
326
327 tooltip_html = tooltip_html % (changeset.author,
327 tooltip_html = tooltip_html % (changeset.author,
328 changeset.date,
328 changeset.date,
329 tooltip(changeset.message))
329 tooltip(changeset.message))
330 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
330 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
331 short_id(changeset.raw_id))
331 short_id(changeset.raw_id))
332 uri = link_to(
332 uri = link_to(
333 lnk_format,
333 lnk_format,
334 url('changeset_home', repo_name=changeset.repository.name,
334 url('changeset_home', repo_name=changeset.repository.name,
335 revision=changeset.raw_id),
335 revision=changeset.raw_id),
336 style=get_color_string(changeset.raw_id),
336 style=get_color_string(changeset.raw_id),
337 class_='tooltip',
337 class_='tooltip',
338 title=tooltip_html
338 title=tooltip_html
339 )
339 )
340
340
341 uri += '\n'
341 uri += '\n'
342 return uri
342 return uri
343 return literal(annotate_highlight(filenode, url_func, **kwargs))
343 return literal(annotate_highlight(filenode, url_func, **kwargs))
344
344
345 def get_changeset_safe(repo, rev):
345 def get_changeset_safe(repo, rev):
346 from vcs.backends.base import BaseRepository
346 from vcs.backends.base import BaseRepository
347 from vcs.exceptions import RepositoryError
347 from vcs.exceptions import RepositoryError
348 if not isinstance(repo, BaseRepository):
348 if not isinstance(repo, BaseRepository):
349 raise Exception('You must pass an Repository '
349 raise Exception('You must pass an Repository '
350 'object as first argument got %s', type(repo))
350 'object as first argument got %s', type(repo))
351
351
352 try:
352 try:
353 cs = repo.get_changeset(rev)
353 cs = repo.get_changeset(rev)
354 except RepositoryError:
354 except RepositoryError:
355 from rhodecode.lib.utils import EmptyChangeset
355 from rhodecode.lib.utils import EmptyChangeset
356 cs = EmptyChangeset()
356 cs = EmptyChangeset()
357 return cs
357 return cs
358
358
359
359
360 def is_following_repo(repo_name, user_id):
360 def is_following_repo(repo_name, user_id):
361 from rhodecode.model.scm import ScmModel
361 from rhodecode.model.scm import ScmModel
362 return ScmModel().is_following_repo(repo_name, user_id)
362 return ScmModel().is_following_repo(repo_name, user_id)
363
363
364 flash = _Flash()
364 flash = _Flash()
365
365
366
366
367 #==============================================================================
367 #==============================================================================
368 # MERCURIAL FILTERS available via h.
368 # MERCURIAL FILTERS available via h.
369 #==============================================================================
369 #==============================================================================
370 from mercurial import util
370 from mercurial import util
371 from mercurial.templatefilters import person as _person
371 from mercurial.templatefilters import person as _person
372
372
373 def _age(curdate):
373 def _age(curdate):
374 """turns a datetime into an age string."""
374 """turns a datetime into an age string."""
375
375
376 if not curdate:
376 if not curdate:
377 return ''
377 return ''
378
378
379 from datetime import timedelta, datetime
379 from datetime import timedelta, datetime
380
380
381 agescales = [("year", 3600 * 24 * 365),
381 agescales = [("year", 3600 * 24 * 365),
382 ("month", 3600 * 24 * 30),
382 ("month", 3600 * 24 * 30),
383 ("day", 3600 * 24),
383 ("day", 3600 * 24),
384 ("hour", 3600),
384 ("hour", 3600),
385 ("minute", 60),
385 ("minute", 60),
386 ("second", 1), ]
386 ("second", 1), ]
387
387
388 age = datetime.now() - curdate
388 age = datetime.now() - curdate
389 age_seconds = (age.days * agescales[2][1]) + age.seconds
389 age_seconds = (age.days * agescales[2][1]) + age.seconds
390 pos = 1
390 pos = 1
391 for scale in agescales:
391 for scale in agescales:
392 if scale[1] <= age_seconds:
392 if scale[1] <= age_seconds:
393 if pos == 6:pos = 5
393 if pos == 6:pos = 5
394 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
394 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
395 pos += 1
395 pos += 1
396
396
397 return _('just now')
397 return _('just now')
398
398
399 age = lambda x:_age(x)
399 age = lambda x:_age(x)
400 capitalize = lambda x: x.capitalize()
400 capitalize = lambda x: x.capitalize()
401 email = util.email
401 email = util.email
402 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
402 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
403 person = lambda x: _person(x)
403 person = lambda x: _person(x)
404 short_id = lambda x: x[:12]
404 short_id = lambda x: x[:12]
405
405
406
406
407 def bool2icon(value):
407 def bool2icon(value):
408 """Returns True/False values represented as small html image of true/false
408 """Returns True/False values represented as small html image of true/false
409 icons
409 icons
410
410
411 :param value: bool value
411 :param value: bool value
412 """
412 """
413
413
414 if value is True:
414 if value is True:
415 return HTML.tag('img', src="/images/icons/accept.png", alt=_('True'))
415 return HTML.tag('img', src="/images/icons/accept.png", alt=_('True'))
416
416
417 if value is False:
417 if value is False:
418 return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False'))
418 return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False'))
419
419
420 return value
420 return value
421
421
422
422
423 def action_parser(user_log):
423 def action_parser(user_log):
424 """This helper will map the specified string action into translated
424 """This helper will map the specified string action into translated
425 fancy names with icons and links
425 fancy names with icons and links
426
426
427 :param user_log: user log instance
427 :param user_log: user log instance
428 """
428 """
429
429
430 action = user_log.action
430 action = user_log.action
431 action_params = ' '
431 action_params = ' '
432
432
433 x = action.split(':')
433 x = action.split(':')
434
434
435 if len(x) > 1:
435 if len(x) > 1:
436 action, action_params = x
436 action, action_params = x
437
437
438 def get_cs_links():
438 def get_cs_links():
439 revs_limit = 5 #display this amount always
439 revs_limit = 5 #display this amount always
440 revs_top_limit = 50 #show upto this amount of changesets hidden
440 revs_top_limit = 50 #show upto this amount of changesets hidden
441 revs = action_params.split(',')
441 revs = action_params.split(',')
442 repo_name = user_log.repository.repo_name
442 repo_name = user_log.repository.repo_name
443 from rhodecode.model.scm import ScmModel
443 from rhodecode.model.scm import ScmModel
444
444
445 message = lambda rev: get_changeset_safe(ScmModel().get(repo_name),
445 message = lambda rev: get_changeset_safe(ScmModel().get(repo_name),
446 rev).message
446 rev).message
447
447
448 cs_links = " " + ', '.join ([link_to(rev,
448 cs_links = " " + ', '.join ([link_to(rev,
449 url('changeset_home',
449 url('changeset_home',
450 repo_name=repo_name,
450 repo_name=repo_name,
451 revision=rev), title=tooltip(message(rev)),
451 revision=rev), title=tooltip(message(rev)),
452 class_='tooltip') for rev in revs[:revs_limit] ])
452 class_='tooltip') for rev in revs[:revs_limit] ])
453
453
454 compare_view = (' <div class="compare_view tooltip" title="%s">'
454 compare_view = (' <div class="compare_view tooltip" title="%s">'
455 '<a href="%s">%s</a> '
455 '<a href="%s">%s</a> '
456 '</div>' % (_('Show all combined changesets %s->%s' \
456 '</div>' % (_('Show all combined changesets %s->%s' \
457 % (revs[0], revs[-1])),
457 % (revs[0], revs[-1])),
458 url('changeset_home', repo_name=repo_name,
458 url('changeset_home', repo_name=repo_name,
459 revision='%s...%s' % (revs[0], revs[-1])
459 revision='%s...%s' % (revs[0], revs[-1])
460 ),
460 ),
461 _('compare view'))
461 _('compare view'))
462 )
462 )
463
463
464 if len(revs) > revs_limit:
464 if len(revs) > revs_limit:
465 uniq_id = revs[0]
465 uniq_id = revs[0]
466 html_tmpl = ('<span> %s '
466 html_tmpl = ('<span> %s '
467 '<a class="show_more" id="_%s" href="#more">%s</a> '
467 '<a class="show_more" id="_%s" href="#more">%s</a> '
468 '%s</span>')
468 '%s</span>')
469 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
469 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
470 % (len(revs) - revs_limit),
470 % (len(revs) - revs_limit),
471 _('revisions'))
471 _('revisions'))
472
472
473 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
473 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
474 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
474 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
475 url('changeset_home',
475 url('changeset_home',
476 repo_name=repo_name, revision=rev),
476 repo_name=repo_name, revision=rev),
477 title=message(rev), class_='tooltip')
477 title=message(rev), class_='tooltip')
478 for rev in revs[revs_limit:revs_top_limit]]))
478 for rev in revs[revs_limit:revs_top_limit]]))
479 if len(revs) > 1:
479 if len(revs) > 1:
480 cs_links += compare_view
480 cs_links += compare_view
481 return cs_links
481 return cs_links
482
482
483 def get_fork_name():
483 def get_fork_name():
484 from rhodecode.model.scm import ScmModel
484 from rhodecode.model.scm import ScmModel
485 repo_name = action_params
485 repo_name = action_params
486 repo = ScmModel().get(repo_name)
486 repo, dbrepo = ScmModel().get(repo_name)
487 if repo is None:
487 if repo is None:
488 return repo_name
488 return repo_name
489 return link_to(action_params, url('summary_home',
489 return link_to(action_params, url('summary_home',
490 repo_name=repo.name,),
490 repo_name=repo.name,),
491 title=repo.dbrepo.description)
491 title=dbrepo.description)
492
492
493 map = {'user_deleted_repo':(_('User [deleted] repository'), None),
493 map = {'user_deleted_repo':(_('User [deleted] repository'), None),
494 'user_created_repo':(_('User [created] repository'), None),
494 'user_created_repo':(_('User [created] repository'), None),
495 'user_forked_repo':(_('User [forked] repository as:'), get_fork_name),
495 'user_forked_repo':(_('User [forked] repository as:'), get_fork_name),
496 'user_updated_repo':(_('User [updated] repository'), None),
496 'user_updated_repo':(_('User [updated] repository'), None),
497 'admin_deleted_repo':(_('Admin [delete] repository'), None),
497 'admin_deleted_repo':(_('Admin [delete] repository'), None),
498 'admin_created_repo':(_('Admin [created] repository'), None),
498 'admin_created_repo':(_('Admin [created] repository'), None),
499 'admin_forked_repo':(_('Admin [forked] repository'), None),
499 'admin_forked_repo':(_('Admin [forked] repository'), None),
500 'admin_updated_repo':(_('Admin [updated] repository'), None),
500 'admin_updated_repo':(_('Admin [updated] repository'), None),
501 'push':(_('[Pushed]'), get_cs_links),
501 'push':(_('[Pushed]'), get_cs_links),
502 'pull':(_('[Pulled]'), None),
502 'pull':(_('[Pulled]'), None),
503 'started_following_repo':(_('User [started following] repository'), None),
503 'started_following_repo':(_('User [started following] repository'), None),
504 'stopped_following_repo':(_('User [stopped following] repository'), None),
504 'stopped_following_repo':(_('User [stopped following] repository'), None),
505 }
505 }
506
506
507 action_str = map.get(action, action)
507 action_str = map.get(action, action)
508 action = action_str[0].replace('[', '<span class="journal_highlight">')\
508 action = action_str[0].replace('[', '<span class="journal_highlight">')\
509 .replace(']', '</span>')
509 .replace(']', '</span>')
510 if action_str[1] is not None:
510 if action_str[1] is not None:
511 action = action + " " + action_str[1]()
511 action = action + " " + action_str[1]()
512
512
513 return literal(action)
513 return literal(action)
514
514
515 def action_parser_icon(user_log):
515 def action_parser_icon(user_log):
516 action = user_log.action
516 action = user_log.action
517 action_params = None
517 action_params = None
518 x = action.split(':')
518 x = action.split(':')
519
519
520 if len(x) > 1:
520 if len(x) > 1:
521 action, action_params = x
521 action, action_params = x
522
522
523 tmpl = """<img src="/images/icons/%s" alt="%s"/>"""
523 tmpl = """<img src="/images/icons/%s" alt="%s"/>"""
524 map = {'user_deleted_repo':'database_delete.png',
524 map = {'user_deleted_repo':'database_delete.png',
525 'user_created_repo':'database_add.png',
525 'user_created_repo':'database_add.png',
526 'user_forked_repo':'arrow_divide.png',
526 'user_forked_repo':'arrow_divide.png',
527 'user_updated_repo':'database_edit.png',
527 'user_updated_repo':'database_edit.png',
528 'admin_deleted_repo':'database_delete.png',
528 'admin_deleted_repo':'database_delete.png',
529 'admin_created_repo':'database_add.png',
529 'admin_created_repo':'database_add.png',
530 'admin_forked_repo':'arrow_divide.png',
530 'admin_forked_repo':'arrow_divide.png',
531 'admin_updated_repo':'database_edit.png',
531 'admin_updated_repo':'database_edit.png',
532 'push':'script_add.png',
532 'push':'script_add.png',
533 'pull':'down_16.png',
533 'pull':'down_16.png',
534 'started_following_repo':'heart_add.png',
534 'started_following_repo':'heart_add.png',
535 'stopped_following_repo':'heart_delete.png',
535 'stopped_following_repo':'heart_delete.png',
536 }
536 }
537 return literal(tmpl % (map.get(action, action), action))
537 return literal(tmpl % (map.get(action, action), action))
538
538
539
539
540 #==============================================================================
540 #==============================================================================
541 # PERMS
541 # PERMS
542 #==============================================================================
542 #==============================================================================
543 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
543 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
544 HasRepoPermissionAny, HasRepoPermissionAll
544 HasRepoPermissionAny, HasRepoPermissionAll
545
545
546 #==============================================================================
546 #==============================================================================
547 # GRAVATAR URL
547 # GRAVATAR URL
548 #==============================================================================
548 #==============================================================================
549 import hashlib
549 import hashlib
550 import urllib
550 import urllib
551 from pylons import request
551 from pylons import request
552
552
553 def gravatar_url(email_address, size=30):
553 def gravatar_url(email_address, size=30):
554 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
554 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
555 default = 'identicon'
555 default = 'identicon'
556 baseurl_nossl = "http://www.gravatar.com/avatar/"
556 baseurl_nossl = "http://www.gravatar.com/avatar/"
557 baseurl_ssl = "https://secure.gravatar.com/avatar/"
557 baseurl_ssl = "https://secure.gravatar.com/avatar/"
558 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
558 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
559
559
560
560
561 # construct the url
561 # construct the url
562 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
562 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
563 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
563 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
564
564
565 return gravatar_url
565 return gravatar_url
566
566
567 def safe_unicode(str):
567 def safe_unicode(str):
568 """safe unicode function. In case of UnicodeDecode error we try to return
568 """safe unicode function. In case of UnicodeDecode error we try to return
569 unicode with errors replace, if this failes we return unicode with
569 unicode with errors replace, if this failes we return unicode with
570 string_escape decoding """
570 string_escape decoding """
571
571
572 try:
572 try:
573 u_str = unicode(str)
573 u_str = unicode(str)
574 except UnicodeDecodeError:
574 except UnicodeDecodeError:
575 try:
575 try:
576 u_str = unicode(str, 'utf-8', 'replace')
576 u_str = unicode(str, 'utf-8', 'replace')
577 except UnicodeDecodeError:
577 except UnicodeDecodeError:
578 #incase we have a decode error just represent as byte string
578 #incase we have a decode error just represent as byte string
579 u_str = unicode(str(str).encode('string_escape'))
579 u_str = unicode(str(str).encode('string_escape'))
580
580
581 return u_str
581 return u_str
582
582
583 def changed_tooltip(nodes):
583 def changed_tooltip(nodes):
584 if nodes:
584 if nodes:
585 pref = ': <br/> '
585 pref = ': <br/> '
586 suf = ''
586 suf = ''
587 if len(nodes) > 30:
587 if len(nodes) > 30:
588 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
588 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
589 return literal(pref + '<br/> '.join([x.path for x in nodes[:30]]) + suf)
589 return literal(pref + '<br/> '.join([x.path for x in nodes[:30]]) + suf)
590 else:
590 else:
591 return ': ' + _('No Files')
591 return ': ' + _('No Files')
@@ -1,342 +1,342 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.repo
3 rhodecode.model.repo
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Repository model for rhodecode
6 Repository model for rhodecode
7
7
8 :created_on: Jun 5, 2010
8 :created_on: Jun 5, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27 import os
27 import os
28 import shutil
28 import shutil
29 import logging
29 import logging
30 import traceback
30 import traceback
31 from datetime import datetime
31 from datetime import datetime
32
32
33 from sqlalchemy.orm import joinedload
33 from sqlalchemy.orm import joinedload
34
34
35 from vcs.utils.lazy import LazyProperty
35 from vcs.utils.lazy import LazyProperty
36 from vcs.backends import get_backend
36 from vcs.backends import get_backend
37
37
38 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
39 from rhodecode.model.caching_query import FromCache
39 from rhodecode.model.caching_query import FromCache
40 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
40 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
41 Statistics, UsersGroup, UsersGroupToPerm, RhodeCodeUi
41 Statistics, UsersGroup, UsersGroupToPerm, RhodeCodeUi
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43 from rhodecode.model.users_group import UsersGroupMember, UsersGroupModel
43 from rhodecode.model.users_group import UsersGroupMember, UsersGroupModel
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48 class RepoModel(BaseModel):
48 class RepoModel(BaseModel):
49
49
50 @LazyProperty
50 @LazyProperty
51 def repos_path(self):
51 def repos_path(self):
52 """Get's the repositories root path from database
52 """Get's the repositories root path from database
53 """
53 """
54
54
55 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
55 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
56 return q.ui_value
56 return q.ui_value
57
57
58 def get(self, repo_id, cache=False):
58 def get(self, repo_id, cache=False):
59 repo = self.sa.query(Repository)\
59 repo = self.sa.query(Repository)\
60 .filter(Repository.repo_id == repo_id)
60 .filter(Repository.repo_id == repo_id)
61
61
62 if cache:
62 if cache:
63 repo = repo.options(FromCache("sql_cache_short",
63 repo = repo.options(FromCache("sql_cache_short",
64 "get_repo_%s" % repo_id))
64 "get_repo_%s" % repo_id))
65 return repo.scalar()
65 return repo.scalar()
66
66
67
67
68 def get_by_repo_name(self, repo_name, cache=False):
68 def get_by_repo_name(self, repo_name, cache=False):
69 repo = self.sa.query(Repository)\
69 repo = self.sa.query(Repository)\
70 .filter(Repository.repo_name == repo_name)
70 .filter(Repository.repo_name == repo_name)
71
71
72 if cache:
72 if cache:
73 repo = repo.options(FromCache("sql_cache_short",
73 repo = repo.options(FromCache("sql_cache_short",
74 "get_repo_%s" % repo_name))
74 "get_repo_%s" % repo_name))
75 return repo.scalar()
75 return repo.scalar()
76
76
77
77
78 def get_full(self, repo_name, cache=False, invalidate=False):
78 def get_full(self, repo_name, cache=False, invalidate=False):
79 repo = self.sa.query(Repository)\
79 repo = self.sa.query(Repository)\
80 .options(joinedload(Repository.fork))\
80 .options(joinedload(Repository.fork))\
81 .options(joinedload(Repository.user))\
81 .options(joinedload(Repository.user))\
82 .options(joinedload(Repository.followers))\
82 .options(joinedload(Repository.followers))\
83 .options(joinedload(Repository.repo_to_perm))\
83 .options(joinedload(Repository.repo_to_perm))\
84 .options(joinedload(Repository.users_group_to_perm))\
84 .options(joinedload(Repository.users_group_to_perm))\
85 .filter(Repository.repo_name == repo_name)\
85 .filter(Repository.repo_name == repo_name)\
86
86
87 if cache:
87 if cache:
88 repo = repo.options(FromCache("sql_cache_long",
88 repo = repo.options(FromCache("sql_cache_long",
89 "get_repo_full_%s" % repo_name))
89 "get_repo_full_%s" % repo_name))
90 if invalidate:
90 if invalidate and cache:
91 repo.invalidate()
91 repo.invalidate()
92
92
93 return repo.scalar()
93 return repo.scalar()
94
94
95
95
96 def get_users_js(self):
96 def get_users_js(self):
97
97
98 users = self.sa.query(User).filter(User.active == True).all()
98 users = self.sa.query(User).filter(User.active == True).all()
99 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
99 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
100 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
100 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
101 u.lastname, u.username)
101 u.lastname, u.username)
102 for u in users])
102 for u in users])
103 return users_array
103 return users_array
104
104
105
105
106 def get_users_groups_js(self):
106 def get_users_groups_js(self):
107 users_groups = self.sa.query(UsersGroup)\
107 users_groups = self.sa.query(UsersGroup)\
108 .filter(UsersGroup.users_group_active == True).all()
108 .filter(UsersGroup.users_group_active == True).all()
109
109
110 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
110 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
111
111
112 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
112 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
113 (gr.users_group_id, gr.users_group_name,
113 (gr.users_group_id, gr.users_group_name,
114 len(gr.members))
114 len(gr.members))
115 for gr in users_groups])
115 for gr in users_groups])
116 return users_groups_array
116 return users_groups_array
117
117
118 def update(self, repo_name, form_data):
118 def update(self, repo_name, form_data):
119 try:
119 try:
120 cur_repo = self.get_by_repo_name(repo_name, cache=False)
120 cur_repo = self.get_by_repo_name(repo_name, cache=False)
121 user_model = UserModel(self.sa)
121 user_model = UserModel(self.sa)
122 users_group_model = UsersGroupModel(self.sa)
122 users_group_model = UsersGroupModel(self.sa)
123
123
124 #update permissions
124 #update permissions
125 for member, perm, member_type in form_data['perms_updates']:
125 for member, perm, member_type in form_data['perms_updates']:
126 if member_type == 'user':
126 if member_type == 'user':
127 r2p = self.sa.query(RepoToPerm)\
127 r2p = self.sa.query(RepoToPerm)\
128 .filter(RepoToPerm.user == user_model.get_by_username(member))\
128 .filter(RepoToPerm.user == user_model.get_by_username(member))\
129 .filter(RepoToPerm.repository == cur_repo)\
129 .filter(RepoToPerm.repository == cur_repo)\
130 .one()
130 .one()
131
131
132 r2p.permission = self.sa.query(Permission)\
132 r2p.permission = self.sa.query(Permission)\
133 .filter(Permission.permission_name == perm)\
133 .filter(Permission.permission_name == perm)\
134 .scalar()
134 .scalar()
135 self.sa.add(r2p)
135 self.sa.add(r2p)
136 else:
136 else:
137 g2p = self.sa.query(UsersGroupToPerm)\
137 g2p = self.sa.query(UsersGroupToPerm)\
138 .filter(UsersGroupToPerm.users_group == users_group_model.get_by_groupname(member))\
138 .filter(UsersGroupToPerm.users_group == users_group_model.get_by_groupname(member))\
139 .filter(UsersGroupToPerm.repository == cur_repo)\
139 .filter(UsersGroupToPerm.repository == cur_repo)\
140 .one()
140 .one()
141
141
142 g2p.permission = self.sa.query(Permission)\
142 g2p.permission = self.sa.query(Permission)\
143 .filter(Permission.permission_name == perm)\
143 .filter(Permission.permission_name == perm)\
144 .scalar()
144 .scalar()
145 self.sa.add(g2p)
145 self.sa.add(g2p)
146
146
147 #set new permissions
147 #set new permissions
148 for member, perm, member_type in form_data['perms_new']:
148 for member, perm, member_type in form_data['perms_new']:
149 if member_type == 'user':
149 if member_type == 'user':
150 r2p = RepoToPerm()
150 r2p = RepoToPerm()
151 r2p.repository = cur_repo
151 r2p.repository = cur_repo
152 r2p.user = user_model.get_by_username(member)
152 r2p.user = user_model.get_by_username(member)
153
153
154 r2p.permission = self.sa.query(Permission)\
154 r2p.permission = self.sa.query(Permission)\
155 .filter(Permission.permission_name == perm)\
155 .filter(Permission.permission_name == perm)\
156 .scalar()
156 .scalar()
157 self.sa.add(r2p)
157 self.sa.add(r2p)
158 else:
158 else:
159 g2p = UsersGroupToPerm()
159 g2p = UsersGroupToPerm()
160 g2p.repository = cur_repo
160 g2p.repository = cur_repo
161 g2p.users_group = users_group_model.get_by_groupname(member)
161 g2p.users_group = users_group_model.get_by_groupname(member)
162
162
163 g2p.permission = self.sa.query(Permission)\
163 g2p.permission = self.sa.query(Permission)\
164 .filter(Permission.permission_name == perm)\
164 .filter(Permission.permission_name == perm)\
165 .scalar()
165 .scalar()
166 self.sa.add(g2p)
166 self.sa.add(g2p)
167
167
168 #update current repo
168 #update current repo
169 for k, v in form_data.items():
169 for k, v in form_data.items():
170 if k == 'user':
170 if k == 'user':
171 cur_repo.user = user_model.get(v)
171 cur_repo.user = user_model.get(v)
172 else:
172 else:
173 setattr(cur_repo, k, v)
173 setattr(cur_repo, k, v)
174
174
175 self.sa.add(cur_repo)
175 self.sa.add(cur_repo)
176
176
177 if repo_name != form_data['repo_name']:
177 if repo_name != form_data['repo_name']:
178 #rename our data
178 #rename our data
179 self.__rename_repo(repo_name, form_data['repo_name'])
179 self.__rename_repo(repo_name, form_data['repo_name'])
180
180
181 self.sa.commit()
181 self.sa.commit()
182 except:
182 except:
183 log.error(traceback.format_exc())
183 log.error(traceback.format_exc())
184 self.sa.rollback()
184 self.sa.rollback()
185 raise
185 raise
186
186
187 def create(self, form_data, cur_user, just_db=False, fork=False):
187 def create(self, form_data, cur_user, just_db=False, fork=False):
188 try:
188 try:
189 if fork:
189 if fork:
190 #force str since hg doesn't go with unicode
190 #force str since hg doesn't go with unicode
191 repo_name = str(form_data['fork_name'])
191 repo_name = str(form_data['fork_name'])
192 org_name = str(form_data['repo_name'])
192 org_name = str(form_data['repo_name'])
193
193
194 else:
194 else:
195 org_name = repo_name = str(form_data['repo_name'])
195 org_name = repo_name = str(form_data['repo_name'])
196 new_repo = Repository()
196 new_repo = Repository()
197 new_repo.enable_statistics = True
197 new_repo.enable_statistics = True
198 for k, v in form_data.items():
198 for k, v in form_data.items():
199 if k == 'repo_name':
199 if k == 'repo_name':
200 v = repo_name
200 v = repo_name
201 setattr(new_repo, k, v)
201 setattr(new_repo, k, v)
202
202
203 if fork:
203 if fork:
204 parent_repo = self.sa.query(Repository)\
204 parent_repo = self.sa.query(Repository)\
205 .filter(Repository.repo_name == org_name).scalar()
205 .filter(Repository.repo_name == org_name).scalar()
206 new_repo.fork = parent_repo
206 new_repo.fork = parent_repo
207
207
208 new_repo.user_id = cur_user.user_id
208 new_repo.user_id = cur_user.user_id
209 self.sa.add(new_repo)
209 self.sa.add(new_repo)
210
210
211 #create default permission
211 #create default permission
212 repo_to_perm = RepoToPerm()
212 repo_to_perm = RepoToPerm()
213 default = 'repository.read'
213 default = 'repository.read'
214 for p in UserModel(self.sa).get_by_username('default', cache=False).user_perms:
214 for p in UserModel(self.sa).get_by_username('default', cache=False).user_perms:
215 if p.permission.permission_name.startswith('repository.'):
215 if p.permission.permission_name.startswith('repository.'):
216 default = p.permission.permission_name
216 default = p.permission.permission_name
217 break
217 break
218
218
219 default_perm = 'repository.none' if form_data['private'] else default
219 default_perm = 'repository.none' if form_data['private'] else default
220
220
221 repo_to_perm.permission_id = self.sa.query(Permission)\
221 repo_to_perm.permission_id = self.sa.query(Permission)\
222 .filter(Permission.permission_name == default_perm)\
222 .filter(Permission.permission_name == default_perm)\
223 .one().permission_id
223 .one().permission_id
224
224
225 repo_to_perm.repository = new_repo
225 repo_to_perm.repository = new_repo
226 repo_to_perm.user_id = UserModel(self.sa)\
226 repo_to_perm.user_id = UserModel(self.sa)\
227 .get_by_username('default', cache=False).user_id
227 .get_by_username('default', cache=False).user_id
228
228
229 self.sa.add(repo_to_perm)
229 self.sa.add(repo_to_perm)
230
230
231 if not just_db:
231 if not just_db:
232 self.__create_repo(repo_name, form_data['repo_type'])
232 self.__create_repo(repo_name, form_data['repo_type'])
233
233
234 self.sa.commit()
234 self.sa.commit()
235
235
236 #now automatically start following this repository as owner
236 #now automatically start following this repository as owner
237 from rhodecode.model.scm import ScmModel
237 from rhodecode.model.scm import ScmModel
238 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
238 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
239 cur_user.user_id)
239 cur_user.user_id)
240
240
241 except:
241 except:
242 log.error(traceback.format_exc())
242 log.error(traceback.format_exc())
243 self.sa.rollback()
243 self.sa.rollback()
244 raise
244 raise
245
245
246 def create_fork(self, form_data, cur_user):
246 def create_fork(self, form_data, cur_user):
247 from rhodecode.lib.celerylib import tasks, run_task
247 from rhodecode.lib.celerylib import tasks, run_task
248 run_task(tasks.create_repo_fork, form_data, cur_user)
248 run_task(tasks.create_repo_fork, form_data, cur_user)
249
249
250 def delete(self, repo):
250 def delete(self, repo):
251 try:
251 try:
252 self.sa.delete(repo)
252 self.sa.delete(repo)
253 self.__delete_repo(repo)
253 self.__delete_repo(repo)
254 self.sa.commit()
254 self.sa.commit()
255 except:
255 except:
256 log.error(traceback.format_exc())
256 log.error(traceback.format_exc())
257 self.sa.rollback()
257 self.sa.rollback()
258 raise
258 raise
259
259
260 def delete_perm_user(self, form_data, repo_name):
260 def delete_perm_user(self, form_data, repo_name):
261 try:
261 try:
262 self.sa.query(RepoToPerm)\
262 self.sa.query(RepoToPerm)\
263 .filter(RepoToPerm.repository \
263 .filter(RepoToPerm.repository \
264 == self.get_by_repo_name(repo_name))\
264 == self.get_by_repo_name(repo_name))\
265 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
265 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
266 self.sa.commit()
266 self.sa.commit()
267 except:
267 except:
268 log.error(traceback.format_exc())
268 log.error(traceback.format_exc())
269 self.sa.rollback()
269 self.sa.rollback()
270 raise
270 raise
271
271
272 def delete_perm_users_group(self, form_data, repo_name):
272 def delete_perm_users_group(self, form_data, repo_name):
273 try:
273 try:
274 self.sa.query(UsersGroupToPerm)\
274 self.sa.query(UsersGroupToPerm)\
275 .filter(UsersGroupToPerm.repository \
275 .filter(UsersGroupToPerm.repository \
276 == self.get_by_repo_name(repo_name))\
276 == self.get_by_repo_name(repo_name))\
277 .filter(UsersGroupToPerm.users_group_id \
277 .filter(UsersGroupToPerm.users_group_id \
278 == form_data['users_group_id']).delete()
278 == form_data['users_group_id']).delete()
279 self.sa.commit()
279 self.sa.commit()
280 except:
280 except:
281 log.error(traceback.format_exc())
281 log.error(traceback.format_exc())
282 self.sa.rollback()
282 self.sa.rollback()
283 raise
283 raise
284
284
285 def delete_stats(self, repo_name):
285 def delete_stats(self, repo_name):
286 try:
286 try:
287 self.sa.query(Statistics)\
287 self.sa.query(Statistics)\
288 .filter(Statistics.repository == \
288 .filter(Statistics.repository == \
289 self.get_by_repo_name(repo_name)).delete()
289 self.get_by_repo_name(repo_name)).delete()
290 self.sa.commit()
290 self.sa.commit()
291 except:
291 except:
292 log.error(traceback.format_exc())
292 log.error(traceback.format_exc())
293 self.sa.rollback()
293 self.sa.rollback()
294 raise
294 raise
295
295
296
296
297 def __create_repo(self, repo_name, alias):
297 def __create_repo(self, repo_name, alias):
298 """
298 """
299 makes repository on filesystem
299 makes repository on filesystem
300 :param repo_name:
300 :param repo_name:
301 :param alias:
301 :param alias:
302 """
302 """
303 from rhodecode.lib.utils import check_repo
303 from rhodecode.lib.utils import check_repo
304 repo_path = os.path.join(self.repos_path, repo_name)
304 repo_path = os.path.join(self.repos_path, repo_name)
305 if check_repo(repo_name, self.repos_path):
305 if check_repo(repo_name, self.repos_path):
306 log.info('creating repo %s in %s', repo_name, repo_path)
306 log.info('creating repo %s in %s', repo_name, repo_path)
307 backend = get_backend(alias)
307 backend = get_backend(alias)
308 backend(repo_path, create=True)
308 backend(repo_path, create=True)
309
309
310 def __rename_repo(self, old, new):
310 def __rename_repo(self, old, new):
311 """
311 """
312 renames repository on filesystem
312 renames repository on filesystem
313 :param old: old name
313 :param old: old name
314 :param new: new name
314 :param new: new name
315 """
315 """
316 log.info('renaming repo from %s to %s', old, new)
316 log.info('renaming repo from %s to %s', old, new)
317
317
318 old_path = os.path.join(self.repos_path, old)
318 old_path = os.path.join(self.repos_path, old)
319 new_path = os.path.join(self.repos_path, new)
319 new_path = os.path.join(self.repos_path, new)
320 if os.path.isdir(new_path):
320 if os.path.isdir(new_path):
321 raise Exception('Was trying to rename to already existing dir %s',
321 raise Exception('Was trying to rename to already existing dir %s',
322 new_path)
322 new_path)
323 shutil.move(old_path, new_path)
323 shutil.move(old_path, new_path)
324
324
325 def __delete_repo(self, repo):
325 def __delete_repo(self, repo):
326 """
326 """
327 removes repo from filesystem, the removal is acctually made by
327 removes repo from filesystem, the removal is acctually made by
328 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
328 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
329 repository is no longer valid for rhodecode, can be undeleted later on
329 repository is no longer valid for rhodecode, can be undeleted later on
330 by reverting the renames on this repository
330 by reverting the renames on this repository
331 :param repo: repo object
331 :param repo: repo object
332 """
332 """
333 rm_path = os.path.join(self.repos_path, repo.repo_name)
333 rm_path = os.path.join(self.repos_path, repo.repo_name)
334 log.info("Removing %s", rm_path)
334 log.info("Removing %s", rm_path)
335 #disable hg/git
335 #disable hg/git
336 alias = repo.repo_type
336 alias = repo.repo_type
337 shutil.move(os.path.join(rm_path, '.%s' % alias),
337 shutil.move(os.path.join(rm_path, '.%s' % alias),
338 os.path.join(rm_path, 'rm__.%s' % alias))
338 os.path.join(rm_path, 'rm__.%s' % alias))
339 #disable repo
339 #disable repo
340 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
340 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
341 % (datetime.today().isoformat(),
341 % (datetime.today().isoformat(),
342 repo.repo_name)))
342 repo.repo_name)))
@@ -1,396 +1,384 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27 import os
27 import os
28 import time
28 import time
29 import traceback
29 import traceback
30 import logging
30 import logging
31
31
32 from mercurial import ui
32 from mercurial import ui
33
33
34 from sqlalchemy.orm import joinedload
35 from sqlalchemy.orm.session import make_transient
36 from sqlalchemy.exc import DatabaseError
34 from sqlalchemy.exc import DatabaseError
37
35
38 from beaker.cache import cache_region, region_invalidate
36 from beaker.cache import cache_region, region_invalidate
39
37
40 from vcs import get_backend
38 from vcs import get_backend
41 from vcs.utils.helpers import get_scm
39 from vcs.utils.helpers import get_scm
42 from vcs.exceptions import RepositoryError, VCSError
40 from vcs.exceptions import RepositoryError, VCSError
43 from vcs.utils.lazy import LazyProperty
41 from vcs.utils.lazy import LazyProperty
44
42
45 from rhodecode import BACKENDS
43 from rhodecode import BACKENDS
46 from rhodecode.lib import helpers as h
44 from rhodecode.lib import helpers as h
47 from rhodecode.lib.auth import HasRepoPermissionAny
45 from rhodecode.lib.auth import HasRepoPermissionAny
48 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, action_logger
46 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
47 action_logger
49 from rhodecode.model import BaseModel
48 from rhodecode.model import BaseModel
50 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
51 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
52 UserFollowing, UserLog
52 UserFollowing, UserLog
53 from rhodecode.model.caching_query import FromCache
53 from rhodecode.model.caching_query import FromCache
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class UserTemp(object):
58 class UserTemp(object):
59 def __init__(self, user_id):
59 def __init__(self, user_id):
60 self.user_id = user_id
60 self.user_id = user_id
61
61
62 def __repr__(self):
62 def __repr__(self):
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64
64
65 class RepoTemp(object):
65 class RepoTemp(object):
66 def __init__(self, repo_id):
66 def __init__(self, repo_id):
67 self.repo_id = repo_id
67 self.repo_id = repo_id
68
68
69 def __repr__(self):
69 def __repr__(self):
70 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
70 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
71
71
72 class ScmModel(BaseModel):
72 class ScmModel(BaseModel):
73 """Generic Scm Model
73 """Generic Scm Model
74 """
74 """
75
75
76 @LazyProperty
76 @LazyProperty
77 def repos_path(self):
77 def repos_path(self):
78 """Get's the repositories root path from database
78 """Get's the repositories root path from database
79 """
79 """
80
80
81 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
81 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
82
82
83 return q.ui_value
83 return q.ui_value
84
84
85 def repo_scan(self, repos_path, baseui):
85 def repo_scan(self, repos_path=None):
86 """Listing of repositories in given path. This path should not be a
86 """Listing of repositories in given path. This path should not be a
87 repository itself. Return a dictionary of repository objects
87 repository itself. Return a dictionary of repository objects
88
88
89 :param repos_path: path to directory containing repositories
89 :param repos_path: path to directory containing repositories
90 :param baseui: baseui instance to instantiate MercurialRepostitory with
91 """
90 """
92
91
93 log.info('scanning for repositories in %s', repos_path)
92 log.info('scanning for repositories in %s', repos_path)
94
93
95 if not isinstance(baseui, ui.ui):
94 if repos_path is None:
96 baseui = make_ui('db')
95 repos_path = self.repos_path
96
97 baseui = make_ui('db')
97 repos_list = {}
98 repos_list = {}
98
99
99 for name, path in get_filesystem_repos(repos_path, recursive=True):
100 for name, path in get_filesystem_repos(repos_path, recursive=True):
100 try:
101 try:
101 if repos_list.has_key(name):
102 if repos_list.has_key(name):
102 raise RepositoryError('Duplicate repository name %s '
103 raise RepositoryError('Duplicate repository name %s '
103 'found in %s' % (name, path))
104 'found in %s' % (name, path))
104 else:
105 else:
105
106
106 klass = get_backend(path[0])
107 klass = get_backend(path[0])
107
108
108 if path[0] == 'hg' and path[0] in BACKENDS.keys():
109 if path[0] == 'hg' and path[0] in BACKENDS.keys():
109 repos_list[name] = klass(path[1], baseui=baseui)
110 repos_list[name] = klass(path[1], baseui=baseui)
110
111
111 if path[0] == 'git' and path[0] in BACKENDS.keys():
112 if path[0] == 'git' and path[0] in BACKENDS.keys():
112 repos_list[name] = klass(path[1])
113 repos_list[name] = klass(path[1])
113 except OSError:
114 except OSError:
114 continue
115 continue
115
116
116 return repos_list
117 return repos_list
117
118
118 def get_repos(self, all_repos=None):
119 def get_repos(self, all_repos=None):
119 """Get all repos from db and for each repo create it's backend instance.
120 """Get all repos from db and for each repo create it's backend instance.
120 and fill that backed with information from database
121 and fill that backed with information from database
121
122
122 :param all_repos: give specific repositories list, good for filtering
123 :param all_repos: give specific repositories list, good for filtering
123 """
124 """
124
125
125 if all_repos is None:
126 if all_repos is None:
126 all_repos = self.sa.query(Repository)\
127 all_repos = self.sa.query(Repository)\
127 .order_by(Repository.repo_name).all()
128 .order_by(Repository.repo_name).all()
128
129
129 #get the repositories that should be invalidated
130 #get the repositories that should be invalidated
130 invalidation_list = [str(x.cache_key) for x in \
131 invalidation_list = [str(x.cache_key) for x in \
131 self.sa.query(CacheInvalidation.cache_key)\
132 self.sa.query(CacheInvalidation.cache_key)\
132 .filter(CacheInvalidation.cache_active == False)\
133 .filter(CacheInvalidation.cache_active == False)\
133 .all()]
134 .all()]
134
135
135 for r in all_repos:
136 for r in all_repos:
136
137
137 repo = self.get(r.repo_name, invalidation_list)
138 repo, dbrepo = self.get(r.repo_name, invalidation_list)
138
139
139 if repo is not None:
140 if repo is not None:
140 last_change = repo.last_change
141 last_change = repo.last_change
141 tip = h.get_changeset_safe(repo, 'tip')
142 tip = h.get_changeset_safe(repo, 'tip')
142
143
143 tmp_d = {}
144 tmp_d = {}
144 tmp_d['name'] = r.repo_name
145 tmp_d['name'] = r.repo_name
145 tmp_d['name_sort'] = tmp_d['name'].lower()
146 tmp_d['name_sort'] = tmp_d['name'].lower()
146 tmp_d['description'] = repo.dbrepo.description
147 tmp_d['description'] = dbrepo.description
147 tmp_d['description_sort'] = tmp_d['description']
148 tmp_d['description_sort'] = tmp_d['description']
148 tmp_d['last_change'] = last_change
149 tmp_d['last_change'] = last_change
149 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
150 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
150 tmp_d['tip'] = tip.raw_id
151 tmp_d['tip'] = tip.raw_id
151 tmp_d['tip_sort'] = tip.revision
152 tmp_d['tip_sort'] = tip.revision
152 tmp_d['rev'] = tip.revision
153 tmp_d['rev'] = tip.revision
153 tmp_d['contact'] = repo.dbrepo.user.full_contact
154 tmp_d['contact'] = dbrepo.user.full_contact
154 tmp_d['contact_sort'] = tmp_d['contact']
155 tmp_d['contact_sort'] = tmp_d['contact']
155 tmp_d['owner_sort'] = tmp_d['contact']
156 tmp_d['owner_sort'] = tmp_d['contact']
156 tmp_d['repo_archives'] = list(repo._get_archives())
157 tmp_d['repo_archives'] = list(repo._get_archives())
157 tmp_d['last_msg'] = tip.message
158 tmp_d['last_msg'] = tip.message
158 tmp_d['repo'] = repo
159 tmp_d['repo'] = repo
160 tmp_d['dbrepo'] = dbrepo
159 yield tmp_d
161 yield tmp_d
160
162
161 def get_repo(self, repo_name):
163 def get(self, repo_name, invalidation_list=None, retval='all'):
162 return self.get(repo_name)
164 """Returns a tuple of Repository,DbRepository,
163
165 Get's repository from given name, creates BackendInstance and
164 def get(self, repo_name, invalidation_list=None):
165 """Get's repository from given name, creates BackendInstance and
166 propagates it's data from database with all additional information
166 propagates it's data from database with all additional information
167
167
168 :param repo_name:
168 :param repo_name:
169 :param invalidation_list: if a invalidation list is given the get
169 :param invalidation_list: if a invalidation list is given the get
170 method should not manually check if this repository needs
170 method should not manually check if this repository needs
171 invalidation and just invalidate the repositories in list
171 invalidation and just invalidate the repositories in list
172
172 :param retval: string specifing what to return one of 'repo','dbrepo',
173 'all'if repo or dbrepo is given it'll just lazy load chosen type
174 and return None as the second
173 """
175 """
174 if not HasRepoPermissionAny('repository.read', 'repository.write',
176 if not HasRepoPermissionAny('repository.read', 'repository.write',
175 'repository.admin')(repo_name, 'get repo check'):
177 'repository.admin')(repo_name, 'get repo check'):
176 return
178 return
177
179
178 #======================================================================
180 #======================================================================
179 # CACHE FUNCTION
181 # CACHE FUNCTION
180 #======================================================================
182 #======================================================================
181 @cache_region('long_term')
183 @cache_region('long_term')
182 def _get_repo(repo_name):
184 def _get_repo(repo_name):
183
185
184 repo_path = os.path.join(self.repos_path, repo_name)
186 repo_path = os.path.join(self.repos_path, repo_name)
185
187
186 try:
188 try:
187 alias = get_scm(repo_path)[0]
189 alias = get_scm(repo_path)[0]
188 log.debug('Creating instance of %s repository', alias)
190 log.debug('Creating instance of %s repository', alias)
189 backend = get_backend(alias)
191 backend = get_backend(alias)
190 except VCSError:
192 except VCSError:
191 log.error(traceback.format_exc())
193 log.error(traceback.format_exc())
192 log.error('Perhaps this repository is in db and not in filesystem'
194 log.error('Perhaps this repository is in db and not in '
193 'run rescan repositories with "destroy old data "'
195 'filesystem run rescan repositories with '
194 'option from admin panel')
196 '"destroy old data " option from admin panel')
195 return
197 return
196
198
197 if alias == 'hg':
199 if alias == 'hg':
198 from pylons import app_globals as g
200 repo = backend(repo_path, create=False, baseui=make_ui('db'))
199 repo = backend(repo_path, create=False, baseui=g.baseui)
200 #skip hidden web repository
201 #skip hidden web repository
201 if repo._get_hidden():
202 if repo._get_hidden():
202 return
203 return
203 else:
204 else:
204 repo = backend(repo_path, create=False)
205 repo = backend(repo_path, create=False)
205
206
206 dbrepo = self.sa.query(Repository)\
207 .options(joinedload(Repository.fork))\
208 .options(joinedload(Repository.user))\
209 .filter(Repository.repo_name == repo_name)\
210 .scalar()
211
212 self.sa.expunge_all()
213 log.debug('making transient %s', dbrepo)
214 make_transient(dbrepo)
215
216 for attr in ['user', 'forks', 'followers', 'group', 'repo_to_perm',
217 'users_group_to_perm', 'stats', 'logs']:
218 attr = getattr(dbrepo, attr, False)
219 if attr:
220 if isinstance(attr, list):
221 for a in attr:
222 log.debug('making transient %s', a)
223 make_transient(a)
224 else:
225 log.debug('making transient %s', attr)
226 make_transient(attr)
227
228 repo.dbrepo = dbrepo
229 return repo
207 return repo
230
208
231 pre_invalidate = True
209 pre_invalidate = True
210 dbinvalidate = False
211
232 if invalidation_list is not None:
212 if invalidation_list is not None:
233 pre_invalidate = repo_name in invalidation_list
213 pre_invalidate = repo_name in invalidation_list
234
214
235 if pre_invalidate:
215 if pre_invalidate:
216 #this returns object to invalidate
236 invalidate = self._should_invalidate(repo_name)
217 invalidate = self._should_invalidate(repo_name)
237
238 if invalidate:
218 if invalidate:
239 log.info('invalidating cache for repository %s', repo_name)
219 log.info('invalidating cache for repository %s', repo_name)
240 region_invalidate(_get_repo, None, repo_name)
220 #region_invalidate(_get_repo, None, repo_name)
241 self._mark_invalidated(invalidate)
221 self._mark_invalidated(invalidate)
222 dbinvalidate = True
242
223
243 return _get_repo(repo_name)
224 r, dbr = None, None
225 if retval == 'repo' or 'all':
226 r = _get_repo(repo_name)
227 if retval == 'dbrepo' or 'all':
228 dbr = RepoModel(self.sa).get_full(repo_name, cache=True,
229 invalidate=dbinvalidate)
230
231
232 return r, dbr
244
233
245
234
246
235
247 def mark_for_invalidation(self, repo_name):
236 def mark_for_invalidation(self, repo_name):
248 """Puts cache invalidation task into db for
237 """Puts cache invalidation task into db for
249 further global cache invalidation
238 further global cache invalidation
250
239
251 :param repo_name: this repo that should invalidation take place
240 :param repo_name: this repo that should invalidation take place
252 """
241 """
253
242
254 log.debug('marking %s for invalidation', repo_name)
243 log.debug('marking %s for invalidation', repo_name)
255 cache = self.sa.query(CacheInvalidation)\
244 cache = self.sa.query(CacheInvalidation)\
256 .filter(CacheInvalidation.cache_key == repo_name).scalar()
245 .filter(CacheInvalidation.cache_key == repo_name).scalar()
257
246
258 if cache:
247 if cache:
259 #mark this cache as inactive
248 #mark this cache as inactive
260 cache.cache_active = False
249 cache.cache_active = False
261 else:
250 else:
262 log.debug('cache key not found in invalidation db -> creating one')
251 log.debug('cache key not found in invalidation db -> creating one')
263 cache = CacheInvalidation(repo_name)
252 cache = CacheInvalidation(repo_name)
264
253
265 try:
254 try:
266 self.sa.add(cache)
255 self.sa.add(cache)
267 self.sa.commit()
256 self.sa.commit()
268 except (DatabaseError,):
257 except (DatabaseError,):
269 log.error(traceback.format_exc())
258 log.error(traceback.format_exc())
270 self.sa.rollback()
259 self.sa.rollback()
271
260
272
261
273 def toggle_following_repo(self, follow_repo_id, user_id):
262 def toggle_following_repo(self, follow_repo_id, user_id):
274
263
275 f = self.sa.query(UserFollowing)\
264 f = self.sa.query(UserFollowing)\
276 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
265 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
277 .filter(UserFollowing.user_id == user_id).scalar()
266 .filter(UserFollowing.user_id == user_id).scalar()
278
267
279 if f is not None:
268 if f is not None:
280
269
281 try:
270 try:
282 self.sa.delete(f)
271 self.sa.delete(f)
283 self.sa.commit()
272 self.sa.commit()
284 action_logger(UserTemp(user_id),
273 action_logger(UserTemp(user_id),
285 'stopped_following_repo',
274 'stopped_following_repo',
286 RepoTemp(follow_repo_id))
275 RepoTemp(follow_repo_id))
287 return
276 return
288 except:
277 except:
289 log.error(traceback.format_exc())
278 log.error(traceback.format_exc())
290 self.sa.rollback()
279 self.sa.rollback()
291 raise
280 raise
292
281
293
282
294 try:
283 try:
295 f = UserFollowing()
284 f = UserFollowing()
296 f.user_id = user_id
285 f.user_id = user_id
297 f.follows_repo_id = follow_repo_id
286 f.follows_repo_id = follow_repo_id
298 self.sa.add(f)
287 self.sa.add(f)
299 self.sa.commit()
288 self.sa.commit()
300 action_logger(UserTemp(user_id),
289 action_logger(UserTemp(user_id),
301 'started_following_repo',
290 'started_following_repo',
302 RepoTemp(follow_repo_id))
291 RepoTemp(follow_repo_id))
303 except:
292 except:
304 log.error(traceback.format_exc())
293 log.error(traceback.format_exc())
305 self.sa.rollback()
294 self.sa.rollback()
306 raise
295 raise
307
296
308 def toggle_following_user(self, follow_user_id , user_id):
297 def toggle_following_user(self, follow_user_id , user_id):
309 f = self.sa.query(UserFollowing)\
298 f = self.sa.query(UserFollowing)\
310 .filter(UserFollowing.follows_user_id == follow_user_id)\
299 .filter(UserFollowing.follows_user_id == follow_user_id)\
311 .filter(UserFollowing.user_id == user_id).scalar()
300 .filter(UserFollowing.user_id == user_id).scalar()
312
301
313 if f is not None:
302 if f is not None:
314 try:
303 try:
315 self.sa.delete(f)
304 self.sa.delete(f)
316 self.sa.commit()
305 self.sa.commit()
317 return
306 return
318 except:
307 except:
319 log.error(traceback.format_exc())
308 log.error(traceback.format_exc())
320 self.sa.rollback()
309 self.sa.rollback()
321 raise
310 raise
322
311
323 try:
312 try:
324 f = UserFollowing()
313 f = UserFollowing()
325 f.user_id = user_id
314 f.user_id = user_id
326 f.follows_user_id = follow_user_id
315 f.follows_user_id = follow_user_id
327 self.sa.add(f)
316 self.sa.add(f)
328 self.sa.commit()
317 self.sa.commit()
329 except:
318 except:
330 log.error(traceback.format_exc())
319 log.error(traceback.format_exc())
331 self.sa.rollback()
320 self.sa.rollback()
332 raise
321 raise
333
322
334 def is_following_repo(self, repo_name, user_id, cache=False):
323 def is_following_repo(self, repo_name, user_id, cache=False):
335 r = self.sa.query(Repository)\
324 r = self.sa.query(Repository)\
336 .filter(Repository.repo_name == repo_name).scalar()
325 .filter(Repository.repo_name == repo_name).scalar()
337
326
338 f = self.sa.query(UserFollowing)\
327 f = self.sa.query(UserFollowing)\
339 .filter(UserFollowing.follows_repository == r)\
328 .filter(UserFollowing.follows_repository == r)\
340 .filter(UserFollowing.user_id == user_id).scalar()
329 .filter(UserFollowing.user_id == user_id).scalar()
341
330
342 return f is not None
331 return f is not None
343
332
344 def is_following_user(self, username, user_id, cache=False):
333 def is_following_user(self, username, user_id, cache=False):
345 u = UserModel(self.sa).get_by_username(username)
334 u = UserModel(self.sa).get_by_username(username)
346
335
347 f = self.sa.query(UserFollowing)\
336 f = self.sa.query(UserFollowing)\
348 .filter(UserFollowing.follows_user == u)\
337 .filter(UserFollowing.follows_user == u)\
349 .filter(UserFollowing.user_id == user_id).scalar()
338 .filter(UserFollowing.user_id == user_id).scalar()
350
339
351 return f is not None
340 return f is not None
352
341
353 def get_followers(self, repo_id):
342 def get_followers(self, repo_id):
354 return self.sa.query(UserFollowing)\
343 return self.sa.query(UserFollowing)\
355 .filter(UserFollowing.follows_repo_id == repo_id).count()
344 .filter(UserFollowing.follows_repo_id == repo_id).count()
356
345
357 def get_forks(self, repo_id):
346 def get_forks(self, repo_id):
358 return self.sa.query(Repository)\
347 return self.sa.query(Repository)\
359 .filter(Repository.fork_id == repo_id).count()
348 .filter(Repository.fork_id == repo_id).count()
360
349
361
350
362 def get_unread_journal(self):
351 def get_unread_journal(self):
363 return self.sa.query(UserLog).count()
352 return self.sa.query(UserLog).count()
364
353
365
354
366 def _should_invalidate(self, repo_name):
355 def _should_invalidate(self, repo_name):
367 """Looks up database for invalidation signals for this repo_name
356 """Looks up database for invalidation signals for this repo_name
368
357
369 :param repo_name:
358 :param repo_name:
370 """
359 """
371
360
372 ret = self.sa.query(CacheInvalidation)\
361 ret = self.sa.query(CacheInvalidation)\
373 .options(FromCache('sql_cache_short',
374 'get_invalidation_%s' % repo_name))\
375 .filter(CacheInvalidation.cache_key == repo_name)\
362 .filter(CacheInvalidation.cache_key == repo_name)\
376 .filter(CacheInvalidation.cache_active == False)\
363 .filter(CacheInvalidation.cache_active == False)\
377 .scalar()
364 .scalar()
378
365
379 return ret
366 return ret
380
367
381 def _mark_invalidated(self, cache_key):
368 def _mark_invalidated(self, cache_key):
382 """ Marks all occurences of cache to invaldation as already invalidated
369 """ Marks all occurrences of cache to invalidation as already
370 invalidated
383
371
384 :param cache_key:
372 :param cache_key:
385 """
373 """
386
374
387 if cache_key:
375 if cache_key:
388 log.debug('marking %s as already invalidated', cache_key)
376 log.debug('marking %s as already invalidated', cache_key)
389 try:
377 try:
390 cache_key.cache_active = True
378 cache_key.cache_active = True
391 self.sa.add(cache_key)
379 self.sa.add(cache_key)
392 self.sa.commit()
380 self.sa.commit()
393 except (DatabaseError,):
381 except (DatabaseError,):
394 log.error(traceback.format_exc())
382 log.error(traceback.format_exc())
395 self.sa.rollback()
383 self.sa.rollback()
396
384
@@ -1,88 +1,88 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Repositories administration')} - ${c.rhodecode_name}
5 ${_('Repositories administration')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Repositories')}
10 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Repositories')}
11 </%def>
11 </%def>
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15 <%def name="main()">
15 <%def name="main()">
16 <div class="box">
16 <div class="box">
17 <!-- box / title -->
17 <!-- box / title -->
18 <div class="title">
18 <div class="title">
19 ${self.breadcrumbs()}
19 ${self.breadcrumbs()}
20 <ul class="links">
20 <ul class="links">
21 <li>
21 <li>
22 <span>${h.link_to(u'ADD NEW REPOSITORY',h.url('new_repo'))}</span>
22 <span>${h.link_to(u'ADD NEW REPOSITORY',h.url('new_repo'))}</span>
23 </li>
23 </li>
24 </ul>
24 </ul>
25 </div>
25 </div>
26 <!-- end box / title -->
26 <!-- end box / title -->
27 <div class="table">
27 <div class="table">
28 <table class="table_disp">
28 <table class="table_disp">
29 <tr class="header">
29 <tr class="header">
30 <th class="left">${_('Name')}</th>
30 <th class="left">${_('Name')}</th>
31 <th class="left">${_('Description')}</th>
31 <th class="left">${_('Description')}</th>
32 <th class="left">${_('Last change')}</th>
32 <th class="left">${_('Last change')}</th>
33 <th class="left">${_('Tip')}</th>
33 <th class="left">${_('Tip')}</th>
34 <th class="left">${_('Contact')}</th>
34 <th class="left">${_('Contact')}</th>
35 <th class="left">${_('action')}</th>
35 <th class="left">${_('action')}</th>
36 </tr>
36 </tr>
37 %for cnt,repo in enumerate(c.repos_list):
37 %for cnt,repo in enumerate(c.repos_list):
38 <tr class="parity${cnt%2}">
38 <tr class="parity${cnt%2}">
39 <td>
39 <td>
40 ## TYPE OF REPO
40 ## TYPE OF REPO
41 %if repo['repo'].dbrepo.repo_type =='hg':
41 %if repo['dbrepo'].repo_type =='hg':
42 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="/images/icons/hgicon.png"/>
42 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="/images/icons/hgicon.png"/>
43 %elif repo['repo'].dbrepo.repo_type =='git':
43 %elif repo['dbrepo'].repo_type =='git':
44 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="/images/icons/giticon.png"/>
44 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="/images/icons/giticon.png"/>
45 %else:
45 %else:
46
46
47 %endif
47 %endif
48
48
49 ## PRIVATE/PUBLIC REPO
49 ## PRIVATE/PUBLIC REPO
50 %if repo['repo'].dbrepo.private:
50 %if repo['dbrepo'].private:
51 <img alt="${_('private')}" src="/images/icons/lock.png"/>
51 <img alt="${_('private')}" src="/images/icons/lock.png"/>
52 %else:
52 %else:
53 <img alt="${_('public')}" src="/images/icons/lock_open.png"/>
53 <img alt="${_('public')}" src="/images/icons/lock_open.png"/>
54 %endif
54 %endif
55 ${h.link_to(repo['name'],h.url('edit_repo',repo_name=repo['name']))}
55 ${h.link_to(repo['name'],h.url('edit_repo',repo_name=repo['name']))}
56
56
57 %if repo['repo'].dbrepo.fork:
57 %if repo['dbrepo'].fork:
58 <a href="${h.url('summary_home',repo_name=repo['repo'].dbrepo.fork.repo_name)}">
58 <a href="${h.url('summary_home',repo_name=repo['dbrepo'].fork.repo_name)}">
59 <img class="icon" alt="${_('public')}"
59 <img class="icon" alt="${_('public')}"
60 title="${_('Fork of')} ${repo['repo'].dbrepo.fork.repo_name}"
60 title="${_('Fork of')} ${repo['dbrepo'].fork.repo_name}"
61 src="/images/icons/arrow_divide.png"/></a>
61 src="/images/icons/arrow_divide.png"/></a>
62 %endif
62 %endif
63 </td>
63 </td>
64 <td title="${repo['description']}">${h.truncate(repo['description'],60)}</td>
64 <td title="${repo['description']}">${h.truncate(repo['description'],60)}</td>
65 <td>${h.age(repo['last_change'])}</td>
65 <td>${h.age(repo['last_change'])}</td>
66 <td>
66 <td>
67 %if repo['rev']>=0:
67 %if repo['rev']>=0:
68 ${h.link_to('r%s:%s' % (repo['rev'],h.short_id(repo['tip'])),
68 ${h.link_to('r%s:%s' % (repo['rev'],h.short_id(repo['tip'])),
69 h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']),
69 h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']),
70 class_="tooltip",
70 class_="tooltip",
71 title=h.tooltip(repo['last_msg']))}
71 title=h.tooltip(repo['last_msg']))}
72 %else:
72 %else:
73 ${_('No changesets yet')}
73 ${_('No changesets yet')}
74 %endif
74 %endif
75 </td>
75 </td>
76 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
76 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
77 <td>
77 <td>
78 ${h.form(url('repo', repo_name=repo['name']),method='delete')}
78 ${h.form(url('repo', repo_name=repo['name']),method='delete')}
79 ${h.submit('remove_%s' % repo['name'],'delete',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
79 ${h.submit('remove_%s' % repo['name'],'delete',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
80 ${h.end_form()}
80 ${h.end_form()}
81 </td>
81 </td>
82 </tr>
82 </tr>
83 %endfor
83 %endfor
84 </table>
84 </table>
85 </div>
85 </div>
86 </div>
86 </div>
87
87
88 </%def>
88 </%def>
@@ -1,207 +1,207 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('My account')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
5 ${_('My account')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${_('My Account')}
9 ${_('My Account')}
10 </%def>
10 </%def>
11
11
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="main()">
16 <%def name="main()">
17
17
18 <div class="box box-left">
18 <div class="box box-left">
19 <!-- box / title -->
19 <!-- box / title -->
20 <div class="title">
20 <div class="title">
21 ${self.breadcrumbs()}
21 ${self.breadcrumbs()}
22 </div>
22 </div>
23 <!-- end box / title -->
23 <!-- end box / title -->
24 <div>
24 <div>
25 ${h.form(url('admin_settings_my_account_update'),method='put')}
25 ${h.form(url('admin_settings_my_account_update'),method='put')}
26 <div class="form">
26 <div class="form">
27
27
28 <div class="field">
28 <div class="field">
29 <div class="gravatar_box">
29 <div class="gravatar_box">
30 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
30 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
31 <p>
31 <p>
32 <strong>Change your avatar at <a href="http://gravatar.com">gravatar.com</a></strong><br/>
32 <strong>Change your avatar at <a href="http://gravatar.com">gravatar.com</a></strong><br/>
33 ${_('Using')} ${c.user.email}
33 ${_('Using')} ${c.user.email}
34 </p>
34 </p>
35 </div>
35 </div>
36 </div>
36 </div>
37
37
38 <div class="fields">
38 <div class="fields">
39 <div class="field">
39 <div class="field">
40 <div class="label">
40 <div class="label">
41 <label for="username">${_('Username')}:</label>
41 <label for="username">${_('Username')}:</label>
42 </div>
42 </div>
43 <div class="input">
43 <div class="input">
44 ${h.text('username',class_="medium")}
44 ${h.text('username',class_="medium")}
45 </div>
45 </div>
46 </div>
46 </div>
47
47
48 <div class="field">
48 <div class="field">
49 <div class="label">
49 <div class="label">
50 <label for="new_password">${_('New password')}:</label>
50 <label for="new_password">${_('New password')}:</label>
51 </div>
51 </div>
52 <div class="input">
52 <div class="input">
53 ${h.password('new_password',class_="medium")}
53 ${h.password('new_password',class_="medium")}
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 <div class="field">
57 <div class="field">
58 <div class="label">
58 <div class="label">
59 <label for="name">${_('First Name')}:</label>
59 <label for="name">${_('First Name')}:</label>
60 </div>
60 </div>
61 <div class="input">
61 <div class="input">
62 ${h.text('name',class_="medium")}
62 ${h.text('name',class_="medium")}
63 </div>
63 </div>
64 </div>
64 </div>
65
65
66 <div class="field">
66 <div class="field">
67 <div class="label">
67 <div class="label">
68 <label for="lastname">${_('Last Name')}:</label>
68 <label for="lastname">${_('Last Name')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 ${h.text('lastname',class_="medium")}
71 ${h.text('lastname',class_="medium")}
72 </div>
72 </div>
73 </div>
73 </div>
74
74
75 <div class="field">
75 <div class="field">
76 <div class="label">
76 <div class="label">
77 <label for="email">${_('Email')}:</label>
77 <label for="email">${_('Email')}:</label>
78 </div>
78 </div>
79 <div class="input">
79 <div class="input">
80 ${h.text('email',class_="medium")}
80 ${h.text('email',class_="medium")}
81 </div>
81 </div>
82 </div>
82 </div>
83
83
84 <div class="buttons">
84 <div class="buttons">
85 ${h.submit('save','Save',class_="ui-button")}
85 ${h.submit('save','Save',class_="ui-button")}
86 ${h.reset('reset','Reset',class_="ui-button")}
86 ${h.reset('reset','Reset',class_="ui-button")}
87 </div>
87 </div>
88 </div>
88 </div>
89 </div>
89 </div>
90 ${h.end_form()}
90 ${h.end_form()}
91 </div>
91 </div>
92 </div>
92 </div>
93
93
94 <div class="box box-right">
94 <div class="box box-right">
95 <!-- box / title -->
95 <!-- box / title -->
96 <div class="title">
96 <div class="title">
97 <h5>${_('My repositories')}
97 <h5>${_('My repositories')}
98 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
98 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
99 </h5>
99 </h5>
100 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
100 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
101 <ul class="links">
101 <ul class="links">
102 <li>
102 <li>
103 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
103 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
104 </li>
104 </li>
105 </ul>
105 </ul>
106 %endif
106 %endif
107 </div>
107 </div>
108 <!-- end box / title -->
108 <!-- end box / title -->
109 <div class="table">
109 <div class="table">
110 <table>
110 <table>
111 <thead>
111 <thead>
112 <tr>
112 <tr>
113 <th class="left">${_('Name')}</th>
113 <th class="left">${_('Name')}</th>
114 <th class="left">${_('revision')}</th>
114 <th class="left">${_('revision')}</th>
115 <th colspan="2" class="left">${_('action')}</th>
115 <th colspan="2" class="left">${_('action')}</th>
116 </thead>
116 </thead>
117 <tbody>
117 <tbody>
118 %if c.user_repos:
118 %if c.user_repos:
119 %for repo in c.user_repos:
119 %for repo in c.user_repos:
120 <tr>
120 <tr>
121 <td>
121 <td>
122 %if repo['repo'].dbrepo.repo_type =='hg':
122 %if repo['dbrepo'].repo_type =='hg':
123 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="/images/icons/hgicon.png"/>
123 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="/images/icons/hgicon.png"/>
124 %elif repo['repo'].dbrepo.repo_type =='git':
124 %elif repo['dbrepo'].repo_type =='git':
125 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="/images/icons/giticon.png"/>
125 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="/images/icons/giticon.png"/>
126 %else:
126 %else:
127
127
128 %endif
128 %endif
129 %if repo['repo'].dbrepo.private:
129 %if repo['dbrepo'].private:
130 <img class="icon" alt="${_('private')}" src="/images/icons/lock.png"/>
130 <img class="icon" alt="${_('private')}" src="/images/icons/lock.png"/>
131 %else:
131 %else:
132 <img class="icon" alt="${_('public')}" src="/images/icons/lock_open.png"/>
132 <img class="icon" alt="${_('public')}" src="/images/icons/lock_open.png"/>
133 %endif
133 %endif
134
134
135 ${h.link_to(repo['repo'].name, h.url('summary_home',repo_name=repo['repo'].name),class_="repo_name")}
135 ${h.link_to(repo['repo'].name, h.url('summary_home',repo_name=repo['repo'].name),class_="repo_name")}
136 %if repo['repo'].dbrepo.fork:
136 %if repo['dbrepo'].fork:
137 <a href="${h.url('summary_home',repo_name=repo['repo'].dbrepo.fork.repo_name)}">
137 <a href="${h.url('summary_home',repo_name=repo['dbrepo'].fork.repo_name)}">
138 <img class="icon" alt="${_('public')}"
138 <img class="icon" alt="${_('public')}"
139 title="${_('Fork of')} ${repo['repo'].dbrepo.fork.repo_name}"
139 title="${_('Fork of')} ${repo['dbrepo'].fork.repo_name}"
140 src="/images/icons/arrow_divide.png"/></a>
140 src="/images/icons/arrow_divide.png"/></a>
141 %endif
141 %endif
142 </td>
142 </td>
143 <td><span class="tooltip" title="${repo['repo'].last_change}">${("r%s:%s") % (h.get_changeset_safe(repo['repo'],'tip').revision,h.short_id(h.get_changeset_safe(repo['repo'],'tip').raw_id))}</span></td>
143 <td><span class="tooltip" title="${repo['repo'].last_change}">${("r%s:%s") % (h.get_changeset_safe(repo['repo'],'tip').revision,h.short_id(h.get_changeset_safe(repo['repo'],'tip').raw_id))}</span></td>
144 <td><a href="${h.url('repo_settings_home',repo_name=repo['repo'].name)}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="/images/icons/application_form_edit.png"/></a></td>
144 <td><a href="${h.url('repo_settings_home',repo_name=repo['repo'].name)}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="/images/icons/application_form_edit.png"/></a></td>
145 <td>
145 <td>
146 ${h.form(url('repo_settings_delete', repo_name=repo['repo'].name),method='delete')}
146 ${h.form(url('repo_settings_delete', repo_name=repo['repo'].name),method='delete')}
147 ${h.submit('remove_%s' % repo['repo'].name,'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
147 ${h.submit('remove_%s' % repo['repo'].name,'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
148 ${h.end_form()}
148 ${h.end_form()}
149 </td>
149 </td>
150 </tr>
150 </tr>
151 %endfor
151 %endfor
152 %else:
152 %else:
153 ${_('No repositories yet')}
153 ${_('No repositories yet')}
154 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
154 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
155 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'))}
155 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'))}
156 %endif
156 %endif
157 %endif
157 %endif
158 </tbody>
158 </tbody>
159 </table>
159 </table>
160 </div>
160 </div>
161
161
162 </div>
162 </div>
163 <script type="text/javascript">
163 <script type="text/javascript">
164 var D = YAHOO.util.Dom;
164 var D = YAHOO.util.Dom;
165 var E = YAHOO.util.Event;
165 var E = YAHOO.util.Event;
166 var S = YAHOO.util.Selector;
166 var S = YAHOO.util.Selector;
167
167
168 var q_filter = D.get('q_filter');
168 var q_filter = D.get('q_filter');
169 var F = YAHOO.namespace('q_filter');
169 var F = YAHOO.namespace('q_filter');
170
170
171 E.on(q_filter,'click',function(){
171 E.on(q_filter,'click',function(){
172 q_filter.value = '';
172 q_filter.value = '';
173 });
173 });
174
174
175 F.filterTimeout = null;
175 F.filterTimeout = null;
176
176
177 F.updateFilter = function() {
177 F.updateFilter = function() {
178 // Reset timeout
178 // Reset timeout
179 F.filterTimeout = null;
179 F.filterTimeout = null;
180
180
181 var obsolete = [];
181 var obsolete = [];
182 var nodes = S.query('div.table tr td a.repo_name');
182 var nodes = S.query('div.table tr td a.repo_name');
183 var req = D.get('q_filter').value;
183 var req = D.get('q_filter').value;
184 for (n in nodes){
184 for (n in nodes){
185 D.setStyle(nodes[n].parentNode.parentNode,'display','')
185 D.setStyle(nodes[n].parentNode.parentNode,'display','')
186 }
186 }
187 if (req){
187 if (req){
188 for (n in nodes){
188 for (n in nodes){
189 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
189 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
190 obsolete.push(nodes[n]);
190 obsolete.push(nodes[n]);
191 }
191 }
192 }
192 }
193 if(obsolete){
193 if(obsolete){
194 for (n in obsolete){
194 for (n in obsolete){
195 D.setStyle(obsolete[n].parentNode.parentNode,'display','none');
195 D.setStyle(obsolete[n].parentNode.parentNode,'display','none');
196 }
196 }
197 }
197 }
198 }
198 }
199 }
199 }
200
200
201 E.on(q_filter,'keyup',function(e){
201 E.on(q_filter,'keyup',function(e){
202 clearTimeout(F.filterTimeout);
202 clearTimeout(F.filterTimeout);
203 setTimeout(F.updateFilter,600);
203 setTimeout(F.updateFilter,600);
204 });
204 });
205
205
206 </script>
206 </script>
207 </%def> No newline at end of file
207 </%def>
@@ -1,392 +1,392 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
4 <head>
4 <head>
5 <title>${next.title()}</title>
5 <title>${next.title()}</title>
6 <link rel="icon" href="/images/icons/database_gear.png" type="image/png" />
6 <link rel="icon" href="/images/icons/database_gear.png" type="image/png" />
7 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
8 <meta name="robots" content="index, nofollow"/>
8 <meta name="robots" content="index, nofollow"/>
9 <!-- stylesheets -->
9 <!-- stylesheets -->
10 ${self.css()}
10 ${self.css()}
11 <!-- scripts -->
11 <!-- scripts -->
12 ${self.js()}
12 ${self.js()}
13 %if c.ga_code:
13 %if c.ga_code:
14 <script type="text/javascript">
14 <script type="text/javascript">
15
15
16 var _gaq = _gaq || [];
16 var _gaq = _gaq || [];
17 _gaq.push(['_setAccount', '${c.ga_code}']);
17 _gaq.push(['_setAccount', '${c.ga_code}']);
18 _gaq.push(['_trackPageview']);
18 _gaq.push(['_trackPageview']);
19
19
20 (function() {
20 (function() {
21 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
21 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
22 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
22 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
23 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
23 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
24 })();
24 })();
25
25
26
26
27 </script>
27 </script>
28 %endif
28 %endif
29 </head>
29 </head>
30 <body>
30 <body>
31 <!-- header -->
31 <!-- header -->
32 <div id="header">
32 <div id="header">
33 <!-- user -->
33 <!-- user -->
34 <ul id="logged-user">
34 <ul id="logged-user">
35 <li class="first">
35 <li class="first">
36 <div class="gravatar">
36 <div class="gravatar">
37 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,20)}" />
37 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,20)}" />
38 </div>
38 </div>
39 <div class="account">
39 <div class="account">
40 %if c.rhodecode_user.username == 'default':
40 %if c.rhodecode_user.username == 'default':
41 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
41 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
42 ${h.link_to('anonymous',h.url('register'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
42 ${h.link_to('anonymous',h.url('register'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
43 %else:
43 %else:
44 ${h.link_to('anonymous',h.url('#'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
44 ${h.link_to('anonymous',h.url('#'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
45 %endif
45 %endif
46
46
47 %else:
47 %else:
48 ${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
48 ${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
49 %endif
49 %endif
50 </div>
50 </div>
51 </li>
51 </li>
52 <li>
52 <li>
53 <a href="${h.url('home')}">${_('Home')}</a>
53 <a href="${h.url('home')}">${_('Home')}</a>
54 </li>
54 </li>
55 %if c.rhodecode_user.username != 'default':
55 %if c.rhodecode_user.username != 'default':
56 <li>
56 <li>
57 <a href="${h.url('journal')}">${_('Journal')}</a>
57 <a href="${h.url('journal')}">${_('Journal')}</a>
58 ##(${c.unread_journal})</a>
58 ##(${c.unread_journal})</a>
59 </li>
59 </li>
60 %endif
60 %endif
61 %if c.rhodecode_user.username == 'default':
61 %if c.rhodecode_user.username == 'default':
62 <li class="last highlight">${h.link_to(u'Login',h.url('login_home'))}</li>
62 <li class="last highlight">${h.link_to(u'Login',h.url('login_home'))}</li>
63 %else:
63 %else:
64 <li class="last highlight">${h.link_to(u'Log Out',h.url('logout_home'))}</li>
64 <li class="last highlight">${h.link_to(u'Log Out',h.url('logout_home'))}</li>
65 %endif
65 %endif
66 </ul>
66 </ul>
67 <!-- end user -->
67 <!-- end user -->
68 <div id="header-inner" class="title top-left-rounded-corner top-right-rounded-corner">
68 <div id="header-inner" class="title top-left-rounded-corner top-right-rounded-corner">
69 <!-- logo -->
69 <!-- logo -->
70 <div id="logo">
70 <div id="logo">
71 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
71 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
72 </div>
72 </div>
73 <!-- end logo -->
73 <!-- end logo -->
74 <!-- menu -->
74 <!-- menu -->
75 ${self.page_nav()}
75 ${self.page_nav()}
76 <!-- quick -->
76 <!-- quick -->
77 </div>
77 </div>
78 </div>
78 </div>
79 <!-- end header -->
79 <!-- end header -->
80
80
81 <!-- CONTENT -->
81 <!-- CONTENT -->
82 <div id="content">
82 <div id="content">
83 <div class="flash_msg">
83 <div class="flash_msg">
84 <% messages = h.flash.pop_messages() %>
84 <% messages = h.flash.pop_messages() %>
85 % if messages:
85 % if messages:
86 <ul id="flash-messages">
86 <ul id="flash-messages">
87 % for message in messages:
87 % for message in messages:
88 <li class="${message.category}_msg">${message}</li>
88 <li class="${message.category}_msg">${message}</li>
89 % endfor
89 % endfor
90 </ul>
90 </ul>
91 % endif
91 % endif
92 </div>
92 </div>
93 <div id="main">
93 <div id="main">
94 ${next.main()}
94 ${next.main()}
95 </div>
95 </div>
96 </div>
96 </div>
97 <!-- END CONTENT -->
97 <!-- END CONTENT -->
98
98
99 <!-- footer -->
99 <!-- footer -->
100 <div id="footer">
100 <div id="footer">
101 <div id="footer-inner" class="title bottom-left-rounded-corner bottom-right-rounded-corner">
101 <div id="footer-inner" class="title bottom-left-rounded-corner bottom-right-rounded-corner">
102 <div>
102 <div>
103 <p class="footer-link">${h.link_to(_('Submit a bug'),h.url('bugtracker'))}</p>
103 <p class="footer-link">${h.link_to(_('Submit a bug'),h.url('bugtracker'))}</p>
104 <p class="footer-link">${h.link_to(_('GPL license'),h.url('gpl_license'))}</p>
104 <p class="footer-link">${h.link_to(_('GPL license'),h.url('gpl_license'))}</p>
105 <p>RhodeCode ${c.rhodecode_version} &copy; 2010-2011 by Marcin Kuzminski</p>
105 <p>RhodeCode ${c.rhodecode_version} &copy; 2010-2011 by Marcin Kuzminski</p>
106 </div>
106 </div>
107 </div>
107 </div>
108 <script type="text/javascript">
108 <script type="text/javascript">
109 function tooltip_activate(){
109 function tooltip_activate(){
110 ${h.tooltip.activate()}
110 ${h.tooltip.activate()}
111 }
111 }
112 tooltip_activate();
112 tooltip_activate();
113 </script>
113 </script>
114 </div>
114 </div>
115 <!-- end footer -->
115 <!-- end footer -->
116 </body>
116 </body>
117
117
118 </html>
118 </html>
119
119
120 ### MAKO DEFS ###
120 ### MAKO DEFS ###
121 <%def name="page_nav()">
121 <%def name="page_nav()">
122 ${self.menu()}
122 ${self.menu()}
123 </%def>
123 </%def>
124
124
125 <%def name="menu(current=None)">
125 <%def name="menu(current=None)">
126 <%
126 <%
127 def is_current(selected):
127 def is_current(selected):
128 if selected == current:
128 if selected == current:
129 return h.literal('class="current"')
129 return h.literal('class="current"')
130 %>
130 %>
131 %if current not in ['home','admin']:
131 %if current not in ['home','admin']:
132 ##REGULAR MENU
132 ##REGULAR MENU
133 <ul id="quick">
133 <ul id="quick">
134 <!-- repo switcher -->
134 <!-- repo switcher -->
135 <li>
135 <li>
136 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
136 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
137 <span class="icon">
137 <span class="icon">
138 <img src="/images/icons/database.png" alt="${_('Products')}" />
138 <img src="/images/icons/database.png" alt="${_('Products')}" />
139 </span>
139 </span>
140 <span>&darr;</span>
140 <span>&darr;</span>
141 </a>
141 </a>
142 <ul class="repo_switcher">
142 <ul class="repo_switcher">
143 %for repo in c.cached_repo_list:
143 %for repo in c.cached_repo_list:
144
144
145 %if repo['repo'].dbrepo.private:
145 %if repo['dbrepo'].private:
146 <li><img src="/images/icons/lock.png" alt="${_('Private repository')}" class="repo_switcher_type"/>${h.link_to(repo['repo'].name,h.url('summary_home',repo_name=repo['repo'].name),class_="%s" % repo['repo'].dbrepo.repo_type)}</li>
146 <li><img src="/images/icons/lock.png" alt="${_('Private repository')}" class="repo_switcher_type"/>${h.link_to(repo['repo'].name,h.url('summary_home',repo_name=repo['repo'].name),class_="%s" % repo['dbrepo'].repo_type)}</li>
147 %else:
147 %else:
148 <li><img src="/images/icons/lock_open.png" alt="${_('Public repository')}" class="repo_switcher_type" />${h.link_to(repo['repo'].name,h.url('summary_home',repo_name=repo['repo'].name),class_="%s" % repo['repo'].dbrepo.repo_type)}</li>
148 <li><img src="/images/icons/lock_open.png" alt="${_('Public repository')}" class="repo_switcher_type" />${h.link_to(repo['repo'].name,h.url('summary_home',repo_name=repo['repo'].name),class_="%s" % repo['dbrepo'].repo_type)}</li>
149 %endif
149 %endif
150 %endfor
150 %endfor
151 </ul>
151 </ul>
152 </li>
152 </li>
153
153
154 <li ${is_current('summary')}>
154 <li ${is_current('summary')}>
155 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
155 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
156 <span class="icon">
156 <span class="icon">
157 <img src="/images/icons/clipboard_16.png" alt="${_('Summary')}" />
157 <img src="/images/icons/clipboard_16.png" alt="${_('Summary')}" />
158 </span>
158 </span>
159 <span>${_('Summary')}</span>
159 <span>${_('Summary')}</span>
160 </a>
160 </a>
161 </li>
161 </li>
162 ##<li ${is_current('shortlog')}>
162 ##<li ${is_current('shortlog')}>
163 ## <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
163 ## <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
164 ## <span class="icon">
164 ## <span class="icon">
165 ## <img src="/images/icons/application_view_list.png" alt="${_('Shortlog')}" />
165 ## <img src="/images/icons/application_view_list.png" alt="${_('Shortlog')}" />
166 ## </span>
166 ## </span>
167 ## <span>${_('Shortlog')}</span>
167 ## <span>${_('Shortlog')}</span>
168 ## </a>
168 ## </a>
169 ##</li>
169 ##</li>
170 <li ${is_current('changelog')}>
170 <li ${is_current('changelog')}>
171 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
171 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
172 <span class="icon">
172 <span class="icon">
173 <img src="/images/icons/time.png" alt="${_('Changelog')}" />
173 <img src="/images/icons/time.png" alt="${_('Changelog')}" />
174 </span>
174 </span>
175 <span>${_('Changelog')}</span>
175 <span>${_('Changelog')}</span>
176 </a>
176 </a>
177 </li>
177 </li>
178
178
179 <li ${is_current('switch_to')}>
179 <li ${is_current('switch_to')}>
180 <a title="${_('Switch to')}" href="#">
180 <a title="${_('Switch to')}" href="#">
181 <span class="icon">
181 <span class="icon">
182 <img src="/images/icons/arrow_switch.png" alt="${_('Switch to')}" />
182 <img src="/images/icons/arrow_switch.png" alt="${_('Switch to')}" />
183 </span>
183 </span>
184 <span>${_('Switch to')}</span>
184 <span>${_('Switch to')}</span>
185 </a>
185 </a>
186 <ul>
186 <ul>
187 <li>
187 <li>
188 ${h.link_to('%s (%s)' % (_('branches'),len(c.repository_branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
188 ${h.link_to('%s (%s)' % (_('branches'),len(c.repository_branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
189 <ul>
189 <ul>
190 %if c.repository_branches.values():
190 %if c.repository_branches.values():
191 %for cnt,branch in enumerate(c.repository_branches.items()):
191 %for cnt,branch in enumerate(c.repository_branches.items()):
192 <li>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
192 <li>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
193 %endfor
193 %endfor
194 %else:
194 %else:
195 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
195 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
196 %endif
196 %endif
197 </ul>
197 </ul>
198 </li>
198 </li>
199 <li>
199 <li>
200 ${h.link_to('%s (%s)' % (_('tags'),len(c.repository_tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
200 ${h.link_to('%s (%s)' % (_('tags'),len(c.repository_tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
201 <ul>
201 <ul>
202 %if c.repository_tags.values():
202 %if c.repository_tags.values():
203 %for cnt,tag in enumerate(c.repository_tags.items()):
203 %for cnt,tag in enumerate(c.repository_tags.items()):
204 <li>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
204 <li>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
205 %endfor
205 %endfor
206 %else:
206 %else:
207 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
207 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
208 %endif
208 %endif
209 </ul>
209 </ul>
210 </li>
210 </li>
211 </ul>
211 </ul>
212 </li>
212 </li>
213 <li ${is_current('files')}>
213 <li ${is_current('files')}>
214 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
214 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
215 <span class="icon">
215 <span class="icon">
216 <img src="/images/icons/file.png" alt="${_('Files')}" />
216 <img src="/images/icons/file.png" alt="${_('Files')}" />
217 </span>
217 </span>
218 <span>${_('Files')}</span>
218 <span>${_('Files')}</span>
219 </a>
219 </a>
220 </li>
220 </li>
221
221
222 <li ${is_current('options')}>
222 <li ${is_current('options')}>
223 <a title="${_('Options')}" href="#">
223 <a title="${_('Options')}" href="#">
224 <span class="icon">
224 <span class="icon">
225 <img src="/images/icons/table_gear.png" alt="${_('Admin')}" />
225 <img src="/images/icons/table_gear.png" alt="${_('Admin')}" />
226 </span>
226 </span>
227 <span>${_('Options')}</span>
227 <span>${_('Options')}</span>
228 </a>
228 </a>
229 <ul>
229 <ul>
230 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
230 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
231 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
231 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
232 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
232 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
233 %else:
233 %else:
234 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
234 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
235 %endif
235 %endif
236 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
236 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
237 %endif
237 %endif
238 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
238 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
239
239
240 %if h.HasPermissionAll('hg.admin')('access admin main page'):
240 %if h.HasPermissionAll('hg.admin')('access admin main page'):
241 <li>
241 <li>
242 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
242 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
243 <%def name="admin_menu()">
243 <%def name="admin_menu()">
244 <ul>
244 <ul>
245 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
245 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
246 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
246 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
247 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
247 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
248 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
248 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
249 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
249 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
250 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
250 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
251 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
251 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
252 </ul>
252 </ul>
253 </%def>
253 </%def>
254
254
255 ${admin_menu()}
255 ${admin_menu()}
256 </li>
256 </li>
257 %endif
257 %endif
258
258
259 </ul>
259 </ul>
260 </li>
260 </li>
261
261
262 <li>
262 <li>
263 <a title="${_('Followers')}" href="#">
263 <a title="${_('Followers')}" href="#">
264 <span class="icon_short">
264 <span class="icon_short">
265 <img src="/images/icons/heart.png" alt="${_('Followers')}" />
265 <img src="/images/icons/heart.png" alt="${_('Followers')}" />
266 </span>
266 </span>
267 <span class="short">${c.repository_followers}</span>
267 <span class="short">${c.repository_followers}</span>
268 </a>
268 </a>
269 </li>
269 </li>
270 <li>
270 <li>
271 <a title="${_('Forks')}" href="#">
271 <a title="${_('Forks')}" href="#">
272 <span class="icon_short">
272 <span class="icon_short">
273 <img src="/images/icons/arrow_divide.png" alt="${_('Forks')}" />
273 <img src="/images/icons/arrow_divide.png" alt="${_('Forks')}" />
274 </span>
274 </span>
275 <span class="short">${c.repository_forks}</span>
275 <span class="short">${c.repository_forks}</span>
276 </a>
276 </a>
277 </li>
277 </li>
278
278
279
279
280
280
281 </ul>
281 </ul>
282 %else:
282 %else:
283 ##ROOT MENU
283 ##ROOT MENU
284 <ul id="quick">
284 <ul id="quick">
285 <li>
285 <li>
286 <a title="${_('Home')}" href="${h.url('home')}">
286 <a title="${_('Home')}" href="${h.url('home')}">
287 <span class="icon">
287 <span class="icon">
288 <img src="/images/icons/home_16.png" alt="${_('Home')}" />
288 <img src="/images/icons/home_16.png" alt="${_('Home')}" />
289 </span>
289 </span>
290 <span>${_('Home')}</span>
290 <span>${_('Home')}</span>
291 </a>
291 </a>
292 </li>
292 </li>
293 %if c.rhodecode_user.username != 'default':
293 %if c.rhodecode_user.username != 'default':
294 <li>
294 <li>
295 <a title="${_('Journal')}" href="${h.url('journal')}">
295 <a title="${_('Journal')}" href="${h.url('journal')}">
296 <span class="icon">
296 <span class="icon">
297 <img src="/images/icons/book.png" alt="${_('Journal')}" />
297 <img src="/images/icons/book.png" alt="${_('Journal')}" />
298 </span>
298 </span>
299 <span>${_('Journal')}</span>
299 <span>${_('Journal')}</span>
300 </a>
300 </a>
301 </li>
301 </li>
302 %endif
302 %endif
303 <li>
303 <li>
304 <a title="${_('Search')}" href="${h.url('search')}">
304 <a title="${_('Search')}" href="${h.url('search')}">
305 <span class="icon">
305 <span class="icon">
306 <img src="/images/icons/search_16.png" alt="${_('Search')}" />
306 <img src="/images/icons/search_16.png" alt="${_('Search')}" />
307 </span>
307 </span>
308 <span>${_('Search')}</span>
308 <span>${_('Search')}</span>
309 </a>
309 </a>
310 </li>
310 </li>
311
311
312 %if h.HasPermissionAll('hg.admin')('access admin main page'):
312 %if h.HasPermissionAll('hg.admin')('access admin main page'):
313 <li ${is_current('admin')}>
313 <li ${is_current('admin')}>
314 <a title="${_('Admin')}" href="${h.url('admin_home')}">
314 <a title="${_('Admin')}" href="${h.url('admin_home')}">
315 <span class="icon">
315 <span class="icon">
316 <img src="/images/icons/cog_edit.png" alt="${_('Admin')}" />
316 <img src="/images/icons/cog_edit.png" alt="${_('Admin')}" />
317 </span>
317 </span>
318 <span>${_('Admin')}</span>
318 <span>${_('Admin')}</span>
319 </a>
319 </a>
320 ${admin_menu()}
320 ${admin_menu()}
321 </li>
321 </li>
322 %endif
322 %endif
323 </ul>
323 </ul>
324 %endif
324 %endif
325 </%def>
325 </%def>
326
326
327
327
328 <%def name="css()">
328 <%def name="css()">
329 <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen" />
329 <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen" />
330 <link rel="stylesheet" type="text/css" href="/css/pygments.css" />
330 <link rel="stylesheet" type="text/css" href="/css/pygments.css" />
331 <link rel="stylesheet" type="text/css" href="/css/diff.css" />
331 <link rel="stylesheet" type="text/css" href="/css/diff.css" />
332 </%def>
332 </%def>
333
333
334 <%def name="js()">
334 <%def name="js()">
335 ##<script type="text/javascript" src="/js/yui/utilities/utilities.js"></script>
335 ##<script type="text/javascript" src="/js/yui/utilities/utilities.js"></script>
336 ##<script type="text/javascript" src="/js/yui/container/container.js"></script>
336 ##<script type="text/javascript" src="/js/yui/container/container.js"></script>
337 ##<script type="text/javascript" src="/js/yui/datasource/datasource.js"></script>
337 ##<script type="text/javascript" src="/js/yui/datasource/datasource.js"></script>
338 ##<script type="text/javascript" src="/js/yui/autocomplete/autocomplete.js"></script>
338 ##<script type="text/javascript" src="/js/yui/autocomplete/autocomplete.js"></script>
339 ##<script type="text/javascript" src="/js/yui/selector/selector-min.js"></script>
339 ##<script type="text/javascript" src="/js/yui/selector/selector-min.js"></script>
340
340
341 <script type="text/javascript" src="/js/yui2a.js"></script>
341 <script type="text/javascript" src="/js/yui2a.js"></script>
342 <!--[if IE]><script language="javascript" type="text/javascript" src="/js/excanvas.min.js"></script><![endif]-->
342 <!--[if IE]><script language="javascript" type="text/javascript" src="/js/excanvas.min.js"></script><![endif]-->
343 <script type="text/javascript" src="/js/yui.flot.js"></script>
343 <script type="text/javascript" src="/js/yui.flot.js"></script>
344
344
345 <script type="text/javascript">
345 <script type="text/javascript">
346 var base_url ='/_admin/toggle_following';
346 var base_url ='/_admin/toggle_following';
347 var YUC = YAHOO.util.Connect;
347 var YUC = YAHOO.util.Connect;
348 var YUD = YAHOO.util.Dom;
348 var YUD = YAHOO.util.Dom;
349 var YUE = YAHOO.util.Event;
349 var YUE = YAHOO.util.Event;
350
350
351 function onSuccess(target){
351 function onSuccess(target){
352
352
353 var f = YUD.get(target.id);
353 var f = YUD.get(target.id);
354 if(f.getAttribute('class')=='follow'){
354 if(f.getAttribute('class')=='follow'){
355 f.setAttribute('class','following');
355 f.setAttribute('class','following');
356 f.setAttribute('title',"${_('Stop following this repository')}");
356 f.setAttribute('title',"${_('Stop following this repository')}");
357 }
357 }
358 else{
358 else{
359 f.setAttribute('class','follow');
359 f.setAttribute('class','follow');
360 f.setAttribute('title',"${_('Start following this repository')}");
360 f.setAttribute('title',"${_('Start following this repository')}");
361 }
361 }
362 }
362 }
363
363
364 function toggleFollowingUser(fallows_user_id,token){
364 function toggleFollowingUser(fallows_user_id,token){
365 args = 'follows_user_id='+fallows_user_id;
365 args = 'follows_user_id='+fallows_user_id;
366 args+= '&amp;auth_token='+token;
366 args+= '&amp;auth_token='+token;
367 YUC.asyncRequest('POST',base_url,{
367 YUC.asyncRequest('POST',base_url,{
368 success:function(o){
368 success:function(o){
369 onSuccess();
369 onSuccess();
370 }
370 }
371 },args); return false;
371 },args); return false;
372 }
372 }
373
373
374 function toggleFollowingRepo(target,fallows_repo_id,token){
374 function toggleFollowingRepo(target,fallows_repo_id,token){
375
375
376 args = 'follows_repo_id='+fallows_repo_id;
376 args = 'follows_repo_id='+fallows_repo_id;
377 args+= '&amp;auth_token='+token;
377 args+= '&amp;auth_token='+token;
378 YUC.asyncRequest('POST',base_url,{
378 YUC.asyncRequest('POST',base_url,{
379 success:function(o){
379 success:function(o){
380 onSuccess(target);
380 onSuccess(target);
381 }
381 }
382 },args); return false;
382 },args); return false;
383 }
383 }
384 </script>
384 </script>
385
385
386 </%def>
386 </%def>
387
387
388 <%def name="breadcrumbs()">
388 <%def name="breadcrumbs()">
389 <div class="breadcrumbs">
389 <div class="breadcrumbs">
390 ${self.breadcrumbs_links()}
390 ${self.breadcrumbs_links()}
391 </div>
391 </div>
392 </%def> No newline at end of file
392 </%def>
@@ -1,168 +1,168 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base/base.html"/>
2 <%inherit file="base/base.html"/>
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Dashboard')} - ${c.rhodecode_name}
4 ${_('Dashboard')} - ${c.rhodecode_name}
5 </%def>
5 </%def>
6 <%def name="breadcrumbs()">
6 <%def name="breadcrumbs()">
7 ${c.rhodecode_name}
7 ${c.rhodecode_name}
8 </%def>
8 </%def>
9 <%def name="page_nav()">
9 <%def name="page_nav()">
10 ${self.menu('home')}
10 ${self.menu('home')}
11 </%def>
11 </%def>
12 <%def name="main()">
12 <%def name="main()">
13 <%def name="get_sort(name)">
13 <%def name="get_sort(name)">
14 <%name_slug = name.lower().replace(' ','_') %>
14 <%name_slug = name.lower().replace(' ','_') %>
15
15
16 %if name_slug == c.sort_slug:
16 %if name_slug == c.sort_slug:
17 %if c.sort_by.startswith('-'):
17 %if c.sort_by.startswith('-'):
18 <a href="?sort=${name_slug}">${name}&uarr;</a>
18 <a href="?sort=${name_slug}">${name}&uarr;</a>
19 %else:
19 %else:
20 <a href="?sort=-${name_slug}">${name}&darr;</a>
20 <a href="?sort=-${name_slug}">${name}&darr;</a>
21 %endif:
21 %endif:
22 %else:
22 %else:
23 <a href="?sort=${name_slug}">${name}</a>
23 <a href="?sort=${name_slug}">${name}</a>
24 %endif
24 %endif
25 </%def>
25 </%def>
26
26
27 <div class="box">
27 <div class="box">
28 <!-- box / title -->
28 <!-- box / title -->
29 <div class="title">
29 <div class="title">
30 <h5>${_('Dashboard')}
30 <h5>${_('Dashboard')}
31 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
31 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
32 </h5>
32 </h5>
33 %if c.rhodecode_user.username != 'default':
33 %if c.rhodecode_user.username != 'default':
34 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
34 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
35 <ul class="links">
35 <ul class="links">
36 <li>
36 <li>
37 <span>${h.link_to(_('ADD NEW REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
37 <span>${h.link_to(_('ADD NEW REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
38 </li>
38 </li>
39 </ul>
39 </ul>
40 %endif
40 %endif
41 %endif
41 %endif
42 </div>
42 </div>
43 <!-- end box / title -->
43 <!-- end box / title -->
44 <div class="table">
44 <div class="table">
45 <table>
45 <table>
46 <thead>
46 <thead>
47 <tr>
47 <tr>
48 <th class="left">${get_sort(_('Name'))}</th>
48 <th class="left">${get_sort(_('Name'))}</th>
49 <th class="left">${get_sort(_('Description'))}</th>
49 <th class="left">${get_sort(_('Description'))}</th>
50 <th class="left">${get_sort(_('Last change'))}</th>
50 <th class="left">${get_sort(_('Last change'))}</th>
51 <th class="left">${get_sort(_('Tip'))}</th>
51 <th class="left">${get_sort(_('Tip'))}</th>
52 <th class="left">${get_sort(_('Owner'))}</th>
52 <th class="left">${get_sort(_('Owner'))}</th>
53 <th class="left">${_('RSS')}</th>
53 <th class="left">${_('RSS')}</th>
54 <th class="left">${_('Atom')}</th>
54 <th class="left">${_('Atom')}</th>
55 </tr>
55 </tr>
56 </thead>
56 </thead>
57 <tbody>
57 <tbody>
58 %for cnt,repo in enumerate(c.repos_list):
58 %for cnt,repo in enumerate(c.repos_list):
59 <tr class="parity${cnt%2}">
59 <tr class="parity${cnt%2}">
60 <td>
60 <td>
61 <div style="white-space: nowrap">
61 <div style="white-space: nowrap">
62 ## TYPE OF REPO
62 ## TYPE OF REPO
63 %if repo['repo'].dbrepo.repo_type =='hg':
63 %if repo['dbrepo'].repo_type =='hg':
64 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="/images/icons/hgicon.png"/>
64 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="/images/icons/hgicon.png"/>
65 %elif repo['repo'].dbrepo.repo_type =='git':
65 %elif repo['dbrepo'].repo_type =='git':
66 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="/images/icons/giticon.png"/>
66 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="/images/icons/giticon.png"/>
67 %else:
67 %else:
68
68
69 %endif
69 %endif
70
70
71 ##PRIVATE/PUBLIC
71 ##PRIVATE/PUBLIC
72 %if repo['repo'].dbrepo.private:
72 %if repo['dbrepo'].private:
73 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="/images/icons/lock.png"/>
73 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="/images/icons/lock.png"/>
74 %else:
74 %else:
75 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="/images/icons/lock_open.png"/>
75 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="/images/icons/lock_open.png"/>
76 %endif
76 %endif
77
77
78 ##NAME
78 ##NAME
79 ${h.link_to(repo['name'],
79 ${h.link_to(repo['name'],
80 h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
80 h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
81 %if repo['repo'].dbrepo.fork:
81 %if repo['dbrepo'].fork:
82 <a href="${h.url('summary_home',repo_name=repo['repo'].dbrepo.fork.repo_name)}">
82 <a href="${h.url('summary_home',repo_name=repo['dbrepo'].fork.repo_name)}">
83 <img class="icon" alt="${_('fork')}"
83 <img class="icon" alt="${_('fork')}"
84 title="${_('Fork of')} ${repo['repo'].dbrepo.fork.repo_name}"
84 title="${_('Fork of')} ${repo['dbrepo'].fork.repo_name}"
85 src="/images/icons/arrow_divide.png"/></a>
85 src="/images/icons/arrow_divide.png"/></a>
86 %endif
86 %endif
87 </div>
87 </div>
88 </td>
88 </td>
89 ##DESCRIPTION
89 ##DESCRIPTION
90 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
90 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
91 ${h.truncate(repo['description'],60)}</span>
91 ${h.truncate(repo['description'],60)}</span>
92 </td>
92 </td>
93 ##LAST CHANGE
93 ##LAST CHANGE
94 <td>
94 <td>
95 <span class="tooltip" title="${repo['last_change']}">
95 <span class="tooltip" title="${repo['last_change']}">
96 ${h.age(repo['last_change'])}</span>
96 ${h.age(repo['last_change'])}</span>
97 </td>
97 </td>
98 <td>
98 <td>
99 %if repo['rev']>=0:
99 %if repo['rev']>=0:
100 ${h.link_to('r%s:%s' % (repo['rev'],h.short_id(repo['tip'])),
100 ${h.link_to('r%s:%s' % (repo['rev'],h.short_id(repo['tip'])),
101 h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']),
101 h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']),
102 class_="tooltip",
102 class_="tooltip",
103 title=h.tooltip(repo['last_msg']))}
103 title=h.tooltip(repo['last_msg']))}
104 %else:
104 %else:
105 ${_('No changesets yet')}
105 ${_('No changesets yet')}
106 %endif
106 %endif
107 </td>
107 </td>
108 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
108 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
109 <td>
109 <td>
110 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
110 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
111 </td>
111 </td>
112 <td>
112 <td>
113 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
113 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
114 </td>
114 </td>
115 </tr>
115 </tr>
116 %endfor
116 %endfor
117 </tbody>
117 </tbody>
118 </table>
118 </table>
119 </div>
119 </div>
120 </div>
120 </div>
121
121
122
122
123 <script type="text/javascript">
123 <script type="text/javascript">
124 var D = YAHOO.util.Dom;
124 var D = YAHOO.util.Dom;
125 var E = YAHOO.util.Event;
125 var E = YAHOO.util.Event;
126 var S = YAHOO.util.Selector;
126 var S = YAHOO.util.Selector;
127
127
128 var q_filter = D.get('q_filter');
128 var q_filter = D.get('q_filter');
129 var F = YAHOO.namespace('q_filter');
129 var F = YAHOO.namespace('q_filter');
130
130
131 E.on(q_filter,'click',function(){
131 E.on(q_filter,'click',function(){
132 q_filter.value = '';
132 q_filter.value = '';
133 });
133 });
134
134
135 F.filterTimeout = null;
135 F.filterTimeout = null;
136
136
137 F.updateFilter = function() {
137 F.updateFilter = function() {
138 // Reset timeout
138 // Reset timeout
139 F.filterTimeout = null;
139 F.filterTimeout = null;
140
140
141 var obsolete = [];
141 var obsolete = [];
142 var nodes = S.query('div.table tr td div a.repo_name');
142 var nodes = S.query('div.table tr td div a.repo_name');
143 var req = D.get('q_filter').value;
143 var req = D.get('q_filter').value;
144 for (n in nodes){
144 for (n in nodes){
145 D.setStyle(nodes[n].parentNode.parentNode.parentNode,'display','')
145 D.setStyle(nodes[n].parentNode.parentNode.parentNode,'display','')
146 }
146 }
147 if (req){
147 if (req){
148 for (n in nodes){
148 for (n in nodes){
149 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
149 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
150 obsolete.push(nodes[n]);
150 obsolete.push(nodes[n]);
151 }
151 }
152 }
152 }
153 if(obsolete){
153 if(obsolete){
154 for (n in obsolete){
154 for (n in obsolete){
155 D.setStyle(obsolete[n].parentNode.parentNode.parentNode,'display','none');
155 D.setStyle(obsolete[n].parentNode.parentNode.parentNode,'display','none');
156 }
156 }
157 }
157 }
158 }
158 }
159 }
159 }
160
160
161 E.on(q_filter,'keyup',function(e){
161 E.on(q_filter,'keyup',function(e){
162 clearTimeout(F.filterTimeout);
162 clearTimeout(F.filterTimeout);
163 setTimeout(F.updateFilter,600);
163 setTimeout(F.updateFilter,600);
164 });
164 });
165
165
166 </script>
166 </script>
167
167
168 </%def>
168 </%def>
@@ -1,674 +1,674 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(u'Home',h.url('/'))}
8 ${h.link_to(u'Home',h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('summary')}
12 ${_('summary')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('summary')}
16 ${self.menu('summary')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box box-left">
20 <div class="box box-left">
21 <!-- box / title -->
21 <!-- box / title -->
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25 <!-- end box / title -->
25 <!-- end box / title -->
26 <div class="form">
26 <div class="form">
27 <div class="fields">
27 <div class="fields">
28
28
29 <div class="field">
29 <div class="field">
30 <div class="label">
30 <div class="label">
31 <label>${_('Name')}:</label>
31 <label>${_('Name')}:</label>
32 </div>
32 </div>
33 <div class="input-short">
33 <div class="input-short">
34 %if c.repo_info.dbrepo.repo_type =='hg':
34 %if c.dbrepo.repo_type =='hg':
35 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="/images/icons/hgicon.png"/>
35 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="/images/icons/hgicon.png"/>
36 %endif
36 %endif
37 %if c.repo_info.dbrepo.repo_type =='git':
37 %if c.dbrepo.repo_type =='git':
38 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="/images/icons/giticon.png"/>
38 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="/images/icons/giticon.png"/>
39 %endif
39 %endif
40
40
41 %if c.repo_info.dbrepo.private:
41 %if c.dbrepo.private:
42 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="/images/icons/lock.png"/>
42 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="/images/icons/lock.png"/>
43 %else:
43 %else:
44 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="/images/icons/lock_open.png"/>
44 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="/images/icons/lock_open.png"/>
45 %endif
45 %endif
46 <span style="font-size: 1.6em;font-weight: bold;vertical-align: baseline;">${c.repo_info.name}</span>
46 <span style="font-size: 1.6em;font-weight: bold;vertical-align: baseline;">${c.repo.name}</span>
47 %if c.rhodecode_user.username != 'default':
47 %if c.rhodecode_user.username != 'default':
48 %if c.following:
48 %if c.following:
49 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
49 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
50 onclick="javascript:toggleFollowingRepo(this,${c.repo_info.dbrepo.repo_id},'${str(h.get_token())}')">
50 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
51 </span>
51 </span>
52 %else:
52 %else:
53 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
53 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
54 onclick="javascript:toggleFollowingRepo(this,${c.repo_info.dbrepo.repo_id},'${str(h.get_token())}')">
54 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
55 </span>
55 </span>
56 %endif
56 %endif
57 %endif:
57 %endif:
58 <br/>
58 <br/>
59 %if c.repo_info.dbrepo.fork:
59 %if c.dbrepo.fork:
60 <span style="margin-top:5px">
60 <span style="margin-top:5px">
61 <a href="${h.url('summary_home',repo_name=c.repo_info.dbrepo.fork.repo_name)}">
61 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
62 <img class="icon" alt="${_('public')}"
62 <img class="icon" alt="${_('public')}"
63 title="${_('Fork of')} ${c.repo_info.dbrepo.fork.repo_name}"
63 title="${_('Fork of')} ${c.dbrepo.fork.repo_name}"
64 src="/images/icons/arrow_divide.png"/>
64 src="/images/icons/arrow_divide.png"/>
65 ${_('Fork of')} ${c.repo_info.dbrepo.fork.repo_name}
65 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
66 </a>
66 </a>
67 </span>
67 </span>
68 %endif
68 %endif
69 </div>
69 </div>
70 </div>
70 </div>
71
71
72
72
73 <div class="field">
73 <div class="field">
74 <div class="label">
74 <div class="label">
75 <label>${_('Description')}:</label>
75 <label>${_('Description')}:</label>
76 </div>
76 </div>
77 <div class="input-short">
77 <div class="input-short">
78 ${c.repo_info.dbrepo.description}
78 ${c.dbrepo.description}
79 </div>
79 </div>
80 </div>
80 </div>
81
81
82
82
83 <div class="field">
83 <div class="field">
84 <div class="label">
84 <div class="label">
85 <label>${_('Contact')}:</label>
85 <label>${_('Contact')}:</label>
86 </div>
86 </div>
87 <div class="input-short">
87 <div class="input-short">
88 <div class="gravatar">
88 <div class="gravatar">
89 <img alt="gravatar" src="${h.gravatar_url(c.repo_info.dbrepo.user.email)}"/>
89 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
90 </div>
90 </div>
91 ${_('Username')}: ${c.repo_info.dbrepo.user.username}<br/>
91 ${_('Username')}: ${c.dbrepo.user.username}<br/>
92 ${_('Name')}: ${c.repo_info.dbrepo.user.name} ${c.repo_info.dbrepo.user.lastname}<br/>
92 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
93 ${_('Email')}: <a href="mailto:${c.repo_info.dbrepo.user.email}">${c.repo_info.dbrepo.user.email}</a>
93 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
94 </div>
94 </div>
95 </div>
95 </div>
96
96
97 <div class="field">
97 <div class="field">
98 <div class="label">
98 <div class="label">
99 <label>${_('Last change')}:</label>
99 <label>${_('Last change')}:</label>
100 </div>
100 </div>
101 <div class="input-short">
101 <div class="input-short">
102 ${h.age(c.repo_info.last_change)} - ${c.repo_info.last_change}
102 ${h.age(c.repo.last_change)} - ${c.repo.last_change}
103 ${_('by')} ${h.get_changeset_safe(c.repo_info,'tip').author}
103 ${_('by')} ${h.get_changeset_safe(c.repo,'tip').author}
104
104
105 </div>
105 </div>
106 </div>
106 </div>
107
107
108 <div class="field">
108 <div class="field">
109 <div class="label">
109 <div class="label">
110 <label>${_('Clone url')}:</label>
110 <label>${_('Clone url')}:</label>
111 </div>
111 </div>
112 <div class="input-short">
112 <div class="input-short">
113 <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/>
113 <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/>
114 </div>
114 </div>
115 </div>
115 </div>
116
116
117 <div class="field">
117 <div class="field">
118 <div class="label">
118 <div class="label">
119 <label>${_('Trending source files')}:</label>
119 <label>${_('Trending source files')}:</label>
120 </div>
120 </div>
121 <div class="input-short">
121 <div class="input-short">
122 <div id="lang_stats"></div>
122 <div id="lang_stats"></div>
123 </div>
123 </div>
124 </div>
124 </div>
125
125
126 <div class="field">
126 <div class="field">
127 <div class="label">
127 <div class="label">
128 <label>${_('Download')}:</label>
128 <label>${_('Download')}:</label>
129 </div>
129 </div>
130 <div class="input-short">
130 <div class="input-short">
131 %if len(c.repo_info.revisions) == 0:
131 %if len(c.repo.revisions) == 0:
132 ${_('There are no downloads yet')}
132 ${_('There are no downloads yet')}
133 %elif c.enable_downloads is False:
133 %elif c.enable_downloads is False:
134 ${_('Downloads are disabled for this repository')}
134 ${_('Downloads are disabled for this repository')}
135 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
135 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
136 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
136 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
137 %endif
137 %endif
138 %else:
138 %else:
139 ${h.select('download_options',c.repo_info.get_changeset().raw_id,c.download_options)}
139 ${h.select('download_options',c.repo.get_changeset().raw_id,c.download_options)}
140 %for cnt,archive in enumerate(c.repo_info._get_archives()):
140 %for cnt,archive in enumerate(c.repo._get_archives()):
141 %if cnt >=1:
141 %if cnt >=1:
142 |
142 |
143 %endif
143 %endif
144 <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
144 <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
145 id="${archive['type']+'_link'}">${h.link_to(archive['type'],
145 id="${archive['type']+'_link'}">${h.link_to(archive['type'],
146 h.url('files_archive_home',repo_name=c.repo_info.name,
146 h.url('files_archive_home',repo_name=c.repo.name,
147 fname='tip'+archive['extension']),class_="archive_icon")}</span>
147 fname='tip'+archive['extension']),class_="archive_icon")}</span>
148 %endfor
148 %endfor
149 %endif
149 %endif
150 </div>
150 </div>
151 </div>
151 </div>
152
152
153 <div class="field">
153 <div class="field">
154 <div class="label">
154 <div class="label">
155 <label>${_('Feeds')}:</label>
155 <label>${_('Feeds')}:</label>
156 </div>
156 </div>
157 <div class="input-short">
157 <div class="input-short">
158 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo_info.name),class_='rss_icon')}
158 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo.name),class_='rss_icon')}
159 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo_info.name),class_='atom_icon')}
159 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo.name),class_='atom_icon')}
160 </div>
160 </div>
161 </div>
161 </div>
162 </div>
162 </div>
163 </div>
163 </div>
164 <script type="text/javascript">
164 <script type="text/javascript">
165 YUE.onDOMReady(function(e){
165 YUE.onDOMReady(function(e){
166 id = 'clone_url';
166 id = 'clone_url';
167 YUE.on(id,'click',function(e){
167 YUE.on(id,'click',function(e){
168 YUD.get('clone_url').select();
168 YUD.get('clone_url').select();
169 })
169 })
170 })
170 })
171 var data = ${c.trending_languages|n};
171 var data = ${c.trending_languages|n};
172 var total = 0;
172 var total = 0;
173 var no_data = true;
173 var no_data = true;
174 for (k in data){
174 for (k in data){
175 total += data[k];
175 total += data[k];
176 no_data = false;
176 no_data = false;
177 }
177 }
178 var tbl = document.createElement('table');
178 var tbl = document.createElement('table');
179 tbl.setAttribute('class','trending_language_tbl');
179 tbl.setAttribute('class','trending_language_tbl');
180 var cnt =0;
180 var cnt =0;
181 for (k in data){
181 for (k in data){
182 cnt+=1;
182 cnt+=1;
183 var hide = cnt>2;
183 var hide = cnt>2;
184 var tr = document.createElement('tr');
184 var tr = document.createElement('tr');
185 if (hide){
185 if (hide){
186 tr.setAttribute('style','display:none');
186 tr.setAttribute('style','display:none');
187 tr.setAttribute('class','stats_hidden');
187 tr.setAttribute('class','stats_hidden');
188 }
188 }
189 var percentage = Math.round((data[k]/total*100),2);
189 var percentage = Math.round((data[k]/total*100),2);
190 var value = data[k];
190 var value = data[k];
191 var td1 = document.createElement('td');
191 var td1 = document.createElement('td');
192 td1.width=150;
192 td1.width=150;
193 var trending_language_label = document.createElement('div');
193 var trending_language_label = document.createElement('div');
194 trending_language_label.innerHTML = k;
194 trending_language_label.innerHTML = k;
195 td1.appendChild(trending_language_label);
195 td1.appendChild(trending_language_label);
196
196
197 var td2 = document.createElement('td');
197 var td2 = document.createElement('td');
198 td2.setAttribute('style','padding-right:14px !important');
198 td2.setAttribute('style','padding-right:14px !important');
199 var trending_language = document.createElement('div');
199 var trending_language = document.createElement('div');
200 var nr_files = value+" ${_('files')}";
200 var nr_files = value+" ${_('files')}";
201
201
202 trending_language.title = k+" "+nr_files;
202 trending_language.title = k+" "+nr_files;
203
203
204 if (percentage>20){
204 if (percentage>20){
205 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
205 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
206 }
206 }
207 else{
207 else{
208 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
208 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
209 }
209 }
210
210
211 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
211 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
212 trending_language.style.width=percentage+"%";
212 trending_language.style.width=percentage+"%";
213 td2.appendChild(trending_language);
213 td2.appendChild(trending_language);
214
214
215 tr.appendChild(td1);
215 tr.appendChild(td1);
216 tr.appendChild(td2);
216 tr.appendChild(td2);
217 tbl.appendChild(tr);
217 tbl.appendChild(tr);
218 if(cnt == 2){
218 if(cnt == 2){
219 var show_more = document.createElement('tr');
219 var show_more = document.createElement('tr');
220 var td=document.createElement('td');
220 var td=document.createElement('td');
221 lnk = document.createElement('a');
221 lnk = document.createElement('a');
222 lnk.href='#';
222 lnk.href='#';
223 lnk.innerHTML = "${_("show more")}";
223 lnk.innerHTML = "${_("show more")}";
224 lnk.id='code_stats_show_more';
224 lnk.id='code_stats_show_more';
225 td.appendChild(lnk);
225 td.appendChild(lnk);
226 show_more.appendChild(td);
226 show_more.appendChild(td);
227 show_more.appendChild(document.createElement('td'));
227 show_more.appendChild(document.createElement('td'));
228 tbl.appendChild(show_more);
228 tbl.appendChild(show_more);
229 }
229 }
230
230
231 }
231 }
232 if(no_data){
232 if(no_data){
233 var tr = document.createElement('tr');
233 var tr = document.createElement('tr');
234 var td1 = document.createElement('td');
234 var td1 = document.createElement('td');
235 td1.innerHTML = "${c.no_data_msg}";
235 td1.innerHTML = "${c.no_data_msg}";
236 tr.appendChild(td1);
236 tr.appendChild(td1);
237 tbl.appendChild(tr);
237 tbl.appendChild(tr);
238 }
238 }
239 YUD.get('lang_stats').appendChild(tbl);
239 YUD.get('lang_stats').appendChild(tbl);
240 YUE.on('code_stats_show_more','click',function(){
240 YUE.on('code_stats_show_more','click',function(){
241 l = YUD.getElementsByClassName('stats_hidden')
241 l = YUD.getElementsByClassName('stats_hidden')
242 for (e in l){
242 for (e in l){
243 YUD.setStyle(l[e],'display','');
243 YUD.setStyle(l[e],'display','');
244 };
244 };
245 YUD.setStyle(YUD.get('code_stats_show_more'),
245 YUD.setStyle(YUD.get('code_stats_show_more'),
246 'display','none');
246 'display','none');
247 })
247 })
248
248
249
249
250 YUE.on('download_options','change',function(e){
250 YUE.on('download_options','change',function(e){
251 var new_cs = e.target.options[e.target.selectedIndex];
251 var new_cs = e.target.options[e.target.selectedIndex];
252 var tmpl_links = {}
252 var tmpl_links = {}
253 %for cnt,archive in enumerate(c.repo_info._get_archives()):
253 %for cnt,archive in enumerate(c.repo._get_archives()):
254 tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
254 tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
255 h.url('files_archive_home',repo_name=c.repo_info.name,
255 h.url('files_archive_home',repo_name=c.repo.name,
256 fname='__CS__'+archive['extension']),class_="archive_icon")}';
256 fname='__CS__'+archive['extension']),class_="archive_icon")}';
257 %endfor
257 %endfor
258
258
259
259
260 for(k in tmpl_links){
260 for(k in tmpl_links){
261 var s = YUD.get(k+'_link')
261 var s = YUD.get(k+'_link')
262 title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__',archive['type'])}";
262 title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__',archive['type'])}";
263 s.title = title_tmpl.replace('__CS_NAME__',new_cs.text)
263 s.title = title_tmpl.replace('__CS_NAME__',new_cs.text)
264 s.innerHTML = tmpl_links[k].replace('__CS__',new_cs.value);
264 s.innerHTML = tmpl_links[k].replace('__CS__',new_cs.value);
265 }
265 }
266
266
267 })
267 })
268
268
269 </script>
269 </script>
270 </div>
270 </div>
271
271
272 <div class="box box-right" style="min-height:455px">
272 <div class="box box-right" style="min-height:455px">
273 <!-- box / title -->
273 <!-- box / title -->
274 <div class="title">
274 <div class="title">
275 <h5>${_('Commit activity by day / author')}</h5>
275 <h5>${_('Commit activity by day / author')}</h5>
276 </div>
276 </div>
277
277
278 <div class="table">
278 <div class="table">
279 %if c.no_data:
279 %if c.no_data:
280 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">${c.no_data_msg}
280 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">${c.no_data_msg}
281 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
281 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
282 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
282 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
283 %endif
283 %endif
284 </div>
284 </div>
285 %endif:
285 %endif:
286 <div id="commit_history" style="width:460px;height:300px;float:left"></div>
286 <div id="commit_history" style="width:460px;height:300px;float:left"></div>
287 <div style="clear: both;height: 10px"></div>
287 <div style="clear: both;height: 10px"></div>
288 <div id="overview" style="width:460px;height:100px;float:left"></div>
288 <div id="overview" style="width:460px;height:100px;float:left"></div>
289
289
290 <div id="legend_data" style="clear:both;margin-top:10px;">
290 <div id="legend_data" style="clear:both;margin-top:10px;">
291 <div id="legend_container"></div>
291 <div id="legend_container"></div>
292 <div id="legend_choices">
292 <div id="legend_choices">
293 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
293 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
294 </div>
294 </div>
295 </div>
295 </div>
296 <script type="text/javascript">
296 <script type="text/javascript">
297 /**
297 /**
298 * Plots summary graph
298 * Plots summary graph
299 *
299 *
300 * @class SummaryPlot
300 * @class SummaryPlot
301 * @param {from} initial from for detailed graph
301 * @param {from} initial from for detailed graph
302 * @param {to} initial to for detailed graph
302 * @param {to} initial to for detailed graph
303 * @param {dataset}
303 * @param {dataset}
304 * @param {overview_dataset}
304 * @param {overview_dataset}
305 */
305 */
306 function SummaryPlot(from,to,dataset,overview_dataset) {
306 function SummaryPlot(from,to,dataset,overview_dataset) {
307 var initial_ranges = {
307 var initial_ranges = {
308 "xaxis":{
308 "xaxis":{
309 "from":from,
309 "from":from,
310 "to":to,
310 "to":to,
311 },
311 },
312 };
312 };
313 var dataset = dataset;
313 var dataset = dataset;
314 var overview_dataset = [overview_dataset];
314 var overview_dataset = [overview_dataset];
315 var choiceContainer = YUD.get("legend_choices");
315 var choiceContainer = YUD.get("legend_choices");
316 var choiceContainerTable = YUD.get("legend_choices_tables");
316 var choiceContainerTable = YUD.get("legend_choices_tables");
317 var plotContainer = YUD.get('commit_history');
317 var plotContainer = YUD.get('commit_history');
318 var overviewContainer = YUD.get('overview');
318 var overviewContainer = YUD.get('overview');
319
319
320 var plot_options = {
320 var plot_options = {
321 bars: {show:true,align:'center',lineWidth:4},
321 bars: {show:true,align:'center',lineWidth:4},
322 legend: {show:true, container:"legend_container"},
322 legend: {show:true, container:"legend_container"},
323 points: {show:true,radius:0,fill:false},
323 points: {show:true,radius:0,fill:false},
324 yaxis: {tickDecimals:0,},
324 yaxis: {tickDecimals:0,},
325 xaxis: {
325 xaxis: {
326 mode: "time",
326 mode: "time",
327 timeformat: "%d/%m",
327 timeformat: "%d/%m",
328 min:from,
328 min:from,
329 max:to,
329 max:to,
330 },
330 },
331 grid: {
331 grid: {
332 hoverable: true,
332 hoverable: true,
333 clickable: true,
333 clickable: true,
334 autoHighlight:true,
334 autoHighlight:true,
335 color: "#999"
335 color: "#999"
336 },
336 },
337 //selection: {mode: "x"}
337 //selection: {mode: "x"}
338 };
338 };
339 var overview_options = {
339 var overview_options = {
340 legend:{show:false},
340 legend:{show:false},
341 bars: {show:true,barWidth: 2,},
341 bars: {show:true,barWidth: 2,},
342 shadowSize: 0,
342 shadowSize: 0,
343 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
343 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
344 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
344 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
345 grid: {color: "#999",},
345 grid: {color: "#999",},
346 selection: {mode: "x"}
346 selection: {mode: "x"}
347 };
347 };
348
348
349 /**
349 /**
350 *get dummy data needed in few places
350 *get dummy data needed in few places
351 */
351 */
352 function getDummyData(label){
352 function getDummyData(label){
353 return {"label":label,
353 return {"label":label,
354 "data":[{"time":0,
354 "data":[{"time":0,
355 "commits":0,
355 "commits":0,
356 "added":0,
356 "added":0,
357 "changed":0,
357 "changed":0,
358 "removed":0,
358 "removed":0,
359 }],
359 }],
360 "schema":["commits"],
360 "schema":["commits"],
361 "color":'#ffffff',
361 "color":'#ffffff',
362 }
362 }
363 }
363 }
364
364
365 /**
365 /**
366 * generate checkboxes accordindly to data
366 * generate checkboxes accordindly to data
367 * @param keys
367 * @param keys
368 * @returns
368 * @returns
369 */
369 */
370 function generateCheckboxes(data) {
370 function generateCheckboxes(data) {
371 //append checkboxes
371 //append checkboxes
372 var i = 0;
372 var i = 0;
373 choiceContainerTable.innerHTML = '';
373 choiceContainerTable.innerHTML = '';
374 for(var pos in data) {
374 for(var pos in data) {
375
375
376 data[pos].color = i;
376 data[pos].color = i;
377 i++;
377 i++;
378 if(data[pos].label != ''){
378 if(data[pos].label != ''){
379 choiceContainerTable.innerHTML += '<tr><td>'+
379 choiceContainerTable.innerHTML += '<tr><td>'+
380 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
380 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
381 +data[pos].label+
381 +data[pos].label+
382 '</td></tr>';
382 '</td></tr>';
383 }
383 }
384 }
384 }
385 }
385 }
386
386
387 /**
387 /**
388 * ToolTip show
388 * ToolTip show
389 */
389 */
390 function showTooltip(x, y, contents) {
390 function showTooltip(x, y, contents) {
391 var div=document.getElementById('tooltip');
391 var div=document.getElementById('tooltip');
392 if(!div) {
392 if(!div) {
393 div = document.createElement('div');
393 div = document.createElement('div');
394 div.id="tooltip";
394 div.id="tooltip";
395 div.style.position="absolute";
395 div.style.position="absolute";
396 div.style.border='1px solid #fdd';
396 div.style.border='1px solid #fdd';
397 div.style.padding='2px';
397 div.style.padding='2px';
398 div.style.backgroundColor='#fee';
398 div.style.backgroundColor='#fee';
399 document.body.appendChild(div);
399 document.body.appendChild(div);
400 }
400 }
401 YUD.setStyle(div, 'opacity', 0);
401 YUD.setStyle(div, 'opacity', 0);
402 div.innerHTML = contents;
402 div.innerHTML = contents;
403 div.style.top=(y + 5) + "px";
403 div.style.top=(y + 5) + "px";
404 div.style.left=(x + 5) + "px";
404 div.style.left=(x + 5) + "px";
405
405
406 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
406 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
407 anim.animate();
407 anim.animate();
408 }
408 }
409
409
410 /**
410 /**
411 * This function will detect if selected period has some changesets
411 * This function will detect if selected period has some changesets
412 for this user if it does this data is then pushed for displaying
412 for this user if it does this data is then pushed for displaying
413 Additionally it will only display users that are selected by the checkbox
413 Additionally it will only display users that are selected by the checkbox
414 */
414 */
415 function getDataAccordingToRanges(ranges) {
415 function getDataAccordingToRanges(ranges) {
416
416
417 var data = [];
417 var data = [];
418 var keys = [];
418 var keys = [];
419 for(var key in dataset){
419 for(var key in dataset){
420 var push = false;
420 var push = false;
421
421
422 //method1 slow !!
422 //method1 slow !!
423 //*
423 //*
424 for(var ds in dataset[key].data){
424 for(var ds in dataset[key].data){
425 commit_data = dataset[key].data[ds];
425 commit_data = dataset[key].data[ds];
426 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
426 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
427 push = true;
427 push = true;
428 break;
428 break;
429 }
429 }
430 }
430 }
431 //*/
431 //*/
432
432
433 /*//method2 sorted commit data !!!
433 /*//method2 sorted commit data !!!
434
434
435 var first_commit = dataset[key].data[0].time;
435 var first_commit = dataset[key].data[0].time;
436 var last_commit = dataset[key].data[dataset[key].data.length-1].time;
436 var last_commit = dataset[key].data[dataset[key].data.length-1].time;
437
437
438 if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){
438 if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){
439 push = true;
439 push = true;
440 }
440 }
441 //*/
441 //*/
442
442
443 if(push){
443 if(push){
444 data.push(dataset[key]);
444 data.push(dataset[key]);
445 }
445 }
446 }
446 }
447 if(data.length >= 1){
447 if(data.length >= 1){
448 return data;
448 return data;
449 }
449 }
450 else{
450 else{
451 //just return dummy data for graph to plot itself
451 //just return dummy data for graph to plot itself
452 return [getDummyData('')];
452 return [getDummyData('')];
453 }
453 }
454
454
455 }
455 }
456
456
457 /**
457 /**
458 * redraw using new checkbox data
458 * redraw using new checkbox data
459 */
459 */
460 function plotchoiced(e,args){
460 function plotchoiced(e,args){
461 var cur_data = args[0];
461 var cur_data = args[0];
462 var cur_ranges = args[1];
462 var cur_ranges = args[1];
463
463
464 var new_data = [];
464 var new_data = [];
465 var inputs = choiceContainer.getElementsByTagName("input");
465 var inputs = choiceContainer.getElementsByTagName("input");
466
466
467 //show only checked labels
467 //show only checked labels
468 for(var i=0; i<inputs.length; i++) {
468 for(var i=0; i<inputs.length; i++) {
469 var checkbox_key = inputs[i].name;
469 var checkbox_key = inputs[i].name;
470
470
471 if(inputs[i].checked){
471 if(inputs[i].checked){
472 for(var d in cur_data){
472 for(var d in cur_data){
473 if(cur_data[d].label == checkbox_key){
473 if(cur_data[d].label == checkbox_key){
474 new_data.push(cur_data[d]);
474 new_data.push(cur_data[d]);
475 }
475 }
476 }
476 }
477 }
477 }
478 else{
478 else{
479 //push dummy data to not hide the label
479 //push dummy data to not hide the label
480 new_data.push(getDummyData(checkbox_key));
480 new_data.push(getDummyData(checkbox_key));
481 }
481 }
482 }
482 }
483
483
484 var new_options = YAHOO.lang.merge(plot_options, {
484 var new_options = YAHOO.lang.merge(plot_options, {
485 xaxis: {
485 xaxis: {
486 min: cur_ranges.xaxis.from,
486 min: cur_ranges.xaxis.from,
487 max: cur_ranges.xaxis.to,
487 max: cur_ranges.xaxis.to,
488 mode:"time",
488 mode:"time",
489 timeformat: "%d/%m",
489 timeformat: "%d/%m",
490 },
490 },
491 });
491 });
492 if (!new_data){
492 if (!new_data){
493 new_data = [[0,1]];
493 new_data = [[0,1]];
494 }
494 }
495 // do the zooming
495 // do the zooming
496 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
496 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
497
497
498 plot.subscribe("plotselected", plotselected);
498 plot.subscribe("plotselected", plotselected);
499
499
500 //resubscribe plothover
500 //resubscribe plothover
501 plot.subscribe("plothover", plothover);
501 plot.subscribe("plothover", plothover);
502
502
503 // don't fire event on the overview to prevent eternal loop
503 // don't fire event on the overview to prevent eternal loop
504 overview.setSelection(cur_ranges, true);
504 overview.setSelection(cur_ranges, true);
505
505
506 }
506 }
507
507
508 /**
508 /**
509 * plot only selected items from overview
509 * plot only selected items from overview
510 * @param ranges
510 * @param ranges
511 * @returns
511 * @returns
512 */
512 */
513 function plotselected(ranges,cur_data) {
513 function plotselected(ranges,cur_data) {
514 //updates the data for new plot
514 //updates the data for new plot
515 data = getDataAccordingToRanges(ranges);
515 data = getDataAccordingToRanges(ranges);
516 generateCheckboxes(data);
516 generateCheckboxes(data);
517
517
518 var new_options = YAHOO.lang.merge(plot_options, {
518 var new_options = YAHOO.lang.merge(plot_options, {
519 xaxis: {
519 xaxis: {
520 min: ranges.xaxis.from,
520 min: ranges.xaxis.from,
521 max: ranges.xaxis.to,
521 max: ranges.xaxis.to,
522 mode:"time",
522 mode:"time",
523 timeformat: "%d/%m",
523 timeformat: "%d/%m",
524 },
524 },
525 yaxis: {
525 yaxis: {
526 min: ranges.yaxis.from,
526 min: ranges.yaxis.from,
527 max: ranges.yaxis.to,
527 max: ranges.yaxis.to,
528 },
528 },
529
529
530 });
530 });
531 // do the zooming
531 // do the zooming
532 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
532 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
533
533
534 plot.subscribe("plotselected", plotselected);
534 plot.subscribe("plotselected", plotselected);
535
535
536 //resubscribe plothover
536 //resubscribe plothover
537 plot.subscribe("plothover", plothover);
537 plot.subscribe("plothover", plothover);
538
538
539 // don't fire event on the overview to prevent eternal loop
539 // don't fire event on the overview to prevent eternal loop
540 overview.setSelection(ranges, true);
540 overview.setSelection(ranges, true);
541
541
542 //resubscribe choiced
542 //resubscribe choiced
543 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
543 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
544 }
544 }
545
545
546 var previousPoint = null;
546 var previousPoint = null;
547
547
548 function plothover(o) {
548 function plothover(o) {
549 var pos = o.pos;
549 var pos = o.pos;
550 var item = o.item;
550 var item = o.item;
551
551
552 //YUD.get("x").innerHTML = pos.x.toFixed(2);
552 //YUD.get("x").innerHTML = pos.x.toFixed(2);
553 //YUD.get("y").innerHTML = pos.y.toFixed(2);
553 //YUD.get("y").innerHTML = pos.y.toFixed(2);
554 if (item) {
554 if (item) {
555 if (previousPoint != item.datapoint) {
555 if (previousPoint != item.datapoint) {
556 previousPoint = item.datapoint;
556 previousPoint = item.datapoint;
557
557
558 var tooltip = YUD.get("tooltip");
558 var tooltip = YUD.get("tooltip");
559 if(tooltip) {
559 if(tooltip) {
560 tooltip.parentNode.removeChild(tooltip);
560 tooltip.parentNode.removeChild(tooltip);
561 }
561 }
562 var x = item.datapoint.x.toFixed(2);
562 var x = item.datapoint.x.toFixed(2);
563 var y = item.datapoint.y.toFixed(2);
563 var y = item.datapoint.y.toFixed(2);
564
564
565 if (!item.series.label){
565 if (!item.series.label){
566 item.series.label = 'commits';
566 item.series.label = 'commits';
567 }
567 }
568 var d = new Date(x*1000);
568 var d = new Date(x*1000);
569 var fd = d.toDateString()
569 var fd = d.toDateString()
570 var nr_commits = parseInt(y);
570 var nr_commits = parseInt(y);
571
571
572 var cur_data = dataset[item.series.label].data[item.dataIndex];
572 var cur_data = dataset[item.series.label].data[item.dataIndex];
573 var added = cur_data.added;
573 var added = cur_data.added;
574 var changed = cur_data.changed;
574 var changed = cur_data.changed;
575 var removed = cur_data.removed;
575 var removed = cur_data.removed;
576
576
577 var nr_commits_suffix = " ${_('commits')} ";
577 var nr_commits_suffix = " ${_('commits')} ";
578 var added_suffix = " ${_('files added')} ";
578 var added_suffix = " ${_('files added')} ";
579 var changed_suffix = " ${_('files changed')} ";
579 var changed_suffix = " ${_('files changed')} ";
580 var removed_suffix = " ${_('files removed')} ";
580 var removed_suffix = " ${_('files removed')} ";
581
581
582
582
583 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
583 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
584 if(added==1){added_suffix=" ${_('file added')} ";}
584 if(added==1){added_suffix=" ${_('file added')} ";}
585 if(changed==1){changed_suffix=" ${_('file changed')} ";}
585 if(changed==1){changed_suffix=" ${_('file changed')} ";}
586 if(removed==1){removed_suffix=" ${_('file removed')} ";}
586 if(removed==1){removed_suffix=" ${_('file removed')} ";}
587
587
588 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
588 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
589 +'<br/>'+
589 +'<br/>'+
590 nr_commits + nr_commits_suffix+'<br/>'+
590 nr_commits + nr_commits_suffix+'<br/>'+
591 added + added_suffix +'<br/>'+
591 added + added_suffix +'<br/>'+
592 changed + changed_suffix + '<br/>'+
592 changed + changed_suffix + '<br/>'+
593 removed + removed_suffix + '<br/>');
593 removed + removed_suffix + '<br/>');
594 }
594 }
595 }
595 }
596 else {
596 else {
597 var tooltip = YUD.get("tooltip");
597 var tooltip = YUD.get("tooltip");
598
598
599 if(tooltip) {
599 if(tooltip) {
600 tooltip.parentNode.removeChild(tooltip);
600 tooltip.parentNode.removeChild(tooltip);
601 }
601 }
602 previousPoint = null;
602 previousPoint = null;
603 }
603 }
604 }
604 }
605
605
606 /**
606 /**
607 * MAIN EXECUTION
607 * MAIN EXECUTION
608 */
608 */
609
609
610 var data = getDataAccordingToRanges(initial_ranges);
610 var data = getDataAccordingToRanges(initial_ranges);
611 generateCheckboxes(data);
611 generateCheckboxes(data);
612
612
613 //main plot
613 //main plot
614 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
614 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
615
615
616 //overview
616 //overview
617 var overview = YAHOO.widget.Flot(overviewContainer, overview_dataset, overview_options);
617 var overview = YAHOO.widget.Flot(overviewContainer, overview_dataset, overview_options);
618
618
619 //show initial selection on overview
619 //show initial selection on overview
620 overview.setSelection(initial_ranges);
620 overview.setSelection(initial_ranges);
621
621
622 plot.subscribe("plotselected", plotselected);
622 plot.subscribe("plotselected", plotselected);
623
623
624 overview.subscribe("plotselected", function (ranges) {
624 overview.subscribe("plotselected", function (ranges) {
625 plot.setSelection(ranges);
625 plot.setSelection(ranges);
626 });
626 });
627
627
628 plot.subscribe("plothover", plothover);
628 plot.subscribe("plothover", plothover);
629
629
630 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
630 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
631 }
631 }
632 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
632 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
633 </script>
633 </script>
634
634
635 </div>
635 </div>
636 </div>
636 </div>
637
637
638 <div class="box">
638 <div class="box">
639 <div class="title">
639 <div class="title">
640 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
640 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
641 </div>
641 </div>
642 <div class="table">
642 <div class="table">
643 <div id="shortlog_data">
643 <div id="shortlog_data">
644 <%include file='../shortlog/shortlog_data.html'/>
644 <%include file='../shortlog/shortlog_data.html'/>
645 </div>
645 </div>
646 ##%if c.repo_changesets:
646 ##%if c.repo_changesets:
647 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
647 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
648 ##%endif
648 ##%endif
649 </div>
649 </div>
650 </div>
650 </div>
651 <div class="box">
651 <div class="box">
652 <div class="title">
652 <div class="title">
653 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
653 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
654 </div>
654 </div>
655 <div class="table">
655 <div class="table">
656 <%include file='../tags/tags_data.html'/>
656 <%include file='../tags/tags_data.html'/>
657 %if c.repo_changesets:
657 %if c.repo_changesets:
658 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
658 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
659 %endif
659 %endif
660 </div>
660 </div>
661 </div>
661 </div>
662 <div class="box">
662 <div class="box">
663 <div class="title">
663 <div class="title">
664 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
664 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
665 </div>
665 </div>
666 <div class="table">
666 <div class="table">
667 <%include file='../branches/branches_data.html'/>
667 <%include file='../branches/branches_data.html'/>
668 %if c.repo_changesets:
668 %if c.repo_changesets:
669 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
669 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
670 %endif
670 %endif
671 </div>
671 </div>
672 </div>
672 </div>
673
673
674 </%def>
674 </%def>
General Comments 0
You need to be logged in to leave comments. Login now