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