##// END OF EJS Templates
fixed error when trying to make download on empty repository
marcink -
r945:05b59c48 beta
parent child Browse files
Show More
@@ -1,178 +1,178 b''
1 1 .. _changelog:
2 2
3 3 Changelog
4 4 =========
5 5
6 6 1.2.0 (**2011-XX-XX**)
7 7 ======================
8 8
9 9 :status: in-progress
10 10 :branch: beta
11 11
12 12 news
13 13 ----
14 14
15 15 - implemented #89 Can setup google analytics code from settings menu
16 16 - implemented #91 added nicer looking archive urls
17 17 - implemented #44 into file browsing, and added follow branch option
18 18 - anonymous repository can be cloned without having to pass default:default
19 19 into clone url
20 20 - fixed #90 whoosh indexer can index chooses repositories passed in command
21 21 line
22 22 - added dynamic download links in summary. With quick branch/tag selection
23 23
24 24 fixes
25 25 -----
26 26
27 27 - fixed file browser bug, when switching into given form revision the url was
28 28 not changing
29 29 - fixed propagation to error controller on simplehg and simplegit middlewares
30
30 - fixed error when trying to make a download on empty repository
31 31
32 32
33 33 1.1.2 (**2011-01-12**)
34 34 ======================
35 35
36 36 news
37 37 ----
38 38
39 39
40 40 fixes
41 41 -----
42 42
43 43 - fixes #98 protection against float division of percentage stats
44 44 - fixed graph bug
45 45 - forced webhelpers version since it was making troubles during installation
46 46
47 47
48 48 1.1.1 (**2011-01-06**)
49 49 ======================
50 50
51 51 news
52 52 ----
53 53
54 54 - added force https option into ini files for easier https usage (no need to
55 55 set server headers with this options)
56 56 - small css updates
57 57
58 58 fixes
59 59 -----
60 60
61 61 - fixed #96 redirect loop on files view on repositories without changesets
62 62 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
63 63 and server crashed with errors
64 64 - fixed large tooltips problems on main page
65 65 - fixed #92 whoosh indexer is more error proof
66 66
67 67 1.1.0 (**2010-12-18**)
68 68 ======================
69 69
70 70 news
71 71 ----
72 72
73 73 - rewrite of internals for vcs >=0.1.10
74 74 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
75 75 with older clients
76 76 - anonymous access, authentication via ldap
77 77 - performance upgrade for cached repos list - each repository has it's own
78 78 cache that's invalidated when needed.
79 79 - performance upgrades on repositories with large amount of commits (20K+)
80 80 - main page quick filter for filtering repositories
81 81 - user dashboards with ability to follow chosen repositories actions
82 82 - sends email to admin on new user registration
83 83 - added cache/statistics reset options into repository settings
84 84 - more detailed action logger (based on hooks) with pushed changesets lists
85 85 and options to disable those hooks from admin panel
86 86 - introduced new enhanced changelog for merges that shows more accurate results
87 87 - new improved and faster code stats (based on pygments lexers mapping tables,
88 88 showing up to 10 trending sources for each repository. Additionally stats
89 89 can be disabled in repository settings.
90 90 - gui optimizations, fixed application width to 1024px
91 91 - added cut off (for large files/changesets) limit into config files
92 92 - whoosh, celeryd, upgrade moved to paster command
93 93 - other than sqlite database backends can be used
94 94
95 95 fixes
96 96 -----
97 97
98 98 - fixes #61 forked repo was showing only after cache expired
99 99 - fixes #76 no confirmation on user deletes
100 100 - fixes #66 Name field misspelled
101 101 - fixes #72 block user removal when he owns repositories
102 102 - fixes #69 added password confirmation fields
103 103 - fixes #87 RhodeCode crashes occasionally on updating repository owner
104 104 - fixes #82 broken annotations on files with more than 1 blank line at the end
105 105 - a lot of fixes and tweaks for file browser
106 106 - fixed detached session issues
107 107 - fixed when user had no repos he would see all repos listed in my account
108 108 - fixed ui() instance bug when global hgrc settings was loaded for server
109 109 instance and all hgrc options were merged with our db ui() object
110 110 - numerous small bugfixes
111 111
112 112 (special thanks for TkSoh for detailed feedback)
113 113
114 114
115 115 1.0.2 (**2010-11-12**)
116 116 ======================
117 117
118 118 news
119 119 ----
120 120
121 121 - tested under python2.7
122 122 - bumped sqlalchemy and celery versions
123 123
124 124 fixes
125 125 -----
126 126
127 127 - fixed #59 missing graph.js
128 128 - fixed repo_size crash when repository had broken symlinks
129 129 - fixed python2.5 crashes.
130 130
131 131
132 132 1.0.1 (**2010-11-10**)
133 133 ======================
134 134
135 135 news
136 136 ----
137 137
138 138 - small css updated
139 139
140 140 fixes
141 141 -----
142 142
143 143 - fixed #53 python2.5 incompatible enumerate calls
144 144 - fixed #52 disable mercurial extension for web
145 145 - fixed #51 deleting repositories don't delete it's dependent objects
146 146
147 147
148 148 1.0.0 (**2010-11-02**)
149 149 ======================
150 150
151 151 - security bugfix simplehg wasn't checking for permissions on commands
152 152 other than pull or push.
153 153 - fixed doubled messages after push or pull in admin journal
154 154 - templating and css corrections, fixed repo switcher on chrome, updated titles
155 155 - admin menu accessible from options menu on repository view
156 156 - permissions cached queries
157 157
158 158 1.0.0rc4 (**2010-10-12**)
159 159 ==========================
160 160
161 161 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
162 162 - removed cache_manager settings from sqlalchemy meta
163 163 - added sqlalchemy cache settings to ini files
164 164 - validated password length and added second try of failure on paster setup-app
165 165 - fixed setup database destroy prompt even when there was no db
166 166
167 167
168 168 1.0.0rc3 (**2010-10-11**)
169 169 =========================
170 170
171 171 - fixed i18n during installation.
172 172
173 173 1.0.0rc2 (**2010-10-11**)
174 174 =========================
175 175
176 176 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
177 177 occure. After vcs is fixed it'll be put back again.
178 178 - templating/css rewrites, optimized css. No newline at end of file
@@ -1,281 +1,283 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.exceptions import RepositoryError, ChangesetError, \
43 43 ChangesetDoesNotExistError, EmptyRepositoryError
44 44 from vcs.nodes import FileNode
45 45 from vcs.utils import diffs as differ
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49 class FilesController(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(FilesController, self).__before__()
56 56 c.cut_off_limit = self.cut_off_limit
57 57
58 58 def index(self, repo_name, revision, f_path):
59 59 hg_model = ScmModel()
60 60 c.repo = hg_model.get_repo(c.repo_name)
61 61
62 62 try:
63 63 #reditect to given revision from form
64 64 post_revision = request.POST.get('at_rev', None)
65 65 if post_revision:
66 66 post_revision = c.repo.get_changeset(post_revision).raw_id
67 67 redirect(url('files_home', repo_name=c.repo_name,
68 68 revision=post_revision, f_path=f_path))
69 69
70 70 c.branch = request.GET.get('branch', None)
71 71
72 72 c.f_path = f_path
73 73
74 74 c.changeset = c.repo.get_changeset(revision)
75 75 cur_rev = c.changeset.revision
76 76
77 77 #prev link
78 78 try:
79 79 prev_rev = c.repo.get_changeset(cur_rev).prev(c.branch).raw_id
80 80 c.url_prev = url('files_home', repo_name=c.repo_name,
81 81 revision=prev_rev, f_path=f_path)
82 82 if c.branch:
83 83 c.url_prev += '?branch=%s' % c.branch
84 84 except ChangesetDoesNotExistError:
85 85 c.url_prev = '#'
86 86
87 87 #next link
88 88 try:
89 89 next_rev = c.repo.get_changeset(cur_rev).next(c.branch).raw_id
90 90 c.url_next = url('files_home', repo_name=c.repo_name,
91 91 revision=next_rev, f_path=f_path)
92 92 if c.branch:
93 93 c.url_next += '?branch=%s' % c.branch
94 94 except ChangesetDoesNotExistError:
95 95 c.url_next = '#'
96 96
97 97 #files
98 98 try:
99 99 c.files_list = c.changeset.get_node(f_path)
100 100 c.file_history = self._get_history(c.repo, c.files_list, f_path)
101 101 except RepositoryError, e:
102 102 h.flash(str(e), category='warning')
103 103 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
104 104
105 105 except EmptyRepositoryError, e:
106 106 h.flash(_('There are no files yet'), category='warning')
107 107 redirect(h.url('summary_home', repo_name=repo_name))
108 108
109 109 except RepositoryError, e:
110 110 h.flash(str(e), category='warning')
111 111 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
112 112
113 113
114 114
115 115 return render('files/files.html')
116 116
117 117 def rawfile(self, repo_name, revision, f_path):
118 118 hg_model = ScmModel()
119 119 c.repo = hg_model.get_repo(c.repo_name)
120 120 file_node = c.repo.get_changeset(revision).get_node(f_path)
121 121 response.content_type = file_node.mimetype
122 122 response.content_disposition = 'attachment; filename=%s' \
123 123 % f_path.split('/')[-1]
124 124 return file_node.content
125 125
126 126 def raw(self, repo_name, revision, f_path):
127 127 hg_model = ScmModel()
128 128 c.repo = hg_model.get_repo(c.repo_name)
129 129 file_node = c.repo.get_changeset(revision).get_node(f_path)
130 130 response.content_type = 'text/plain'
131 131
132 132 return file_node.content
133 133
134 134 def annotate(self, repo_name, revision, f_path):
135 135 hg_model = ScmModel()
136 136 c.repo = hg_model.get_repo(c.repo_name)
137 137
138 138 try:
139 139 c.cs = c.repo.get_changeset(revision)
140 140 c.file = c.cs.get_node(f_path)
141 141 except RepositoryError, e:
142 142 h.flash(str(e), category='warning')
143 143 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
144 144
145 145 c.file_history = self._get_history(c.repo, c.file, f_path)
146 146
147 147 c.f_path = f_path
148 148
149 149 return render('files/files_annotate.html')
150 150
151 151 def archivefile(self, repo_name, fname):
152 152 archive_specs = {
153 153 '.tar.bz2': ('application/x-tar', 'tbz2'),
154 154 '.tar.gz': ('application/x-tar', 'tgz'),
155 155 '.zip': ('application/zip', 'zip'),
156 156 }
157
157
158 158 fileformat = None
159 159 revision = None
160
160
161 161 for ext in archive_specs.keys():
162 162 archive_spec = fname.split(ext)
163 163 if len(archive_spec) == 2:
164 164 fileformat = archive_spec[1] or ext
165 165 revision = archive_spec[0]
166
166
167 167 if not archive_specs.has_key(fileformat):
168 168 return _('Unknown archive type')
169 169
170 170 repo = ScmModel().get_repo(repo_name)
171 171
172 172 try:
173 173 repo.get_changeset(revision)
174 174 except ChangesetDoesNotExistError:
175 175 return _('Unknown revision %s') % revision
176 except EmptyRepositoryError:
177 return _('Empty repository')
176 178
177 179 archive = tempfile.TemporaryFile()
178 180 localrepo = repo.repo
179 181 fname = '%s-%s%s' % (repo_name, revision, fileformat)
180 182 archival.archive(localrepo, archive, revision, archive_specs[fileformat][1],
181 183 prefix='%s-%s' % (repo_name, revision))
182 184 response.content_type = archive_specs[fileformat][0]
183 185 response.content_disposition = 'attachment; filename=%s' % fname
184 186 archive.seek(0)
185 187
186 188 def read_in_chunks(file_object, chunk_size=1024 * 40):
187 189 """Lazy function (generator) to read a file piece by piece.
188 190 Default chunk size: 40k."""
189 191 while True:
190 192 data = file_object.read(chunk_size)
191 193 if not data:
192 194 break
193 195 yield data
194 196
195 197 return read_in_chunks(archive)
196 198
197 199 def diff(self, repo_name, f_path):
198 200 hg_model = ScmModel()
199 201 diff1 = request.GET.get('diff1')
200 202 diff2 = request.GET.get('diff2')
201 203 c.action = request.GET.get('diff')
202 204 c.no_changes = diff1 == diff2
203 205 c.f_path = f_path
204 206 c.repo = hg_model.get_repo(c.repo_name)
205 207
206 208 try:
207 209 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
208 210 c.changeset_1 = c.repo.get_changeset(diff1)
209 211 node1 = c.changeset_1.get_node(f_path)
210 212 else:
211 213 c.changeset_1 = EmptyChangeset()
212 214 node1 = FileNode('.', '', changeset=c.changeset_1)
213 215
214 216 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
215 217 c.changeset_2 = c.repo.get_changeset(diff2)
216 218 node2 = c.changeset_2.get_node(f_path)
217 219 else:
218 220 c.changeset_2 = EmptyChangeset()
219 221 node2 = FileNode('.', '', changeset=c.changeset_2)
220 222 except RepositoryError:
221 223 return redirect(url('files_home',
222 224 repo_name=c.repo_name, f_path=f_path))
223 225
224 226 f_udiff = differ.get_udiff(node1, node2)
225 227 diff = differ.DiffProcessor(f_udiff)
226 228
227 229 if c.action == 'download':
228 230 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
229 231 response.content_type = 'text/plain'
230 232 response.content_disposition = 'attachment; filename=%s' \
231 233 % diff_name
232 234 return diff.raw_diff()
233 235
234 236 elif c.action == 'raw':
235 237 response.content_type = 'text/plain'
236 238 return diff.raw_diff()
237 239
238 240 elif c.action == 'diff':
239 241 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
240 242 c.cur_diff = _('Diff is to big to display')
241 243 else:
242 244 c.cur_diff = diff.as_html()
243 245 else:
244 246 #default option
245 247 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
246 248 c.cur_diff = _('Diff is to big to display')
247 249 else:
248 250 c.cur_diff = diff.as_html()
249 251
250 252 if not c.cur_diff: c.no_changes = True
251 253 return render('files/file_diff.html')
252 254
253 255 def _get_history(self, repo, node, f_path):
254 256 from vcs.nodes import NodeKind
255 257 if not node.kind is NodeKind.FILE:
256 258 return []
257 259 changesets = node.history
258 260 hist_l = []
259 261
260 262 changesets_group = ([], _("Changesets"))
261 263 branches_group = ([], _("Branches"))
262 264 tags_group = ([], _("Tags"))
263 265
264 266 for chs in changesets:
265 267 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
266 268 changesets_group[0].append((chs.raw_id, n_desc,))
267 269
268 270 hist_l.append(changesets_group)
269 271
270 272 for name, chs in c.repository_branches.items():
271 273 #chs = chs.split(':')[-1]
272 274 branches_group[0].append((chs, name),)
273 275 hist_l.append(branches_group)
274 276
275 277 for name, chs in c.repository_tags.items():
276 278 #chs = chs.split(':')[-1]
277 279 tags_group[0].append((chs, name),)
278 280 hist_l.append(tags_group)
279 281
280 282 return hist_l
281 283
@@ -1,667 +1,669 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 34 %if c.repo_info.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 37 %if c.repo_info.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 41 %if c.repo_info.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 46 <span style="font-size: 1.6em;font-weight: bold;vertical-align: baseline;">${c.repo_info.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 50 onclick="javascript:toggleFollowingRepo(${c.repo_info.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 54 onclick="javascript:toggleFollowingRepo(${c.repo_info.dbrepo.repo_id},'${str(h.get_token())}')">
55 55 </span>
56 56 %endif
57 57 %endif:
58 58 <br/>
59 59 %if c.repo_info.dbrepo.fork:
60 60 <span style="margin-top:5px">
61 61 <a href="${h.url('summary_home',repo_name=c.repo_info.dbrepo.fork.repo_name)}">
62 62 <img class="icon" alt="${_('public')}"
63 63 title="${_('Fork of')} ${c.repo_info.dbrepo.fork.repo_name}"
64 64 src="/images/icons/arrow_divide.png"/>
65 65 ${_('Fork of')} ${c.repo_info.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 78 ${c.repo_info.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 89 <img alt="gravatar" src="${h.gravatar_url(c.repo_info.dbrepo.user.email)}"/>
90 90 </div>
91 91 ${_('Username')}: ${c.repo_info.dbrepo.user.username}<br/>
92 92 ${_('Name')}: ${c.repo_info.dbrepo.user.name} ${c.repo_info.dbrepo.user.lastname}<br/>
93 93 ${_('Email')}: <a href="mailto:${c.repo_info.dbrepo.user.email}">${c.repo_info.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 102 ${h.age(c.repo_info.last_change)} - ${c.repo_info.last_change}
103 103 ${_('by')} ${h.get_changeset_safe(c.repo_info,'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
132 ${h.select('download_options',c.repo_info.get_changeset().raw_id,c.download_options)}
133 %for cnt,archive in enumerate(c.repo_info._get_archives()):
134 %if cnt >=1:
135 |
136 %endif
137 <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
138 id="${archive['type']+'_link'}">${h.link_to(archive['type'],
139 h.url('files_archive_home',repo_name=c.repo_info.name,
140 fname='tip'+archive['extension']),class_="archive_icon")}</span>
141 %endfor
131 %if len(c.repo_info.revisions) == 0:
132 ${_('There are no downloads yet')}
133 %else:
134 ${h.select('download_options',c.repo_info.get_changeset().raw_id,c.download_options)}
135 %for cnt,archive in enumerate(c.repo_info._get_archives()):
136 %if cnt >=1:
137 |
138 %endif
139 <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
140 id="${archive['type']+'_link'}">${h.link_to(archive['type'],
141 h.url('files_archive_home',repo_name=c.repo_info.name,
142 fname='tip'+archive['extension']),class_="archive_icon")}</span>
143 %endfor
144 %endif
142 145 </div>
143 146 </div>
144 147
145 148 <div class="field">
146 149 <div class="label">
147 150 <label>${_('Feeds')}:</label>
148 151 </div>
149 152 <div class="input-short">
150 153 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo_info.name),class_='rss_icon')}
151 154 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo_info.name),class_='atom_icon')}
152 155 </div>
153 156 </div>
154 157 </div>
155 158 </div>
156 159 <script type="text/javascript">
157 160 YUE.onDOMReady(function(e){
158 161 id = 'clone_url';
159 162 YUE.on(id,'click',function(e){
160 163 YUD.get('clone_url').select();
161 164 })
162 165 })
163 166 var data = ${c.trending_languages|n};
164 167 var total = 0;
165 168 var no_data = true;
166 169 for (k in data){
167 170 total += data[k];
168 171 no_data = false;
169 172 }
170 173 var tbl = document.createElement('table');
171 174 tbl.setAttribute('class','trending_language_tbl');
172 175 var cnt =0;
173 176 for (k in data){
174 177 cnt+=1;
175 178 var hide = cnt>2;
176 179 var tr = document.createElement('tr');
177 180 if (hide){
178 181 tr.setAttribute('style','display:none');
179 182 tr.setAttribute('class','stats_hidden');
180 183 }
181 184 var percentage = Math.round((data[k]/total*100),2);
182 185 var value = data[k];
183 186 var td1 = document.createElement('td');
184 187 td1.width=150;
185 188 var trending_language_label = document.createElement('div');
186 189 trending_language_label.innerHTML = k;
187 190 td1.appendChild(trending_language_label);
188 191
189 192 var td2 = document.createElement('td');
190 193 td2.setAttribute('style','padding-right:14px !important');
191 194 var trending_language = document.createElement('div');
192 195 var nr_files = value+" ${_('files')}";
193 196
194 197 trending_language.title = k+" "+nr_files;
195 198
196 199 if (percentage>20){
197 200 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
198 201 }
199 202 else{
200 203 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
201 204 }
202 205
203 206 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
204 207 trending_language.style.width=percentage+"%";
205 208 td2.appendChild(trending_language);
206 209
207 210 tr.appendChild(td1);
208 211 tr.appendChild(td2);
209 212 tbl.appendChild(tr);
210 213 if(cnt == 2){
211 214 var show_more = document.createElement('tr');
212 215 var td=document.createElement('td');
213 216 lnk = document.createElement('a');
214 217 lnk.href='#';
215 218 lnk.innerHTML = "${_("show more")}";
216 219 lnk.id='code_stats_show_more';
217 220 td.appendChild(lnk);
218 221 show_more.appendChild(td);
219 222 show_more.appendChild(document.createElement('td'));
220 223 tbl.appendChild(show_more);
221 224 }
222 225
223 226 }
224 227 if(no_data){
225 228 var tr = document.createElement('tr');
226 229 var td1 = document.createElement('td');
227 230 td1.innerHTML = "${c.no_data_msg}";
228 231 tr.appendChild(td1);
229 232 tbl.appendChild(tr);
230 233 }
231 234 YUD.get('lang_stats').appendChild(tbl);
232 235 YUE.on('code_stats_show_more','click',function(){
233 236 l = YUD.getElementsByClassName('stats_hidden')
234 237 for (e in l){
235 238 YUD.setStyle(l[e],'display','');
236 239 };
237 240 YUD.setStyle(YUD.get('code_stats_show_more'),
238 241 'display','none');
239 242 })
240 243
241 244
242 245 YUE.on('download_options','change',function(e){
243 246 var new_cs = e.target.options[e.target.selectedIndex];
244 247 var tmpl_links = {}
245 248 %for cnt,archive in enumerate(c.repo_info._get_archives()):
246 249 tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
247 250 h.url('files_archive_home',repo_name=c.repo_info.name,
248 251 fname='__CS__'+archive['extension']),class_="archive_icon")}';
249 252 %endfor
250 253
251 254
252 255 for(k in tmpl_links){
253 256 var s = YUD.get(k+'_link')
254 257 title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__',archive['type'])}";
255 258 s.title = title_tmpl.replace('__CS_NAME__',new_cs.text)
256 259 s.innerHTML = tmpl_links[k].replace('__CS__',new_cs.value);
257 260 }
258 261
259 262 })
260 263
261 264 </script>
262 265 </div>
263 266
264 267 <div class="box box-right" style="min-height:455px">
265 268 <!-- box / title -->
266 269 <div class="title">
267 270 <h5>${_('Commit activity by day / author')}</h5>
268 271 </div>
269 272
270 273 <div class="table">
271
272 274 %if c.no_data:
273 275 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">${c.no_data_msg}
274 276 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
275 277 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
276 %endif
278 %endif
277 279 </div>
278 280 %endif:
279 281 <div id="commit_history" style="width:460px;height:300px;float:left"></div>
280 282 <div style="clear: both;height: 10px"></div>
281 283 <div id="overview" style="width:460px;height:100px;float:left"></div>
282 284
283 285 <div id="legend_data" style="clear:both;margin-top:10px;">
284 286 <div id="legend_container"></div>
285 287 <div id="legend_choices">
286 288 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
287 289 </div>
288 290 </div>
289 291 <script type="text/javascript">
290 292 /**
291 293 * Plots summary graph
292 294 *
293 295 * @class SummaryPlot
294 296 * @param {from} initial from for detailed graph
295 297 * @param {to} initial to for detailed graph
296 298 * @param {dataset}
297 299 * @param {overview_dataset}
298 300 */
299 301 function SummaryPlot(from,to,dataset,overview_dataset) {
300 302 var initial_ranges = {
301 303 "xaxis":{
302 304 "from":from,
303 305 "to":to,
304 306 },
305 307 };
306 308 var dataset = dataset;
307 309 var overview_dataset = [overview_dataset];
308 310 var choiceContainer = YUD.get("legend_choices");
309 311 var choiceContainerTable = YUD.get("legend_choices_tables");
310 312 var plotContainer = YUD.get('commit_history');
311 313 var overviewContainer = YUD.get('overview');
312 314
313 315 var plot_options = {
314 316 bars: {show:true,align:'center',lineWidth:4},
315 317 legend: {show:true, container:"legend_container"},
316 318 points: {show:true,radius:0,fill:false},
317 319 yaxis: {tickDecimals:0,},
318 320 xaxis: {
319 321 mode: "time",
320 322 timeformat: "%d/%m",
321 323 min:from,
322 324 max:to,
323 325 },
324 326 grid: {
325 327 hoverable: true,
326 328 clickable: true,
327 329 autoHighlight:true,
328 330 color: "#999"
329 331 },
330 332 //selection: {mode: "x"}
331 333 };
332 334 var overview_options = {
333 335 legend:{show:false},
334 336 bars: {show:true,barWidth: 2,},
335 337 shadowSize: 0,
336 338 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
337 339 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
338 340 grid: {color: "#999",},
339 341 selection: {mode: "x"}
340 342 };
341 343
342 344 /**
343 345 *get dummy data needed in few places
344 346 */
345 347 function getDummyData(label){
346 348 return {"label":label,
347 349 "data":[{"time":0,
348 350 "commits":0,
349 351 "added":0,
350 352 "changed":0,
351 353 "removed":0,
352 354 }],
353 355 "schema":["commits"],
354 356 "color":'#ffffff',
355 357 }
356 358 }
357 359
358 360 /**
359 361 * generate checkboxes accordindly to data
360 362 * @param keys
361 363 * @returns
362 364 */
363 365 function generateCheckboxes(data) {
364 366 //append checkboxes
365 367 var i = 0;
366 368 choiceContainerTable.innerHTML = '';
367 369 for(var pos in data) {
368 370
369 371 data[pos].color = i;
370 372 i++;
371 373 if(data[pos].label != ''){
372 374 choiceContainerTable.innerHTML += '<tr><td>'+
373 375 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
374 376 +data[pos].label+
375 377 '</td></tr>';
376 378 }
377 379 }
378 380 }
379 381
380 382 /**
381 383 * ToolTip show
382 384 */
383 385 function showTooltip(x, y, contents) {
384 386 var div=document.getElementById('tooltip');
385 387 if(!div) {
386 388 div = document.createElement('div');
387 389 div.id="tooltip";
388 390 div.style.position="absolute";
389 391 div.style.border='1px solid #fdd';
390 392 div.style.padding='2px';
391 393 div.style.backgroundColor='#fee';
392 394 document.body.appendChild(div);
393 395 }
394 396 YUD.setStyle(div, 'opacity', 0);
395 397 div.innerHTML = contents;
396 398 div.style.top=(y + 5) + "px";
397 399 div.style.left=(x + 5) + "px";
398 400
399 401 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
400 402 anim.animate();
401 403 }
402 404
403 405 /**
404 406 * This function will detect if selected period has some changesets
405 407 for this user if it does this data is then pushed for displaying
406 408 Additionally it will only display users that are selected by the checkbox
407 409 */
408 410 function getDataAccordingToRanges(ranges) {
409 411
410 412 var data = [];
411 413 var keys = [];
412 414 for(var key in dataset){
413 415 var push = false;
414 416
415 417 //method1 slow !!
416 418 //*
417 419 for(var ds in dataset[key].data){
418 420 commit_data = dataset[key].data[ds];
419 421 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
420 422 push = true;
421 423 break;
422 424 }
423 425 }
424 426 //*/
425 427
426 428 /*//method2 sorted commit data !!!
427 429
428 430 var first_commit = dataset[key].data[0].time;
429 431 var last_commit = dataset[key].data[dataset[key].data.length-1].time;
430 432
431 433 if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){
432 434 push = true;
433 435 }
434 436 //*/
435 437
436 438 if(push){
437 439 data.push(dataset[key]);
438 440 }
439 441 }
440 442 if(data.length >= 1){
441 443 return data;
442 444 }
443 445 else{
444 446 //just return dummy data for graph to plot itself
445 447 return [getDummyData('')];
446 448 }
447 449
448 450 }
449 451
450 452 /**
451 453 * redraw using new checkbox data
452 454 */
453 455 function plotchoiced(e,args){
454 456 var cur_data = args[0];
455 457 var cur_ranges = args[1];
456 458
457 459 var new_data = [];
458 460 var inputs = choiceContainer.getElementsByTagName("input");
459 461
460 462 //show only checked labels
461 463 for(var i=0; i<inputs.length; i++) {
462 464 var checkbox_key = inputs[i].name;
463 465
464 466 if(inputs[i].checked){
465 467 for(var d in cur_data){
466 468 if(cur_data[d].label == checkbox_key){
467 469 new_data.push(cur_data[d]);
468 470 }
469 471 }
470 472 }
471 473 else{
472 474 //push dummy data to not hide the label
473 475 new_data.push(getDummyData(checkbox_key));
474 476 }
475 477 }
476 478
477 479 var new_options = YAHOO.lang.merge(plot_options, {
478 480 xaxis: {
479 481 min: cur_ranges.xaxis.from,
480 482 max: cur_ranges.xaxis.to,
481 483 mode:"time",
482 484 timeformat: "%d/%m",
483 485 },
484 486 });
485 487 if (!new_data){
486 488 new_data = [[0,1]];
487 489 }
488 490 // do the zooming
489 491 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
490 492
491 493 plot.subscribe("plotselected", plotselected);
492 494
493 495 //resubscribe plothover
494 496 plot.subscribe("plothover", plothover);
495 497
496 498 // don't fire event on the overview to prevent eternal loop
497 499 overview.setSelection(cur_ranges, true);
498 500
499 501 }
500 502
501 503 /**
502 504 * plot only selected items from overview
503 505 * @param ranges
504 506 * @returns
505 507 */
506 508 function plotselected(ranges,cur_data) {
507 509 //updates the data for new plot
508 510 data = getDataAccordingToRanges(ranges);
509 511 generateCheckboxes(data);
510 512
511 513 var new_options = YAHOO.lang.merge(plot_options, {
512 514 xaxis: {
513 515 min: ranges.xaxis.from,
514 516 max: ranges.xaxis.to,
515 517 mode:"time",
516 518 timeformat: "%d/%m",
517 519 },
518 520 yaxis: {
519 521 min: ranges.yaxis.from,
520 522 max: ranges.yaxis.to,
521 523 },
522 524
523 525 });
524 526 // do the zooming
525 527 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
526 528
527 529 plot.subscribe("plotselected", plotselected);
528 530
529 531 //resubscribe plothover
530 532 plot.subscribe("plothover", plothover);
531 533
532 534 // don't fire event on the overview to prevent eternal loop
533 535 overview.setSelection(ranges, true);
534 536
535 537 //resubscribe choiced
536 538 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
537 539 }
538 540
539 541 var previousPoint = null;
540 542
541 543 function plothover(o) {
542 544 var pos = o.pos;
543 545 var item = o.item;
544 546
545 547 //YUD.get("x").innerHTML = pos.x.toFixed(2);
546 548 //YUD.get("y").innerHTML = pos.y.toFixed(2);
547 549 if (item) {
548 550 if (previousPoint != item.datapoint) {
549 551 previousPoint = item.datapoint;
550 552
551 553 var tooltip = YUD.get("tooltip");
552 554 if(tooltip) {
553 555 tooltip.parentNode.removeChild(tooltip);
554 556 }
555 557 var x = item.datapoint.x.toFixed(2);
556 558 var y = item.datapoint.y.toFixed(2);
557 559
558 560 if (!item.series.label){
559 561 item.series.label = 'commits';
560 562 }
561 563 var d = new Date(x*1000);
562 564 var fd = d.toDateString()
563 565 var nr_commits = parseInt(y);
564 566
565 567 var cur_data = dataset[item.series.label].data[item.dataIndex];
566 568 var added = cur_data.added;
567 569 var changed = cur_data.changed;
568 570 var removed = cur_data.removed;
569 571
570 572 var nr_commits_suffix = " ${_('commits')} ";
571 573 var added_suffix = " ${_('files added')} ";
572 574 var changed_suffix = " ${_('files changed')} ";
573 575 var removed_suffix = " ${_('files removed')} ";
574 576
575 577
576 578 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
577 579 if(added==1){added_suffix=" ${_('file added')} ";}
578 580 if(changed==1){changed_suffix=" ${_('file changed')} ";}
579 581 if(removed==1){removed_suffix=" ${_('file removed')} ";}
580 582
581 583 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
582 584 +'<br/>'+
583 585 nr_commits + nr_commits_suffix+'<br/>'+
584 586 added + added_suffix +'<br/>'+
585 587 changed + changed_suffix + '<br/>'+
586 588 removed + removed_suffix + '<br/>');
587 589 }
588 590 }
589 591 else {
590 592 var tooltip = YUD.get("tooltip");
591 593
592 594 if(tooltip) {
593 595 tooltip.parentNode.removeChild(tooltip);
594 596 }
595 597 previousPoint = null;
596 598 }
597 599 }
598 600
599 601 /**
600 602 * MAIN EXECUTION
601 603 */
602 604
603 605 var data = getDataAccordingToRanges(initial_ranges);
604 606 generateCheckboxes(data);
605 607
606 608 //main plot
607 609 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
608 610
609 611 //overview
610 612 var overview = YAHOO.widget.Flot(overviewContainer, overview_dataset, overview_options);
611 613
612 614 //show initial selection on overview
613 615 overview.setSelection(initial_ranges);
614 616
615 617 plot.subscribe("plotselected", plotselected);
616 618
617 619 overview.subscribe("plotselected", function (ranges) {
618 620 plot.setSelection(ranges);
619 621 });
620 622
621 623 plot.subscribe("plothover", plothover);
622 624
623 625 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
624 626 }
625 627 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
626 628 </script>
627 629
628 630 </div>
629 631 </div>
630 632
631 633 <div class="box">
632 634 <div class="title">
633 635 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
634 636 </div>
635 637 <div class="table">
636 638 <div id="shortlog_data">
637 639 <%include file='../shortlog/shortlog_data.html'/>
638 640 </div>
639 641 ##%if c.repo_changesets:
640 642 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
641 643 ##%endif
642 644 </div>
643 645 </div>
644 646 <div class="box">
645 647 <div class="title">
646 648 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
647 649 </div>
648 650 <div class="table">
649 651 <%include file='../tags/tags_data.html'/>
650 652 %if c.repo_changesets:
651 653 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
652 654 %endif
653 655 </div>
654 656 </div>
655 657 <div class="box">
656 658 <div class="title">
657 659 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
658 660 </div>
659 661 <div class="table">
660 662 <%include file='../branches/branches_data.html'/>
661 663 %if c.repo_changesets:
662 664 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
663 665 %endif
664 666 </div>
665 667 </div>
666 668
667 669 </%def>
General Comments 0
You need to be logged in to leave comments. Login now