##// END OF EJS Templates
fixes #214 added support for downloading subrepos in download menu.
marcink -
r1450:2a8bf2a3 beta
parent child Browse files
Show More
@@ -1,414 +1,415 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 modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import mimetypes
29 29 import traceback
30 30
31 31 from pylons import request, response, session, tmpl_context as c, url
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34
35 35 from vcs.backends import ARCHIVE_SPECS
36 36 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
37 37 EmptyRepositoryError, ImproperArchiveTypeError, VCSError
38 38 from vcs.nodes import FileNode, NodeKind
39 39 from vcs.utils import diffs as differ
40 40
41 41 from rhodecode.lib import convert_line_endings, detect_mode, safe_str
42 42 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 43 from rhodecode.lib.base import BaseRepoController, render
44 44 from rhodecode.lib.utils import EmptyChangeset
45 45 import rhodecode.lib.helpers as h
46 46 from rhodecode.model.repo import RepoModel
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class FilesController(BaseRepoController):
52 52
53 53 @LoginRequired()
54 54 def __before__(self):
55 55 super(FilesController, self).__before__()
56 56 c.cut_off_limit = self.cut_off_limit
57 57
58 58 def __get_cs_or_redirect(self, rev, repo_name):
59 59 """
60 60 Safe way to get changeset if error occur it redirects to tip with
61 61 proper message
62 62
63 63 :param rev: revision to fetch
64 64 :param repo_name: repo name to redirect after
65 65 """
66 66
67 67 try:
68 68 return c.rhodecode_repo.get_changeset(rev)
69 69 except EmptyRepositoryError, e:
70 70 h.flash(_('There are no files yet'), category='warning')
71 71 redirect(h.url('summary_home', repo_name=repo_name))
72 72
73 73 except RepositoryError, e:
74 74 h.flash(str(e), category='warning')
75 75 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
76 76
77 77 def __get_filenode_or_redirect(self, repo_name, cs, path):
78 78 """
79 79 Returns file_node, if error occurs or given path is directory,
80 80 it'll redirect to top level path
81 81
82 82 :param repo_name: repo_name
83 83 :param cs: given changeset
84 84 :param path: path to lookup
85 85 """
86 86
87 87 try:
88 88 file_node = cs.get_node(path)
89 89 if file_node.is_dir():
90 90 raise RepositoryError('given path is a directory')
91 91 except RepositoryError, e:
92 92 h.flash(str(e), category='warning')
93 93 redirect(h.url('files_home', repo_name=repo_name,
94 94 revision=cs.raw_id))
95 95
96 96 return file_node
97 97
98 98 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
99 99 'repository.admin')
100 100 def index(self, repo_name, revision, f_path):
101 101 #reditect to given revision from form if given
102 102 post_revision = request.POST.get('at_rev', None)
103 103 if post_revision:
104 104 cs = self.__get_cs_or_redirect(post_revision, repo_name)
105 105 redirect(url('files_home', repo_name=c.repo_name,
106 106 revision=cs.raw_id, f_path=f_path))
107 107
108 108 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
109 109 c.branch = request.GET.get('branch', None)
110 110 c.f_path = f_path
111 111
112 112 cur_rev = c.changeset.revision
113 113
114 114 #prev link
115 115 try:
116 116 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
117 117 c.url_prev = url('files_home', repo_name=c.repo_name,
118 118 revision=prev_rev.raw_id, f_path=f_path)
119 119 if c.branch:
120 120 c.url_prev += '?branch=%s' % c.branch
121 121 except (ChangesetDoesNotExistError, VCSError):
122 122 c.url_prev = '#'
123 123
124 124 #next link
125 125 try:
126 126 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
127 127 c.url_next = url('files_home', repo_name=c.repo_name,
128 128 revision=next_rev.raw_id, f_path=f_path)
129 129 if c.branch:
130 130 c.url_next += '?branch=%s' % c.branch
131 131 except (ChangesetDoesNotExistError, VCSError):
132 132 c.url_next = '#'
133 133
134 134 #files or dirs
135 135 try:
136 136 c.files_list = c.changeset.get_node(f_path)
137 137
138 138 if c.files_list.is_file():
139 139 c.file_history = self._get_node_history(c.changeset, f_path)
140 140 else:
141 141 c.file_history = []
142 142 except RepositoryError, e:
143 143 h.flash(str(e), category='warning')
144 144 redirect(h.url('files_home', repo_name=repo_name,
145 145 revision=revision))
146 146
147 147 return render('files/files.html')
148 148
149 149 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
150 150 'repository.admin')
151 151 def rawfile(self, repo_name, revision, f_path):
152 152 cs = self.__get_cs_or_redirect(revision, repo_name)
153 153 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
154 154
155 155 response.content_disposition = 'attachment; filename=%s' % \
156 156 safe_str(f_path.split(os.sep)[-1])
157 157
158 158 response.content_type = file_node.mimetype
159 159 return file_node.content
160 160
161 161 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
162 162 'repository.admin')
163 163 def raw(self, repo_name, revision, f_path):
164 164 cs = self.__get_cs_or_redirect(revision, repo_name)
165 165 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
166 166
167 167 raw_mimetype_mapping = {
168 168 # map original mimetype to a mimetype used for "show as raw"
169 169 # you can also provide a content-disposition to override the
170 170 # default "attachment" disposition.
171 171 # orig_type: (new_type, new_dispo)
172 172
173 173 # show images inline:
174 174 'image/x-icon': ('image/x-icon', 'inline'),
175 175 'image/png': ('image/png', 'inline'),
176 176 'image/gif': ('image/gif', 'inline'),
177 177 'image/jpeg': ('image/jpeg', 'inline'),
178 178 'image/svg+xml': ('image/svg+xml', 'inline'),
179 179 }
180 180
181 181 mimetype = file_node.mimetype
182 182 try:
183 183 mimetype, dispo = raw_mimetype_mapping[mimetype]
184 184 except KeyError:
185 185 # we don't know anything special about this, handle it safely
186 186 if file_node.is_binary:
187 187 # do same as download raw for binary files
188 188 mimetype, dispo = 'application/octet-stream', 'attachment'
189 189 else:
190 190 # do not just use the original mimetype, but force text/plain,
191 191 # otherwise it would serve text/html and that might be unsafe.
192 192 # Note: underlying vcs library fakes text/plain mimetype if the
193 193 # mimetype can not be determined and it thinks it is not
194 194 # binary.This might lead to erroneous text display in some
195 195 # cases, but helps in other cases, like with text files
196 196 # without extension.
197 197 mimetype, dispo = 'text/plain', 'inline'
198 198
199 199 if dispo == 'attachment':
200 200 dispo = 'attachment; filename=%s' % \
201 201 safe_str(f_path.split(os.sep)[-1])
202 202
203 203 response.content_disposition = dispo
204 204 response.content_type = mimetype
205 205 return file_node.content
206 206
207 207 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
208 208 'repository.admin')
209 209 def annotate(self, repo_name, revision, f_path):
210 210 c.cs = self.__get_cs_or_redirect(revision, repo_name)
211 211 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
212 212
213 213 c.file_history = self._get_node_history(c.cs, f_path)
214 214 c.f_path = f_path
215 215 return render('files/files_annotate.html')
216 216
217 217 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
218 218 def edit(self, repo_name, revision, f_path):
219 219 r_post = request.POST
220 220
221 221 c.cs = self.__get_cs_or_redirect(revision, repo_name)
222 222 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
223 223
224 224 if c.file.is_binary:
225 225 return redirect(url('files_home', repo_name=c.repo_name,
226 226 revision=c.cs.raw_id, f_path=f_path))
227 227
228 228 c.file_history = self._get_node_history(c.cs, f_path)
229 229 c.f_path = f_path
230 230
231 231 if r_post:
232 232
233 233 old_content = c.file.content
234 234 sl = old_content.splitlines(1)
235 235 first_line = sl[0] if sl else ''
236 236 # modes: 0 - Unix, 1 - Mac, 2 - DOS
237 237 mode = detect_mode(first_line, 0)
238 238 content = convert_line_endings(r_post.get('content'), mode)
239 239
240 240 message = r_post.get('message') or (_('Edited %s via RhodeCode')
241 241 % (f_path))
242 242 author = self.rhodecode_user.full_contact
243 243
244 244 if content == old_content:
245 245 h.flash(_('No changes'),
246 246 category='warning')
247 247 return redirect(url('changeset_home', repo_name=c.repo_name,
248 248 revision='tip'))
249 249
250 250 try:
251 251 self.scm_model.commit_change(repo=c.rhodecode_repo,
252 252 repo_name=repo_name, cs=c.cs,
253 253 user=self.rhodecode_user,
254 254 author=author, message=message,
255 255 content=content, f_path=f_path)
256 256 h.flash(_('Successfully committed to %s' % f_path),
257 257 category='success')
258 258
259 259 except Exception:
260 260 log.error(traceback.format_exc())
261 261 h.flash(_('Error occurred during commit'), category='error')
262 262 return redirect(url('changeset_home',
263 263 repo_name=c.repo_name, revision='tip'))
264 264
265 265 return render('files/files_edit.html')
266 266
267 267 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
268 268 'repository.admin')
269 269 def archivefile(self, repo_name, fname):
270 270
271 271 fileformat = None
272 272 revision = None
273 273 ext = None
274 subrepos = request.GET.get('subrepos') == 'true'
274 275
275 276 for a_type, ext_data in ARCHIVE_SPECS.items():
276 277 archive_spec = fname.split(ext_data[1])
277 278 if len(archive_spec) == 2 and archive_spec[1] == '':
278 279 fileformat = a_type or ext_data[1]
279 280 revision = archive_spec[0]
280 281 ext = ext_data[1]
281 282
282 283 try:
283 284 dbrepo = RepoModel().get_by_repo_name(repo_name)
284 285 if dbrepo.enable_downloads is False:
285 286 return _('downloads disabled')
286 287
287 288 cs = c.rhodecode_repo.get_changeset(revision)
288 289 content_type = ARCHIVE_SPECS[fileformat][0]
289 290 except ChangesetDoesNotExistError:
290 291 return _('Unknown revision %s') % revision
291 292 except EmptyRepositoryError:
292 293 return _('Empty repository')
293 294 except (ImproperArchiveTypeError, KeyError):
294 295 return _('Unknown archive type')
295 296
296 297 response.content_type = content_type
297 298 response.content_disposition = 'attachment; filename=%s-%s%s' \
298 299 % (repo_name, revision, ext)
299 300
300 301 import tempfile
301 302 archive = tempfile.mkstemp()[1]
302 303 t = open(archive, 'wb')
303 cs.fill_archive(stream=t, kind=fileformat)
304 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
304 305
305 306 def get_chunked_archive(archive):
306 307 stream = open(archive, 'rb')
307 308 while True:
308 309 data = stream.read(4096)
309 310 if not data:
310 311 os.remove(archive)
311 312 break
312 313 yield data
313 314
314 315 return get_chunked_archive(archive)
315 316
316 317 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
317 318 'repository.admin')
318 319 def diff(self, repo_name, f_path):
319 320 diff1 = request.GET.get('diff1')
320 321 diff2 = request.GET.get('diff2')
321 322 c.action = request.GET.get('diff')
322 323 c.no_changes = diff1 == diff2
323 324 c.f_path = f_path
324 325 c.big_diff = False
325 326
326 327 try:
327 328 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
328 329 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
329 330 node1 = c.changeset_1.get_node(f_path)
330 331 else:
331 332 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
332 333 node1 = FileNode('.', '', changeset=c.changeset_1)
333 334
334 335 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
335 336 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
336 337 node2 = c.changeset_2.get_node(f_path)
337 338 else:
338 339 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
339 340 node2 = FileNode('.', '', changeset=c.changeset_2)
340 341 except RepositoryError:
341 342 return redirect(url('files_home',
342 343 repo_name=c.repo_name, f_path=f_path))
343 344
344 345 if c.action == 'download':
345 346 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
346 347 format='gitdiff')
347 348
348 349 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
349 350 response.content_type = 'text/plain'
350 351 response.content_disposition = 'attachment; filename=%s' \
351 352 % diff_name
352 353 return diff.raw_diff()
353 354
354 355 elif c.action == 'raw':
355 356 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
356 357 format='gitdiff')
357 358 response.content_type = 'text/plain'
358 359 return diff.raw_diff()
359 360
360 361 elif c.action == 'diff':
361 362 if node1.is_binary or node2.is_binary:
362 363 c.cur_diff = _('Binary file')
363 364 elif node1.size > self.cut_off_limit or \
364 365 node2.size > self.cut_off_limit:
365 366 c.cur_diff = ''
366 367 c.big_diff = True
367 368 else:
368 369 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
369 370 format='gitdiff')
370 371 c.cur_diff = diff.as_html()
371 372 else:
372 373
373 374 #default option
374 375 if node1.is_binary or node2.is_binary:
375 376 c.cur_diff = _('Binary file')
376 377 elif node1.size > self.cut_off_limit or \
377 378 node2.size > self.cut_off_limit:
378 379 c.cur_diff = ''
379 380 c.big_diff = True
380 381
381 382 else:
382 383 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
383 384 format='gitdiff')
384 385 c.cur_diff = diff.as_html()
385 386
386 387 if not c.cur_diff and not c.big_diff:
387 388 c.no_changes = True
388 389 return render('files/file_diff.html')
389 390
390 391 def _get_node_history(self, cs, f_path):
391 392 changesets = cs.get_file_history(f_path)
392 393 hist_l = []
393 394
394 395 changesets_group = ([], _("Changesets"))
395 396 branches_group = ([], _("Branches"))
396 397 tags_group = ([], _("Tags"))
397 398
398 399 for chs in changesets:
399 400 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
400 401 changesets_group[0].append((chs.raw_id, n_desc,))
401 402
402 403 hist_l.append(changesets_group)
403 404
404 405 for name, chs in c.rhodecode_repo.branches.items():
405 406 #chs = chs.split(':')[-1]
406 407 branches_group[0].append((chs, name),)
407 408 hist_l.append(branches_group)
408 409
409 410 for name, chs in c.rhodecode_repo.tags.items():
410 411 #chs = chs.split(':')[-1]
411 412 tags_group[0].append((chs, name),)
412 413 hist_l.append(tags_group)
413 414
414 415 return hist_l
@@ -1,696 +1,700 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.dbrepo.just_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 id="summary" 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 34 %if c.rhodecode_user.username != 'default':
35 35 %if c.following:
36 36 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
37 37 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
38 38 </span>
39 39 %else:
40 40 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
41 41 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
42 42 </span>
43 43 %endif
44 44 %endif:
45 45
46 46 ##REPO TYPE
47 47 %if c.dbrepo.repo_type =='hg':
48 48 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
49 49 %endif
50 50 %if c.dbrepo.repo_type =='git':
51 51 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
52 52 %endif
53 53
54 54 ##PUBLIC/PRIVATE
55 55 %if c.dbrepo.private:
56 56 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url("/images/icons/lock.png")}"/>
57 57 %else:
58 58 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url("/images/icons/lock_open.png")}"/>
59 59 %endif
60 60
61 61 ##REPO NAME
62 62 <span class="repo_name">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
63 63
64 64 ##FORK
65 65 %if c.dbrepo.fork:
66 66 <div style="margin-top:5px;clear:both"">
67 67 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
68 68 <img class="icon" alt="${_('public')}"
69 69 title="${_('Fork of')} ${c.dbrepo.fork.repo_name}"
70 70 src="${h.url("/images/icons/arrow_divide.png")}"/>
71 71 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
72 72 </a>
73 73 </div>
74 74 %endif
75 75 ##REMOTE
76 76 %if c.dbrepo.clone_uri:
77 77 <div style="margin-top:5px;clear:both">
78 78 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}">
79 79 <img class="icon" alt="${_('remote clone')}"
80 80 title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}"
81 81 src="${h.url("/images/icons/connect.png")}"/>
82 82 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
83 83 </a>
84 84 </div>
85 85 %endif
86 86 </div>
87 87 </div>
88 88
89 89
90 90 <div class="field">
91 91 <div class="label">
92 92 <label>${_('Description')}:</label>
93 93 </div>
94 94 <div class="input-short desc">${h.urlify_text(c.dbrepo.description)}</div>
95 95 </div>
96 96
97 97
98 98 <div class="field">
99 99 <div class="label">
100 100 <label>${_('Contact')}:</label>
101 101 </div>
102 102 <div class="input-short">
103 103 <div class="gravatar">
104 104 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
105 105 </div>
106 106 ${_('Username')}: ${c.dbrepo.user.username}<br/>
107 107 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
108 108 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
109 109 </div>
110 110 </div>
111 111
112 112 <div class="field">
113 113 <div class="label">
114 114 <label>${_('Last change')}:</label>
115 115 </div>
116 116 <div class="input-short">
117 117 <b>${'r%s:%s' % (h.get_changeset_safe(c.rhodecode_repo,'tip').revision,
118 118 h.get_changeset_safe(c.rhodecode_repo,'tip').short_id)}</b> -
119 119 <span class="tooltip" title="${c.rhodecode_repo.last_change}">
120 120 ${h.age(c.rhodecode_repo.last_change)}</span><br/>
121 121 ${_('by')} ${h.get_changeset_safe(c.rhodecode_repo,'tip').author}
122 122
123 123 </div>
124 124 </div>
125 125
126 126 <div class="field">
127 127 <div class="label">
128 128 <label>${_('Clone url')}:</label>
129 129 </div>
130 130 <div class="input-short">
131 131 <input type="text" id="clone_url" readonly="readonly" value="${c.rhodecode_repo.alias} clone ${c.clone_repo_url}" size="70"/>
132 132 </div>
133 133 </div>
134 134
135 135 <div class="field">
136 136 <div class="label">
137 137 <label>${_('Trending source files')}:</label>
138 138 </div>
139 139 <div class="input-short">
140 140 <div id="lang_stats"></div>
141 141 </div>
142 142 </div>
143 143
144 144 <div class="field">
145 145 <div class="label">
146 146 <label>${_('Download')}:</label>
147 147 </div>
148 148 <div class="input-short">
149 149 %if len(c.rhodecode_repo.revisions) == 0:
150 150 ${_('There are no downloads yet')}
151 151 %elif c.enable_downloads is False:
152 152 ${_('Downloads are disabled for this repository')}
153 153 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
154 154 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
155 155 %endif
156 156 %else:
157 157 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
158 158 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
159 159 %if cnt >=1:
160 160 |
161 161 %endif
162 162 <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
163 163 id="${archive['type']+'_link'}">${h.link_to(archive['type'],
164 164 h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
165 165 fname='tip'+archive['extension']),class_="archive_icon")}</span>
166 166 %endfor
167 <span style="vertical-align: bottom">
168 <input id="archive_subrepos" type="checkbox" name="subrepos"/> <span class="tooltip" title="${_('Check this to download archive with subrepos')}" >${_('with subrepos')}</span>
169 </span>
167 170 %endif
168 171 </div>
169 172 </div>
170 173
171 174 <div class="field">
172 175 <div class="label">
173 176 <label>${_('Feeds')}:</label>
174 177 </div>
175 178 <div class="input-short">
176 179 %if c.rhodecode_user.username != 'default':
177 180 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
178 181 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
179 182 %else:
180 183 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
181 184 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
182 185 %endif
183 186 </div>
184 187 </div>
185 188 </div>
186 189 </div>
187 190 <script type="text/javascript">
188 191 YUE.onDOMReady(function(e){
189 192 id = 'clone_url';
190 193 YUE.on(id,'click',function(e){
191 194 if(YUD.hasClass(id,'selected')){
192 195 return
193 196 }
194 197 else{
195 198 YUD.addClass(id,'selected');
196 199 YUD.get(id).select();
197 200 }
198 201
199 202 })
200 203 })
201 204 var data = ${c.trending_languages|n};
202 205 var total = 0;
203 206 var no_data = true;
204 207 for (k in data){
205 208 total += data[k].count;
206 209 no_data = false;
207 210 }
208 211 var tbl = document.createElement('table');
209 212 tbl.setAttribute('class','trending_language_tbl');
210 213 var cnt = 0;
211 214 for (k in data){
212 215 cnt += 1;
213 216 var hide = cnt>2;
214 217 var tr = document.createElement('tr');
215 218 if (hide){
216 219 tr.setAttribute('style','display:none');
217 220 tr.setAttribute('class','stats_hidden');
218 221 }
219 222 var percentage = Math.round((data[k].count/total*100),2);
220 223 var value = data[k].count;
221 224 var td1 = document.createElement('td');
222 225 td1.width = 150;
223 226 var trending_language_label = document.createElement('div');
224 227 trending_language_label.innerHTML = data[k].desc+" ("+k+")";
225 228 td1.appendChild(trending_language_label);
226 229
227 230 var td2 = document.createElement('td');
228 231 td2.setAttribute('style','padding-right:14px !important');
229 232 var trending_language = document.createElement('div');
230 233 var nr_files = value+" ${_('files')}";
231 234
232 235 trending_language.title = k+" "+nr_files;
233 236
234 237 if (percentage>22){
235 238 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
236 239 }
237 240 else{
238 241 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
239 242 }
240 243
241 244 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
242 245 trending_language.style.width=percentage+"%";
243 246 td2.appendChild(trending_language);
244 247
245 248 tr.appendChild(td1);
246 249 tr.appendChild(td2);
247 250 tbl.appendChild(tr);
248 251 if(cnt == 3){
249 252 var show_more = document.createElement('tr');
250 253 var td = document.createElement('td');
251 254 lnk = document.createElement('a');
252 255
253 256 lnk.href='#';
254 257 lnk.innerHTML = "${_('show more')}";
255 258 lnk.id='code_stats_show_more';
256 259 td.appendChild(lnk);
257 260
258 261 show_more.appendChild(td);
259 262 show_more.appendChild(document.createElement('td'));
260 263 tbl.appendChild(show_more);
261 264 }
262 265
263 266 }
264 267 if(no_data){
265 268 var tr = document.createElement('tr');
266 269 var td1 = document.createElement('td');
267 270 td1.innerHTML = "${c.no_data_msg}";
268 271 tr.appendChild(td1);
269 272 tbl.appendChild(tr);
270 273 }
271 274 YUD.get('lang_stats').appendChild(tbl);
272 275 YUE.on('code_stats_show_more','click',function(){
273 276 l = YUD.getElementsByClassName('stats_hidden')
274 277 for (e in l){
275 278 YUD.setStyle(l[e],'display','');
276 279 };
277 280 YUD.setStyle(YUD.get('code_stats_show_more'),
278 281 'display','none');
279 282 })
280 283
281
282 YUE.on('download_options','change',function(e){
283 var new_cs = e.target.options[e.target.selectedIndex];
284 var tmpl_links = {}
285 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
286 tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
287 h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
288 fname='__CS__'+archive['extension']),class_="archive_icon")}';
289 %endfor
290
284 var tmpl_links = {}
285 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
286 tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
287 h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
288 fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_="archive_icon")}';
289 %endfor
290
291 YUE.on(['download_options','archive_subrepos'],'change',function(e){
292 var sm = YUD.get('download_options');
293 var new_cs = sm.options[sm.selectedIndex];
291 294
292 295 for(k in tmpl_links){
293 var s = YUD.get(k+'_link')
296 var s = YUD.get(k+'_link');
294 297 title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
295 298 s.title = title_tmpl.replace('__CS_NAME__',new_cs.text);
296 299 s.title = s.title.replace('__CS_EXT__',k);
297 s.innerHTML = tmpl_links[k].replace('__CS__',new_cs.value);
300 var url = tmpl_links[k].replace('__CS__',new_cs.value);
301 var subrepos = YUD.get('archive_subrepos').checked
302 url = url.replace('__SUB__',subrepos);
303 s.innerHTML = url
298 304 }
299
300 })
301
305 });
302 306 </script>
303 307 </div>
304 308
305 309 <div class="box box-right" style="min-height:455px">
306 310 <!-- box / title -->
307 311 <div class="title">
308 312 <h5>${_('Commit activity by day / author')}</h5>
309 313 </div>
310 314
311 315 <div class="graph">
312 316 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">
313 317 %if c.no_data:
314 318 ${c.no_data_msg}
315 319 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
316 320 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
317 321 %endif
318 322
319 323 %else:
320 324 ${_('Loaded in')} ${c.stats_percentage} %
321 325 %endif
322 326 </div>
323 327 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
324 328 <div style="clear: both;height: 10px"></div>
325 329 <div id="overview" style="width:450px;height:100px;float:left"></div>
326 330
327 331 <div id="legend_data" style="clear:both;margin-top:10px;">
328 332 <div id="legend_container"></div>
329 333 <div id="legend_choices">
330 334 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
331 335 </div>
332 336 </div>
333 337 <script type="text/javascript">
334 338 /**
335 339 * Plots summary graph
336 340 *
337 341 * @class SummaryPlot
338 342 * @param {from} initial from for detailed graph
339 343 * @param {to} initial to for detailed graph
340 344 * @param {dataset}
341 345 * @param {overview_dataset}
342 346 */
343 347 function SummaryPlot(from,to,dataset,overview_dataset) {
344 348 var initial_ranges = {
345 349 "xaxis":{
346 350 "from":from,
347 351 "to":to,
348 352 },
349 353 };
350 354 var dataset = dataset;
351 355 var overview_dataset = [overview_dataset];
352 356 var choiceContainer = YUD.get("legend_choices");
353 357 var choiceContainerTable = YUD.get("legend_choices_tables");
354 358 var plotContainer = YUD.get('commit_history');
355 359 var overviewContainer = YUD.get('overview');
356 360
357 361 var plot_options = {
358 362 bars: {show:true,align:'center',lineWidth:4},
359 363 legend: {show:true, container:"legend_container"},
360 364 points: {show:true,radius:0,fill:false},
361 365 yaxis: {tickDecimals:0,},
362 366 xaxis: {
363 367 mode: "time",
364 368 timeformat: "%d/%m",
365 369 min:from,
366 370 max:to,
367 371 },
368 372 grid: {
369 373 hoverable: true,
370 374 clickable: true,
371 375 autoHighlight:true,
372 376 color: "#999"
373 377 },
374 378 //selection: {mode: "x"}
375 379 };
376 380 var overview_options = {
377 381 legend:{show:false},
378 382 bars: {show:true,barWidth: 2,},
379 383 shadowSize: 0,
380 384 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
381 385 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
382 386 grid: {color: "#999",},
383 387 selection: {mode: "x"}
384 388 };
385 389
386 390 /**
387 391 *get dummy data needed in few places
388 392 */
389 393 function getDummyData(label){
390 394 return {"label":label,
391 395 "data":[{"time":0,
392 396 "commits":0,
393 397 "added":0,
394 398 "changed":0,
395 399 "removed":0,
396 400 }],
397 401 "schema":["commits"],
398 402 "color":'#ffffff',
399 403 }
400 404 }
401 405
402 406 /**
403 407 * generate checkboxes accordindly to data
404 408 * @param keys
405 409 * @returns
406 410 */
407 411 function generateCheckboxes(data) {
408 412 //append checkboxes
409 413 var i = 0;
410 414 choiceContainerTable.innerHTML = '';
411 415 for(var pos in data) {
412 416
413 417 data[pos].color = i;
414 418 i++;
415 419 if(data[pos].label != ''){
416 420 choiceContainerTable.innerHTML += '<tr><td>'+
417 421 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
418 422 +data[pos].label+
419 423 '</td></tr>';
420 424 }
421 425 }
422 426 }
423 427
424 428 /**
425 429 * ToolTip show
426 430 */
427 431 function showTooltip(x, y, contents) {
428 432 var div=document.getElementById('tooltip');
429 433 if(!div) {
430 434 div = document.createElement('div');
431 435 div.id="tooltip";
432 436 div.style.position="absolute";
433 437 div.style.border='1px solid #fdd';
434 438 div.style.padding='2px';
435 439 div.style.backgroundColor='#fee';
436 440 document.body.appendChild(div);
437 441 }
438 442 YUD.setStyle(div, 'opacity', 0);
439 443 div.innerHTML = contents;
440 444 div.style.top=(y + 5) + "px";
441 445 div.style.left=(x + 5) + "px";
442 446
443 447 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
444 448 anim.animate();
445 449 }
446 450
447 451 /**
448 452 * This function will detect if selected period has some changesets
449 453 for this user if it does this data is then pushed for displaying
450 454 Additionally it will only display users that are selected by the checkbox
451 455 */
452 456 function getDataAccordingToRanges(ranges) {
453 457
454 458 var data = [];
455 459 var new_dataset = {};
456 460 var keys = [];
457 461 var max_commits = 0;
458 462 for(var key in dataset){
459 463
460 464 for(var ds in dataset[key].data){
461 465 commit_data = dataset[key].data[ds];
462 466 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
463 467
464 468 if(new_dataset[key] === undefined){
465 469 new_dataset[key] = {data:[],schema:["commits"],label:key};
466 470 }
467 471 new_dataset[key].data.push(commit_data);
468 472 }
469 473 }
470 474 if (new_dataset[key] !== undefined){
471 475 data.push(new_dataset[key]);
472 476 }
473 477 }
474 478
475 479 if (data.length > 0){
476 480 return data;
477 481 }
478 482 else{
479 483 //just return dummy data for graph to plot itself
480 484 return [getDummyData('')];
481 485 }
482 486 }
483 487
484 488 /**
485 489 * redraw using new checkbox data
486 490 */
487 491 function plotchoiced(e,args){
488 492 var cur_data = args[0];
489 493 var cur_ranges = args[1];
490 494
491 495 var new_data = [];
492 496 var inputs = choiceContainer.getElementsByTagName("input");
493 497
494 498 //show only checked labels
495 499 for(var i=0; i<inputs.length; i++) {
496 500 var checkbox_key = inputs[i].name;
497 501
498 502 if(inputs[i].checked){
499 503 for(var d in cur_data){
500 504 if(cur_data[d].label == checkbox_key){
501 505 new_data.push(cur_data[d]);
502 506 }
503 507 }
504 508 }
505 509 else{
506 510 //push dummy data to not hide the label
507 511 new_data.push(getDummyData(checkbox_key));
508 512 }
509 513 }
510 514
511 515 var new_options = YAHOO.lang.merge(plot_options, {
512 516 xaxis: {
513 517 min: cur_ranges.xaxis.from,
514 518 max: cur_ranges.xaxis.to,
515 519 mode:"time",
516 520 timeformat: "%d/%m",
517 521 },
518 522 });
519 523 if (!new_data){
520 524 new_data = [[0,1]];
521 525 }
522 526 // do the zooming
523 527 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
524 528
525 529 plot.subscribe("plotselected", plotselected);
526 530
527 531 //resubscribe plothover
528 532 plot.subscribe("plothover", plothover);
529 533
530 534 // don't fire event on the overview to prevent eternal loop
531 535 overview.setSelection(cur_ranges, true);
532 536
533 537 }
534 538
535 539 /**
536 540 * plot only selected items from overview
537 541 * @param ranges
538 542 * @returns
539 543 */
540 544 function plotselected(ranges,cur_data) {
541 545 //updates the data for new plot
542 546 var data = getDataAccordingToRanges(ranges);
543 547 generateCheckboxes(data);
544 548
545 549 var new_options = YAHOO.lang.merge(plot_options, {
546 550 xaxis: {
547 551 min: ranges.xaxis.from,
548 552 max: ranges.xaxis.to,
549 553 mode:"time",
550 554 timeformat: "%d/%m",
551 555 },
552 556 });
553 557 // do the zooming
554 558 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
555 559
556 560 plot.subscribe("plotselected", plotselected);
557 561
558 562 //resubscribe plothover
559 563 plot.subscribe("plothover", plothover);
560 564
561 565 // don't fire event on the overview to prevent eternal loop
562 566 overview.setSelection(ranges, true);
563 567
564 568 //resubscribe choiced
565 569 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
566 570 }
567 571
568 572 var previousPoint = null;
569 573
570 574 function plothover(o) {
571 575 var pos = o.pos;
572 576 var item = o.item;
573 577
574 578 //YUD.get("x").innerHTML = pos.x.toFixed(2);
575 579 //YUD.get("y").innerHTML = pos.y.toFixed(2);
576 580 if (item) {
577 581 if (previousPoint != item.datapoint) {
578 582 previousPoint = item.datapoint;
579 583
580 584 var tooltip = YUD.get("tooltip");
581 585 if(tooltip) {
582 586 tooltip.parentNode.removeChild(tooltip);
583 587 }
584 588 var x = item.datapoint.x.toFixed(2);
585 589 var y = item.datapoint.y.toFixed(2);
586 590
587 591 if (!item.series.label){
588 592 item.series.label = 'commits';
589 593 }
590 594 var d = new Date(x*1000);
591 595 var fd = d.toDateString()
592 596 var nr_commits = parseInt(y);
593 597
594 598 var cur_data = dataset[item.series.label].data[item.dataIndex];
595 599 var added = cur_data.added;
596 600 var changed = cur_data.changed;
597 601 var removed = cur_data.removed;
598 602
599 603 var nr_commits_suffix = " ${_('commits')} ";
600 604 var added_suffix = " ${_('files added')} ";
601 605 var changed_suffix = " ${_('files changed')} ";
602 606 var removed_suffix = " ${_('files removed')} ";
603 607
604 608
605 609 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
606 610 if(added==1){added_suffix=" ${_('file added')} ";}
607 611 if(changed==1){changed_suffix=" ${_('file changed')} ";}
608 612 if(removed==1){removed_suffix=" ${_('file removed')} ";}
609 613
610 614 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
611 615 +'<br/>'+
612 616 nr_commits + nr_commits_suffix+'<br/>'+
613 617 added + added_suffix +'<br/>'+
614 618 changed + changed_suffix + '<br/>'+
615 619 removed + removed_suffix + '<br/>');
616 620 }
617 621 }
618 622 else {
619 623 var tooltip = YUD.get("tooltip");
620 624
621 625 if(tooltip) {
622 626 tooltip.parentNode.removeChild(tooltip);
623 627 }
624 628 previousPoint = null;
625 629 }
626 630 }
627 631
628 632 /**
629 633 * MAIN EXECUTION
630 634 */
631 635
632 636 var data = getDataAccordingToRanges(initial_ranges);
633 637 generateCheckboxes(data);
634 638
635 639 //main plot
636 640 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
637 641
638 642 //overview
639 643 var overview = YAHOO.widget.Flot(overviewContainer,
640 644 overview_dataset, overview_options);
641 645
642 646 //show initial selection on overview
643 647 overview.setSelection(initial_ranges);
644 648
645 649 plot.subscribe("plotselected", plotselected);
646 650 plot.subscribe("plothover", plothover)
647 651
648 652 overview.subscribe("plotselected", function (ranges) {
649 653 plot.setSelection(ranges);
650 654 });
651 655
652 656 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
653 657 }
654 658 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
655 659 </script>
656 660
657 661 </div>
658 662 </div>
659 663
660 664 <div class="box">
661 665 <div class="title">
662 666 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
663 667 </div>
664 668 <div class="table">
665 669 <div id="shortlog_data">
666 670 <%include file='../shortlog/shortlog_data.html'/>
667 671 </div>
668 672 ##%if c.repo_changesets:
669 673 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
670 674 ##%endif
671 675 </div>
672 676 </div>
673 677 <div class="box">
674 678 <div class="title">
675 679 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
676 680 </div>
677 681 <div class="table">
678 682 <%include file='../tags/tags_data.html'/>
679 683 %if c.repo_changesets:
680 684 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
681 685 %endif
682 686 </div>
683 687 </div>
684 688 <div class="box">
685 689 <div class="title">
686 690 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
687 691 </div>
688 692 <div class="table">
689 693 <%include file='../branches/branches_data.html'/>
690 694 %if c.repo_changesets:
691 695 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
692 696 %endif
693 697 </div>
694 698 </div>
695 699
696 700 </%def>
General Comments 0
You need to be logged in to leave comments. Login now