##// END OF EJS Templates
added branch/tag options to download links in summary
marcink -
r942:32318ec7 beta
parent child Browse files
Show More
@@ -1,273 +1,281 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.files
3 rhodecode.controllers.files
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Files controller for RhodeCode
6 Files controller for RhodeCode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27 import tempfile
27 import tempfile
28 import logging
28 import logging
29 import rhodecode.lib.helpers as h
29 import rhodecode.lib.helpers as h
30
30
31 from mercurial import archival
31 from mercurial import archival
32
32
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from pylons.controllers.util import redirect
35 from pylons.controllers.util import redirect
36
36
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 from rhodecode.lib.base import BaseController, render
38 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.utils import EmptyChangeset
39 from rhodecode.lib.utils import EmptyChangeset
40 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.scm import ScmModel
41
41
42 from vcs.exceptions import RepositoryError, ChangesetError, \
42 from vcs.exceptions import RepositoryError, ChangesetError, \
43 ChangesetDoesNotExistError, EmptyRepositoryError
43 ChangesetDoesNotExistError, EmptyRepositoryError
44 from vcs.nodes import FileNode
44 from vcs.nodes import FileNode
45 from vcs.utils import diffs as differ
45 from vcs.utils import diffs as differ
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49 class FilesController(BaseController):
49 class FilesController(BaseController):
50
50
51 @LoginRequired()
51 @LoginRequired()
52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
53 'repository.admin')
53 'repository.admin')
54 def __before__(self):
54 def __before__(self):
55 super(FilesController, self).__before__()
55 super(FilesController, self).__before__()
56 c.cut_off_limit = self.cut_off_limit
56 c.cut_off_limit = self.cut_off_limit
57
57
58 def index(self, repo_name, revision, f_path):
58 def index(self, repo_name, revision, f_path):
59 hg_model = ScmModel()
59 hg_model = ScmModel()
60 c.repo = hg_model.get_repo(c.repo_name)
60 c.repo = hg_model.get_repo(c.repo_name)
61
61
62 try:
62 try:
63 #reditect to given revision from form
63 #reditect to given revision from form
64 post_revision = request.POST.get('at_rev', None)
64 post_revision = request.POST.get('at_rev', None)
65 if post_revision:
65 if post_revision:
66 post_revision = c.repo.get_changeset(post_revision).raw_id
66 post_revision = c.repo.get_changeset(post_revision).raw_id
67 redirect(url('files_home', repo_name=c.repo_name,
67 redirect(url('files_home', repo_name=c.repo_name,
68 revision=post_revision, f_path=f_path))
68 revision=post_revision, f_path=f_path))
69
69
70 c.branch = request.GET.get('branch', None)
70 c.branch = request.GET.get('branch', None)
71
71
72 c.f_path = f_path
72 c.f_path = f_path
73
73
74 c.changeset = c.repo.get_changeset(revision)
74 c.changeset = c.repo.get_changeset(revision)
75 cur_rev = c.changeset.revision
75 cur_rev = c.changeset.revision
76
76
77 #prev link
77 #prev link
78 try:
78 try:
79 prev_rev = c.repo.get_changeset(cur_rev).prev(c.branch).raw_id
79 prev_rev = c.repo.get_changeset(cur_rev).prev(c.branch).raw_id
80 c.url_prev = url('files_home', repo_name=c.repo_name,
80 c.url_prev = url('files_home', repo_name=c.repo_name,
81 revision=prev_rev, f_path=f_path)
81 revision=prev_rev, f_path=f_path)
82 if c.branch:
82 if c.branch:
83 c.url_prev += '?branch=%s' % c.branch
83 c.url_prev += '?branch=%s' % c.branch
84 except ChangesetDoesNotExistError:
84 except ChangesetDoesNotExistError:
85 c.url_prev = '#'
85 c.url_prev = '#'
86
86
87 #next link
87 #next link
88 try:
88 try:
89 next_rev = c.repo.get_changeset(cur_rev).next(c.branch).raw_id
89 next_rev = c.repo.get_changeset(cur_rev).next(c.branch).raw_id
90 c.url_next = url('files_home', repo_name=c.repo_name,
90 c.url_next = url('files_home', repo_name=c.repo_name,
91 revision=next_rev, f_path=f_path)
91 revision=next_rev, f_path=f_path)
92 if c.branch:
92 if c.branch:
93 c.url_next += '?branch=%s' % c.branch
93 c.url_next += '?branch=%s' % c.branch
94 except ChangesetDoesNotExistError:
94 except ChangesetDoesNotExistError:
95 c.url_next = '#'
95 c.url_next = '#'
96
96
97 #files
97 #files
98 try:
98 try:
99 c.files_list = c.changeset.get_node(f_path)
99 c.files_list = c.changeset.get_node(f_path)
100 c.file_history = self._get_history(c.repo, c.files_list, f_path)
100 c.file_history = self._get_history(c.repo, c.files_list, f_path)
101 except RepositoryError, e:
101 except RepositoryError, e:
102 h.flash(str(e), category='warning')
102 h.flash(str(e), category='warning')
103 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
103 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
104
104
105 except EmptyRepositoryError, e:
105 except EmptyRepositoryError, e:
106 h.flash(_('There are no files yet'), category='warning')
106 h.flash(_('There are no files yet'), category='warning')
107 redirect(h.url('summary_home', repo_name=repo_name))
107 redirect(h.url('summary_home', repo_name=repo_name))
108
108
109 except RepositoryError, e:
109 except RepositoryError, e:
110 h.flash(str(e), category='warning')
110 h.flash(str(e), category='warning')
111 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
111 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
112
112
113
113
114
114
115 return render('files/files.html')
115 return render('files/files.html')
116
116
117 def rawfile(self, repo_name, revision, f_path):
117 def rawfile(self, repo_name, revision, f_path):
118 hg_model = ScmModel()
118 hg_model = ScmModel()
119 c.repo = hg_model.get_repo(c.repo_name)
119 c.repo = hg_model.get_repo(c.repo_name)
120 file_node = c.repo.get_changeset(revision).get_node(f_path)
120 file_node = c.repo.get_changeset(revision).get_node(f_path)
121 response.content_type = file_node.mimetype
121 response.content_type = file_node.mimetype
122 response.content_disposition = 'attachment; filename=%s' \
122 response.content_disposition = 'attachment; filename=%s' \
123 % f_path.split('/')[-1]
123 % f_path.split('/')[-1]
124 return file_node.content
124 return file_node.content
125
125
126 def raw(self, repo_name, revision, f_path):
126 def raw(self, repo_name, revision, f_path):
127 hg_model = ScmModel()
127 hg_model = ScmModel()
128 c.repo = hg_model.get_repo(c.repo_name)
128 c.repo = hg_model.get_repo(c.repo_name)
129 file_node = c.repo.get_changeset(revision).get_node(f_path)
129 file_node = c.repo.get_changeset(revision).get_node(f_path)
130 response.content_type = 'text/plain'
130 response.content_type = 'text/plain'
131
131
132 return file_node.content
132 return file_node.content
133
133
134 def annotate(self, repo_name, revision, f_path):
134 def annotate(self, repo_name, revision, f_path):
135 hg_model = ScmModel()
135 hg_model = ScmModel()
136 c.repo = hg_model.get_repo(c.repo_name)
136 c.repo = hg_model.get_repo(c.repo_name)
137
137
138 try:
138 try:
139 c.cs = c.repo.get_changeset(revision)
139 c.cs = c.repo.get_changeset(revision)
140 c.file = c.cs.get_node(f_path)
140 c.file = c.cs.get_node(f_path)
141 except RepositoryError, e:
141 except RepositoryError, e:
142 h.flash(str(e), category='warning')
142 h.flash(str(e), category='warning')
143 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
143 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
144
144
145 c.file_history = self._get_history(c.repo, c.file, f_path)
145 c.file_history = self._get_history(c.repo, c.file, f_path)
146
146
147 c.f_path = f_path
147 c.f_path = f_path
148
148
149 return render('files/files_annotate.html')
149 return render('files/files_annotate.html')
150
150
151 def archivefile(self, repo_name, fname):
151 def archivefile(self, repo_name, fname):
152 info = fname.split('.')
153 revision, fileformat = info[0], '.' + '.'.join(info[1:])
154 archive_specs = {
152 archive_specs = {
155 '.tar.bz2': ('application/x-tar', 'tbz2'),
153 '.tar.bz2': ('application/x-tar', 'tbz2'),
156 '.tar.gz': ('application/x-tar', 'tgz'),
154 '.tar.gz': ('application/x-tar', 'tgz'),
157 '.zip': ('application/zip', 'zip'),
155 '.zip': ('application/zip', 'zip'),
158 }
156 }
157
158 fileformat = None
159 revision = None
160
161 for ext in archive_specs.keys():
162 archive_spec = fname.split(ext)
163 if len(archive_spec) == 2:
164 fileformat = archive_spec[1] or ext
165 revision = archive_spec[0]
166
159 if not archive_specs.has_key(fileformat):
167 if not archive_specs.has_key(fileformat):
160 return _('Unknown archive type %s') % fileformat
168 return _('Unknown archive type')
161
169
162 repo = ScmModel().get_repo(repo_name)
170 repo = ScmModel().get_repo(repo_name)
163
171
164 try:
172 try:
165 repo.get_changeset(revision)
173 repo.get_changeset(revision)
166 except ChangesetDoesNotExistError:
174 except ChangesetDoesNotExistError:
167 return _('Unknown revision %s') % revision
175 return _('Unknown revision %s') % revision
168
176
169 archive = tempfile.TemporaryFile()
177 archive = tempfile.TemporaryFile()
170 localrepo = repo.repo
178 localrepo = repo.repo
171 fname = '%s-%s%s' % (repo_name, revision, fileformat)
179 fname = '%s-%s%s' % (repo_name, revision, fileformat)
172 archival.archive(localrepo, archive, revision, archive_specs[fileformat][1],
180 archival.archive(localrepo, archive, revision, archive_specs[fileformat][1],
173 prefix='%s-%s' % (repo_name, revision))
181 prefix='%s-%s' % (repo_name, revision))
174 response.content_type = archive_specs[fileformat][0]
182 response.content_type = archive_specs[fileformat][0]
175 response.content_disposition = 'attachment; filename=%s' % fname
183 response.content_disposition = 'attachment; filename=%s' % fname
176 archive.seek(0)
184 archive.seek(0)
177
185
178 def read_in_chunks(file_object, chunk_size=1024 * 40):
186 def read_in_chunks(file_object, chunk_size=1024 * 40):
179 """Lazy function (generator) to read a file piece by piece.
187 """Lazy function (generator) to read a file piece by piece.
180 Default chunk size: 40k."""
188 Default chunk size: 40k."""
181 while True:
189 while True:
182 data = file_object.read(chunk_size)
190 data = file_object.read(chunk_size)
183 if not data:
191 if not data:
184 break
192 break
185 yield data
193 yield data
186
194
187 return read_in_chunks(archive)
195 return read_in_chunks(archive)
188
196
189 def diff(self, repo_name, f_path):
197 def diff(self, repo_name, f_path):
190 hg_model = ScmModel()
198 hg_model = ScmModel()
191 diff1 = request.GET.get('diff1')
199 diff1 = request.GET.get('diff1')
192 diff2 = request.GET.get('diff2')
200 diff2 = request.GET.get('diff2')
193 c.action = request.GET.get('diff')
201 c.action = request.GET.get('diff')
194 c.no_changes = diff1 == diff2
202 c.no_changes = diff1 == diff2
195 c.f_path = f_path
203 c.f_path = f_path
196 c.repo = hg_model.get_repo(c.repo_name)
204 c.repo = hg_model.get_repo(c.repo_name)
197
205
198 try:
206 try:
199 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
207 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
200 c.changeset_1 = c.repo.get_changeset(diff1)
208 c.changeset_1 = c.repo.get_changeset(diff1)
201 node1 = c.changeset_1.get_node(f_path)
209 node1 = c.changeset_1.get_node(f_path)
202 else:
210 else:
203 c.changeset_1 = EmptyChangeset()
211 c.changeset_1 = EmptyChangeset()
204 node1 = FileNode('.', '', changeset=c.changeset_1)
212 node1 = FileNode('.', '', changeset=c.changeset_1)
205
213
206 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
214 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
207 c.changeset_2 = c.repo.get_changeset(diff2)
215 c.changeset_2 = c.repo.get_changeset(diff2)
208 node2 = c.changeset_2.get_node(f_path)
216 node2 = c.changeset_2.get_node(f_path)
209 else:
217 else:
210 c.changeset_2 = EmptyChangeset()
218 c.changeset_2 = EmptyChangeset()
211 node2 = FileNode('.', '', changeset=c.changeset_2)
219 node2 = FileNode('.', '', changeset=c.changeset_2)
212 except RepositoryError:
220 except RepositoryError:
213 return redirect(url('files_home',
221 return redirect(url('files_home',
214 repo_name=c.repo_name, f_path=f_path))
222 repo_name=c.repo_name, f_path=f_path))
215
223
216 f_udiff = differ.get_udiff(node1, node2)
224 f_udiff = differ.get_udiff(node1, node2)
217 diff = differ.DiffProcessor(f_udiff)
225 diff = differ.DiffProcessor(f_udiff)
218
226
219 if c.action == 'download':
227 if c.action == 'download':
220 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
228 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
221 response.content_type = 'text/plain'
229 response.content_type = 'text/plain'
222 response.content_disposition = 'attachment; filename=%s' \
230 response.content_disposition = 'attachment; filename=%s' \
223 % diff_name
231 % diff_name
224 return diff.raw_diff()
232 return diff.raw_diff()
225
233
226 elif c.action == 'raw':
234 elif c.action == 'raw':
227 response.content_type = 'text/plain'
235 response.content_type = 'text/plain'
228 return diff.raw_diff()
236 return diff.raw_diff()
229
237
230 elif c.action == 'diff':
238 elif c.action == 'diff':
231 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
239 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
232 c.cur_diff = _('Diff is to big to display')
240 c.cur_diff = _('Diff is to big to display')
233 else:
241 else:
234 c.cur_diff = diff.as_html()
242 c.cur_diff = diff.as_html()
235 else:
243 else:
236 #default option
244 #default option
237 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
245 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
238 c.cur_diff = _('Diff is to big to display')
246 c.cur_diff = _('Diff is to big to display')
239 else:
247 else:
240 c.cur_diff = diff.as_html()
248 c.cur_diff = diff.as_html()
241
249
242 if not c.cur_diff: c.no_changes = True
250 if not c.cur_diff: c.no_changes = True
243 return render('files/file_diff.html')
251 return render('files/file_diff.html')
244
252
245 def _get_history(self, repo, node, f_path):
253 def _get_history(self, repo, node, f_path):
246 from vcs.nodes import NodeKind
254 from vcs.nodes import NodeKind
247 if not node.kind is NodeKind.FILE:
255 if not node.kind is NodeKind.FILE:
248 return []
256 return []
249 changesets = node.history
257 changesets = node.history
250 hist_l = []
258 hist_l = []
251
259
252 changesets_group = ([], _("Changesets"))
260 changesets_group = ([], _("Changesets"))
253 branches_group = ([], _("Branches"))
261 branches_group = ([], _("Branches"))
254 tags_group = ([], _("Tags"))
262 tags_group = ([], _("Tags"))
255
263
256 for chs in changesets:
264 for chs in changesets:
257 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
265 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
258 changesets_group[0].append((chs.raw_id, n_desc,))
266 changesets_group[0].append((chs.raw_id, n_desc,))
259
267
260 hist_l.append(changesets_group)
268 hist_l.append(changesets_group)
261
269
262 for name, chs in c.repository_branches.items():
270 for name, chs in c.repository_branches.items():
263 #chs = chs.split(':')[-1]
271 #chs = chs.split(':')[-1]
264 branches_group[0].append((chs, name),)
272 branches_group[0].append((chs, name),)
265 hist_l.append(branches_group)
273 hist_l.append(branches_group)
266
274
267 for name, chs in c.repository_tags.items():
275 for name, chs in c.repository_tags.items():
268 #chs = chs.split(':')[-1]
276 #chs = chs.split(':')[-1]
269 tags_group[0].append((chs, name),)
277 tags_group[0].append((chs, name),)
270 hist_l.append(tags_group)
278 hist_l.append(tags_group)
271
279
272 return hist_l
280 return hist_l
273
281
@@ -1,146 +1,168 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.summary
3 rhodecode.controllers.summary
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Summary controller for Rhodecode
6 Summary controller for Rhodecode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import calendar
28 import calendar
29 import logging
29 import logging
30 from time import mktime
30 from time import mktime
31 from datetime import datetime, timedelta, date
31 from datetime import datetime, timedelta, date
32
32
33 from vcs.exceptions import ChangesetError
33 from vcs.exceptions import ChangesetError
34
34
35 from pylons import tmpl_context as c, request, url
35 from pylons import tmpl_context as c, request, url
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.db import Statistics
39 from rhodecode.model.db import Statistics
40
40
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.utils import OrderedDict, EmptyChangeset
43 from rhodecode.lib.utils import OrderedDict, EmptyChangeset
44
44
45 from rhodecode.lib.celerylib import run_task
45 from rhodecode.lib.celerylib import run_task
46 from rhodecode.lib.celerylib.tasks import get_commits_stats
46 from rhodecode.lib.celerylib.tasks import get_commits_stats
47
47
48 from webhelpers.paginate import Page
48 from webhelpers.paginate import Page
49
49
50 try:
50 try:
51 import json
51 import json
52 except ImportError:
52 except ImportError:
53 #python 2.5 compatibility
53 #python 2.5 compatibility
54 import simplejson as json
54 import simplejson as json
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57 class SummaryController(BaseController):
57 class SummaryController(BaseController):
58
58
59 @LoginRequired()
59 @LoginRequired()
60 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
60 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
61 'repository.admin')
61 'repository.admin')
62 def __before__(self):
62 def __before__(self):
63 super(SummaryController, self).__before__()
63 super(SummaryController, self).__before__()
64
64
65 def index(self):
65 def index(self):
66 scm_model = ScmModel()
66 scm_model = ScmModel()
67 c.repo_info = scm_model.get_repo(c.repo_name)
67 c.repo_info = scm_model.get_repo(c.repo_name)
68 c.following = scm_model.is_following_repo(c.repo_name,
68 c.following = scm_model.is_following_repo(c.repo_name,
69 c.rhodecode_user.user_id)
69 c.rhodecode_user.user_id)
70 def url_generator(**kw):
70 def url_generator(**kw):
71 return url('shortlog_home', repo_name=c.repo_name, **kw)
71 return url('shortlog_home', repo_name=c.repo_name, **kw)
72
72
73 c.repo_changesets = Page(c.repo_info, page=1, items_per_page=10,
73 c.repo_changesets = Page(c.repo_info, page=1, items_per_page=10,
74 url=url_generator)
74 url=url_generator)
75
75
76 e = request.environ
76 e = request.environ
77
77
78 if self.rhodecode_user.username == 'default':
78 if self.rhodecode_user.username == 'default':
79 #for default(anonymous) user we don't need to pass credentials
79 #for default(anonymous) user we don't need to pass credentials
80 username = ''
80 username = ''
81 password = ''
81 password = ''
82 else:
82 else:
83 username = str(c.rhodecode_user.username)
83 username = str(c.rhodecode_user.username)
84 password = '@'
84 password = '@'
85
85
86 uri = u'%(protocol)s://%(user)s%(password)s%(host)s%(prefix)s/%(repo_name)s' % {
86 uri = u'%(protocol)s://%(user)s%(password)s%(host)s%(prefix)s/%(repo_name)s' % {
87 'protocol': e.get('wsgi.url_scheme'),
87 'protocol': e.get('wsgi.url_scheme'),
88 'user':username,
88 'user':username,
89 'password':password,
89 'password':password,
90 'host':e.get('HTTP_HOST'),
90 'host':e.get('HTTP_HOST'),
91 'prefix':e.get('SCRIPT_NAME'),
91 'prefix':e.get('SCRIPT_NAME'),
92 'repo_name':c.repo_name, }
92 'repo_name':c.repo_name, }
93 c.clone_repo_url = uri
93 c.clone_repo_url = uri
94 c.repo_tags = OrderedDict()
94 c.repo_tags = OrderedDict()
95 for name, hash in c.repo_info.tags.items()[:10]:
95 for name, hash in c.repo_info.tags.items()[:10]:
96 try:
96 try:
97 c.repo_tags[name] = c.repo_info.get_changeset(hash)
97 c.repo_tags[name] = c.repo_info.get_changeset(hash)
98 except ChangesetError:
98 except ChangesetError:
99 c.repo_tags[name] = EmptyChangeset(hash)
99 c.repo_tags[name] = EmptyChangeset(hash)
100
100
101 c.repo_branches = OrderedDict()
101 c.repo_branches = OrderedDict()
102 for name, hash in c.repo_info.branches.items()[:10]:
102 for name, hash in c.repo_info.branches.items()[:10]:
103 try:
103 try:
104 c.repo_branches[name] = c.repo_info.get_changeset(hash)
104 c.repo_branches[name] = c.repo_info.get_changeset(hash)
105 except ChangesetError:
105 except ChangesetError:
106 c.repo_branches[name] = EmptyChangeset(hash)
106 c.repo_branches[name] = EmptyChangeset(hash)
107
107
108 td = date.today() + timedelta(days=1)
108 td = date.today() + timedelta(days=1)
109 td_1m = td - timedelta(days=calendar.mdays[td.month])
109 td_1m = td - timedelta(days=calendar.mdays[td.month])
110 td_1y = td - timedelta(days=365)
110 td_1y = td - timedelta(days=365)
111
111
112 ts_min_m = mktime(td_1m.timetuple())
112 ts_min_m = mktime(td_1m.timetuple())
113 ts_min_y = mktime(td_1y.timetuple())
113 ts_min_y = mktime(td_1y.timetuple())
114 ts_max_y = mktime(td.timetuple())
114 ts_max_y = mktime(td.timetuple())
115
115
116 if c.repo_info.dbrepo.enable_statistics:
116 if c.repo_info.dbrepo.enable_statistics:
117 c.no_data_msg = _('No data loaded yet')
117 c.no_data_msg = _('No data loaded yet')
118 run_task(get_commits_stats, c.repo_info.name, ts_min_y, ts_max_y)
118 run_task(get_commits_stats, c.repo_info.name, ts_min_y, ts_max_y)
119 else:
119 else:
120 c.no_data_msg = _('Statistics update are disabled for this repository')
120 c.no_data_msg = _('Statistics update are disabled for this repository')
121 c.ts_min = ts_min_m
121 c.ts_min = ts_min_m
122 c.ts_max = ts_max_y
122 c.ts_max = ts_max_y
123
123
124 stats = self.sa.query(Statistics)\
124 stats = self.sa.query(Statistics)\
125 .filter(Statistics.repository == c.repo_info.dbrepo)\
125 .filter(Statistics.repository == c.repo_info.dbrepo)\
126 .scalar()
126 .scalar()
127
127
128
128
129 if stats and stats.languages:
129 if stats and stats.languages:
130 c.no_data = False is c.repo_info.dbrepo.enable_statistics
130 c.no_data = False is c.repo_info.dbrepo.enable_statistics
131 lang_stats = json.loads(stats.languages)
131 lang_stats = json.loads(stats.languages)
132 c.commit_data = stats.commit_activity
132 c.commit_data = stats.commit_activity
133 c.overview_data = stats.commit_activity_combined
133 c.overview_data = stats.commit_activity_combined
134 c.trending_languages = json.dumps(OrderedDict(
134 c.trending_languages = json.dumps(OrderedDict(
135 sorted(lang_stats.items(), reverse=True,
135 sorted(lang_stats.items(), reverse=True,
136 key=lambda k: k[1])[:10]
136 key=lambda k: k[1])[:10]
137 )
137 )
138 )
138 )
139 else:
139 else:
140 c.commit_data = json.dumps({})
140 c.commit_data = json.dumps({})
141 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10] ])
141 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10] ])
142 c.trending_languages = json.dumps({})
142 c.trending_languages = json.dumps({})
143 c.no_data = True
143 c.no_data = True
144
144
145 c.download_options = self._get_download_links(c.repo_info)
146
145 return render('summary/summary.html')
147 return render('summary/summary.html')
146
148
149
150
151 def _get_download_links(self, repo):
152
153 download_l = []
154
155 branches_group = ([], _("Branches"))
156 tags_group = ([], _("Tags"))
157
158 for name, chs in c.repository_branches.items():
159 #chs = chs.split(':')[-1]
160 branches_group[0].append((chs, name),)
161 download_l.append(branches_group)
162
163 for name, chs in c.repository_tags.items():
164 #chs = chs.split(':')[-1]
165 tags_group[0].append((chs, name),)
166 download_l.append(tags_group)
167
168 return download_l
@@ -1,647 +1,652 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(u'Home',h.url('/'))}
8 ${h.link_to(u'Home',h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('summary')}
12 ${_('summary')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('summary')}
16 ${self.menu('summary')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box box-left">
20 <div class="box box-left">
21 <!-- box / title -->
21 <!-- box / title -->
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25 <!-- end box / title -->
25 <!-- end box / title -->
26 <div class="form">
26 <div class="form">
27 <div class="fields">
27 <div class="fields">
28
28
29 <div class="field">
29 <div class="field">
30 <div class="label">
30 <div class="label">
31 <label>${_('Name')}:</label>
31 <label>${_('Name')}:</label>
32 </div>
32 </div>
33 <div class="input-short">
33 <div class="input-short">
34 %if c.repo_info.dbrepo.repo_type =='hg':
34 %if c.repo_info.dbrepo.repo_type =='hg':
35 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="/images/icons/hgicon.png"/>
35 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="/images/icons/hgicon.png"/>
36 %endif
36 %endif
37 %if c.repo_info.dbrepo.repo_type =='git':
37 %if c.repo_info.dbrepo.repo_type =='git':
38 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="/images/icons/giticon.png"/>
38 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="/images/icons/giticon.png"/>
39 %endif
39 %endif
40
40
41 %if c.repo_info.dbrepo.private:
41 %if c.repo_info.dbrepo.private:
42 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="/images/icons/lock.png"/>
42 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="/images/icons/lock.png"/>
43 %else:
43 %else:
44 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="/images/icons/lock_open.png"/>
44 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="/images/icons/lock_open.png"/>
45 %endif
45 %endif
46 <span style="font-size: 1.6em;font-weight: bold;vertical-align: baseline;">${c.repo_info.name}</span>
46 <span style="font-size: 1.6em;font-weight: bold;vertical-align: baseline;">${c.repo_info.name}</span>
47 %if c.rhodecode_user.username != 'default':
47 %if c.rhodecode_user.username != 'default':
48 %if c.following:
48 %if c.following:
49 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
49 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
50 onclick="javascript:toggleFollowingRepo(${c.repo_info.dbrepo.repo_id},'${str(h.get_token())}')">
50 onclick="javascript:toggleFollowingRepo(${c.repo_info.dbrepo.repo_id},'${str(h.get_token())}')">
51 </span>
51 </span>
52 %else:
52 %else:
53 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
53 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
54 onclick="javascript:toggleFollowingRepo(${c.repo_info.dbrepo.repo_id},'${str(h.get_token())}')">
54 onclick="javascript:toggleFollowingRepo(${c.repo_info.dbrepo.repo_id},'${str(h.get_token())}')">
55 </span>
55 </span>
56 %endif
56 %endif
57 %endif:
57 %endif:
58 <br/>
58 <br/>
59 %if c.repo_info.dbrepo.fork:
59 %if c.repo_info.dbrepo.fork:
60 <span style="margin-top:5px">
60 <span style="margin-top:5px">
61 <a href="${h.url('summary_home',repo_name=c.repo_info.dbrepo.fork.repo_name)}">
61 <a href="${h.url('summary_home',repo_name=c.repo_info.dbrepo.fork.repo_name)}">
62 <img class="icon" alt="${_('public')}"
62 <img class="icon" alt="${_('public')}"
63 title="${_('Fork of')} ${c.repo_info.dbrepo.fork.repo_name}"
63 title="${_('Fork of')} ${c.repo_info.dbrepo.fork.repo_name}"
64 src="/images/icons/arrow_divide.png"/>
64 src="/images/icons/arrow_divide.png"/>
65 ${_('Fork of')} ${c.repo_info.dbrepo.fork.repo_name}
65 ${_('Fork of')} ${c.repo_info.dbrepo.fork.repo_name}
66 </a>
66 </a>
67 </span>
67 </span>
68 %endif
68 %endif
69 </div>
69 </div>
70 </div>
70 </div>
71
71
72
72
73 <div class="field">
73 <div class="field">
74 <div class="label">
74 <div class="label">
75 <label>${_('Description')}:</label>
75 <label>${_('Description')}:</label>
76 </div>
76 </div>
77 <div class="input-short">
77 <div class="input-short">
78 ${c.repo_info.dbrepo.description}
78 ${c.repo_info.dbrepo.description}
79 </div>
79 </div>
80 </div>
80 </div>
81
81
82
82
83 <div class="field">
83 <div class="field">
84 <div class="label">
84 <div class="label">
85 <label>${_('Contact')}:</label>
85 <label>${_('Contact')}:</label>
86 </div>
86 </div>
87 <div class="input-short">
87 <div class="input-short">
88 <div class="gravatar">
88 <div class="gravatar">
89 <img alt="gravatar" src="${h.gravatar_url(c.repo_info.dbrepo.user.email)}"/>
89 <img alt="gravatar" src="${h.gravatar_url(c.repo_info.dbrepo.user.email)}"/>
90 </div>
90 </div>
91 ${_('Username')}: ${c.repo_info.dbrepo.user.username}<br/>
91 ${_('Username')}: ${c.repo_info.dbrepo.user.username}<br/>
92 ${_('Name')}: ${c.repo_info.dbrepo.user.name} ${c.repo_info.dbrepo.user.lastname}<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>
93 ${_('Email')}: <a href="mailto:${c.repo_info.dbrepo.user.email}">${c.repo_info.dbrepo.user.email}</a>
94 </div>
94 </div>
95 </div>
95 </div>
96
96
97 <div class="field">
97 <div class="field">
98 <div class="label">
98 <div class="label">
99 <label>${_('Last change')}:</label>
99 <label>${_('Last change')}:</label>
100 </div>
100 </div>
101 <div class="input-short">
101 <div class="input-short">
102 ${h.age(c.repo_info.last_change)} - ${c.repo_info.last_change}
102 ${h.age(c.repo_info.last_change)} - ${c.repo_info.last_change}
103 ${_('by')} ${h.get_changeset_safe(c.repo_info,'tip').author}
103 ${_('by')} ${h.get_changeset_safe(c.repo_info,'tip').author}
104
104
105 </div>
105 </div>
106 </div>
106 </div>
107
107
108 <div class="field">
108 <div class="field">
109 <div class="label">
109 <div class="label">
110 <label>${_('Clone url')}:</label>
110 <label>${_('Clone url')}:</label>
111 </div>
111 </div>
112 <div class="input-short">
112 <div class="input-short">
113 <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/>
113 <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/>
114 </div>
114 </div>
115 </div>
115 </div>
116
116
117 <div class="field">
117 <div class="field">
118 <div class="label">
118 <div class="label">
119 <label>${_('Trending source files')}:</label>
119 <label>${_('Trending source files')}:</label>
120 </div>
120 </div>
121 <div class="input-short">
121 <div class="input-short">
122 <div id="lang_stats">
122 <div id="lang_stats"></div>
123
123 </div>
124 </div>
124 </div>
125
126 <div class="field">
127 <div class="label">
128 <label>${_('Download')}:</label>
129 </div>
130 <div class="input-short">
131
132 ${h.select('download_options','tip',c.download_options)}
133
134 %for cnt,archive in enumerate(c.repo_info._get_archives()):
135 %if cnt >=1:
136 |
137 %endif
138 ${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")}
141 %endfor
142 </div>
143 </div>
144
145 <div class="field">
146 <div class="label">
147 <label>${_('Feeds')}:</label>
148 </div>
149 <div class="input-short">
150 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo_info.name),class_='rss_icon')}
151 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo_info.name),class_='atom_icon')}
152 </div>
153 </div>
154 </div>
155 </div>
125 <script type="text/javascript">
156 <script type="text/javascript">
126 YUE.onDOMReady(function(e){
157 YUE.onDOMReady(function(e){
127 id = 'clone_url';
158 id = 'clone_url';
128 YUE.on(id,'click',function(e){
159 YUE.on(id,'click',function(e){
129 YUD.get('clone_url').select();
160 YUD.get('clone_url').select();
130 })
161 })
131 })
162 })
132 var data = ${c.trending_languages|n};
163 var data = ${c.trending_languages|n};
133 var total = 0;
164 var total = 0;
134 var no_data = true;
165 var no_data = true;
135 for (k in data){
166 for (k in data){
136 total += data[k];
167 total += data[k];
137 no_data = false;
168 no_data = false;
138 }
169 }
139 var tbl = document.createElement('table');
170 var tbl = document.createElement('table');
140 tbl.setAttribute('class','trending_language_tbl');
171 tbl.setAttribute('class','trending_language_tbl');
141 var cnt =0;
172 var cnt =0;
142 for (k in data){
173 for (k in data){
143 cnt+=1;
174 cnt+=1;
144 var hide = cnt>2;
175 var hide = cnt>2;
145 var tr = document.createElement('tr');
176 var tr = document.createElement('tr');
146 if (hide){
177 if (hide){
147 tr.setAttribute('style','display:none');
178 tr.setAttribute('style','display:none');
148 tr.setAttribute('class','stats_hidden');
179 tr.setAttribute('class','stats_hidden');
149 }
180 }
150 var percentage = Math.round((data[k]/total*100),2);
181 var percentage = Math.round((data[k]/total*100),2);
151 var value = data[k];
182 var value = data[k];
152 var td1 = document.createElement('td');
183 var td1 = document.createElement('td');
153 td1.width=150;
184 td1.width=150;
154 var trending_language_label = document.createElement('div');
185 var trending_language_label = document.createElement('div');
155 trending_language_label.innerHTML = k;
186 trending_language_label.innerHTML = k;
156 td1.appendChild(trending_language_label);
187 td1.appendChild(trending_language_label);
157
188
158 var td2 = document.createElement('td');
189 var td2 = document.createElement('td');
159 td2.setAttribute('style','padding-right:14px !important');
190 td2.setAttribute('style','padding-right:14px !important');
160 var trending_language = document.createElement('div');
191 var trending_language = document.createElement('div');
161 var nr_files = value+" ${_('files')}";
192 var nr_files = value+" ${_('files')}";
162
193
163 trending_language.title = k+" "+nr_files;
194 trending_language.title = k+" "+nr_files;
164
195
165 if (percentage>20){
196 if (percentage>20){
166 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
197 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
167 }
198 }
168 else{
199 else{
169 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
200 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
170 }
201 }
171
202
172 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
203 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
173 trending_language.style.width=percentage+"%";
204 trending_language.style.width=percentage+"%";
174 td2.appendChild(trending_language);
205 td2.appendChild(trending_language);
175
206
176 tr.appendChild(td1);
207 tr.appendChild(td1);
177 tr.appendChild(td2);
208 tr.appendChild(td2);
178 tbl.appendChild(tr);
209 tbl.appendChild(tr);
179 if(cnt == 2){
210 if(cnt == 2){
180 var show_more = document.createElement('tr');
211 var show_more = document.createElement('tr');
181 var td=document.createElement('td');
212 var td=document.createElement('td');
182 lnk = document.createElement('a');
213 lnk = document.createElement('a');
183 lnk.href='#';
214 lnk.href='#';
184 lnk.innerHTML = "${_("show more")}";
215 lnk.innerHTML = "${_("show more")}";
185 lnk.id='code_stats_show_more';
216 lnk.id='code_stats_show_more';
186 td.appendChild(lnk);
217 td.appendChild(lnk);
187 show_more.appendChild(td);
218 show_more.appendChild(td);
188 show_more.appendChild(document.createElement('td'));
219 show_more.appendChild(document.createElement('td'));
189 tbl.appendChild(show_more);
220 tbl.appendChild(show_more);
190 }
221 }
191
222
192 }
223 }
193 if(no_data){
224 if(no_data){
194 var tr = document.createElement('tr');
225 var tr = document.createElement('tr');
195 var td1 = document.createElement('td');
226 var td1 = document.createElement('td');
196 td1.innerHTML = "${c.no_data_msg}";
227 td1.innerHTML = "${c.no_data_msg}";
197 tr.appendChild(td1);
228 tr.appendChild(td1);
198 tbl.appendChild(tr);
229 tbl.appendChild(tr);
199 }
230 }
200 YUD.get('lang_stats').appendChild(tbl);
231 YUD.get('lang_stats').appendChild(tbl);
201 YUE.on('code_stats_show_more','click',function(){
232 YUE.on('code_stats_show_more','click',function(){
202 l = YUD.getElementsByClassName('stats_hidden')
233 l = YUD.getElementsByClassName('stats_hidden')
203 for (e in l){
234 for (e in l){
204 YUD.setStyle(l[e],'display','');
235 YUD.setStyle(l[e],'display','');
205 };
236 };
206 YUD.setStyle(YUD.get('code_stats_show_more'),
237 YUD.setStyle(YUD.get('code_stats_show_more'),
207 'display','none');
238 'display','none');
208 })
239 })
209
240
210 </script>
241
211
242 YUE.on('download_options','change',function(e){
212 </div>
243 var new_cs = e.target.options[e.target.selectedIndex].value;
213 </div>
244 })
214
245
215 <div class="field">
246 </script>
216 <div class="label">
217 <label>${_('Download')}:</label>
218 </div>
219 <div class="input-short">
220 %for cnt,archive in enumerate(c.repo_info._get_archives()):
221 %if cnt >=1:
222 |
223 %endif
224 ${h.link_to(c.repo_info.name+'.'+archive['type'],
225 h.url('files_archive_home',repo_name=c.repo_info.name,
226 fname='tip'+archive['extension']),class_="archive_icon")}
227 %endfor
228 </div>
229 </div>
230
231 <div class="field">
232 <div class="label">
233 <label>${_('Feeds')}:</label>
234 </div>
235 <div class="input-short">
236 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo_info.name),class_='rss_icon')}
237 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo_info.name),class_='atom_icon')}
238 </div>
239 </div>
240 </div>
241 </div>
242 </div>
247 </div>
243
248
244 <div class="box box-right" style="min-height:455px">
249 <div class="box box-right" style="min-height:455px">
245 <!-- box / title -->
250 <!-- box / title -->
246 <div class="title">
251 <div class="title">
247 <h5>${_('Commit activity by day / author')}</h5>
252 <h5>${_('Commit activity by day / author')}</h5>
248 </div>
253 </div>
249
254
250 <div class="table">
255 <div class="table">
251
256
252 %if c.no_data:
257 %if c.no_data:
253 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">${c.no_data_msg}
258 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">${c.no_data_msg}
254 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
259 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
255 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
260 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
256 %endif
261 %endif
257 </div>
262 </div>
258 %endif:
263 %endif:
259 <div id="commit_history" style="width:460px;height:300px;float:left"></div>
264 <div id="commit_history" style="width:460px;height:300px;float:left"></div>
260 <div style="clear: both;height: 10px"></div>
265 <div style="clear: both;height: 10px"></div>
261 <div id="overview" style="width:460px;height:100px;float:left"></div>
266 <div id="overview" style="width:460px;height:100px;float:left"></div>
262
267
263 <div id="legend_data" style="clear:both;margin-top:10px;">
268 <div id="legend_data" style="clear:both;margin-top:10px;">
264 <div id="legend_container"></div>
269 <div id="legend_container"></div>
265 <div id="legend_choices">
270 <div id="legend_choices">
266 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
271 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
267 </div>
272 </div>
268 </div>
273 </div>
269 <script type="text/javascript">
274 <script type="text/javascript">
270 /**
275 /**
271 * Plots summary graph
276 * Plots summary graph
272 *
277 *
273 * @class SummaryPlot
278 * @class SummaryPlot
274 * @param {from} initial from for detailed graph
279 * @param {from} initial from for detailed graph
275 * @param {to} initial to for detailed graph
280 * @param {to} initial to for detailed graph
276 * @param {dataset}
281 * @param {dataset}
277 * @param {overview_dataset}
282 * @param {overview_dataset}
278 */
283 */
279 function SummaryPlot(from,to,dataset,overview_dataset) {
284 function SummaryPlot(from,to,dataset,overview_dataset) {
280 var initial_ranges = {
285 var initial_ranges = {
281 "xaxis":{
286 "xaxis":{
282 "from":from,
287 "from":from,
283 "to":to,
288 "to":to,
284 },
289 },
285 };
290 };
286 var dataset = dataset;
291 var dataset = dataset;
287 var overview_dataset = [overview_dataset];
292 var overview_dataset = [overview_dataset];
288 var choiceContainer = YUD.get("legend_choices");
293 var choiceContainer = YUD.get("legend_choices");
289 var choiceContainerTable = YUD.get("legend_choices_tables");
294 var choiceContainerTable = YUD.get("legend_choices_tables");
290 var plotContainer = YUD.get('commit_history');
295 var plotContainer = YUD.get('commit_history');
291 var overviewContainer = YUD.get('overview');
296 var overviewContainer = YUD.get('overview');
292
297
293 var plot_options = {
298 var plot_options = {
294 bars: {show:true,align:'center',lineWidth:4},
299 bars: {show:true,align:'center',lineWidth:4},
295 legend: {show:true, container:"legend_container"},
300 legend: {show:true, container:"legend_container"},
296 points: {show:true,radius:0,fill:false},
301 points: {show:true,radius:0,fill:false},
297 yaxis: {tickDecimals:0,},
302 yaxis: {tickDecimals:0,},
298 xaxis: {
303 xaxis: {
299 mode: "time",
304 mode: "time",
300 timeformat: "%d/%m",
305 timeformat: "%d/%m",
301 min:from,
306 min:from,
302 max:to,
307 max:to,
303 },
308 },
304 grid: {
309 grid: {
305 hoverable: true,
310 hoverable: true,
306 clickable: true,
311 clickable: true,
307 autoHighlight:true,
312 autoHighlight:true,
308 color: "#999"
313 color: "#999"
309 },
314 },
310 //selection: {mode: "x"}
315 //selection: {mode: "x"}
311 };
316 };
312 var overview_options = {
317 var overview_options = {
313 legend:{show:false},
318 legend:{show:false},
314 bars: {show:true,barWidth: 2,},
319 bars: {show:true,barWidth: 2,},
315 shadowSize: 0,
320 shadowSize: 0,
316 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
321 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
317 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
322 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
318 grid: {color: "#999",},
323 grid: {color: "#999",},
319 selection: {mode: "x"}
324 selection: {mode: "x"}
320 };
325 };
321
326
322 /**
327 /**
323 *get dummy data needed in few places
328 *get dummy data needed in few places
324 */
329 */
325 function getDummyData(label){
330 function getDummyData(label){
326 return {"label":label,
331 return {"label":label,
327 "data":[{"time":0,
332 "data":[{"time":0,
328 "commits":0,
333 "commits":0,
329 "added":0,
334 "added":0,
330 "changed":0,
335 "changed":0,
331 "removed":0,
336 "removed":0,
332 }],
337 }],
333 "schema":["commits"],
338 "schema":["commits"],
334 "color":'#ffffff',
339 "color":'#ffffff',
335 }
340 }
336 }
341 }
337
342
338 /**
343 /**
339 * generate checkboxes accordindly to data
344 * generate checkboxes accordindly to data
340 * @param keys
345 * @param keys
341 * @returns
346 * @returns
342 */
347 */
343 function generateCheckboxes(data) {
348 function generateCheckboxes(data) {
344 //append checkboxes
349 //append checkboxes
345 var i = 0;
350 var i = 0;
346 choiceContainerTable.innerHTML = '';
351 choiceContainerTable.innerHTML = '';
347 for(var pos in data) {
352 for(var pos in data) {
348
353
349 data[pos].color = i;
354 data[pos].color = i;
350 i++;
355 i++;
351 if(data[pos].label != ''){
356 if(data[pos].label != ''){
352 choiceContainerTable.innerHTML += '<tr><td>'+
357 choiceContainerTable.innerHTML += '<tr><td>'+
353 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
358 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
354 +data[pos].label+
359 +data[pos].label+
355 '</td></tr>';
360 '</td></tr>';
356 }
361 }
357 }
362 }
358 }
363 }
359
364
360 /**
365 /**
361 * ToolTip show
366 * ToolTip show
362 */
367 */
363 function showTooltip(x, y, contents) {
368 function showTooltip(x, y, contents) {
364 var div=document.getElementById('tooltip');
369 var div=document.getElementById('tooltip');
365 if(!div) {
370 if(!div) {
366 div = document.createElement('div');
371 div = document.createElement('div');
367 div.id="tooltip";
372 div.id="tooltip";
368 div.style.position="absolute";
373 div.style.position="absolute";
369 div.style.border='1px solid #fdd';
374 div.style.border='1px solid #fdd';
370 div.style.padding='2px';
375 div.style.padding='2px';
371 div.style.backgroundColor='#fee';
376 div.style.backgroundColor='#fee';
372 document.body.appendChild(div);
377 document.body.appendChild(div);
373 }
378 }
374 YUD.setStyle(div, 'opacity', 0);
379 YUD.setStyle(div, 'opacity', 0);
375 div.innerHTML = contents;
380 div.innerHTML = contents;
376 div.style.top=(y + 5) + "px";
381 div.style.top=(y + 5) + "px";
377 div.style.left=(x + 5) + "px";
382 div.style.left=(x + 5) + "px";
378
383
379 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
384 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
380 anim.animate();
385 anim.animate();
381 }
386 }
382
387
383 /**
388 /**
384 * This function will detect if selected period has some changesets
389 * This function will detect if selected period has some changesets
385 for this user if it does this data is then pushed for displaying
390 for this user if it does this data is then pushed for displaying
386 Additionally it will only display users that are selected by the checkbox
391 Additionally it will only display users that are selected by the checkbox
387 */
392 */
388 function getDataAccordingToRanges(ranges) {
393 function getDataAccordingToRanges(ranges) {
389
394
390 var data = [];
395 var data = [];
391 var keys = [];
396 var keys = [];
392 for(var key in dataset){
397 for(var key in dataset){
393 var push = false;
398 var push = false;
394
399
395 //method1 slow !!
400 //method1 slow !!
396 //*
401 //*
397 for(var ds in dataset[key].data){
402 for(var ds in dataset[key].data){
398 commit_data = dataset[key].data[ds];
403 commit_data = dataset[key].data[ds];
399 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
404 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
400 push = true;
405 push = true;
401 break;
406 break;
402 }
407 }
403 }
408 }
404 //*/
409 //*/
405
410
406 /*//method2 sorted commit data !!!
411 /*//method2 sorted commit data !!!
407
412
408 var first_commit = dataset[key].data[0].time;
413 var first_commit = dataset[key].data[0].time;
409 var last_commit = dataset[key].data[dataset[key].data.length-1].time;
414 var last_commit = dataset[key].data[dataset[key].data.length-1].time;
410
415
411 if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){
416 if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){
412 push = true;
417 push = true;
413 }
418 }
414 //*/
419 //*/
415
420
416 if(push){
421 if(push){
417 data.push(dataset[key]);
422 data.push(dataset[key]);
418 }
423 }
419 }
424 }
420 if(data.length >= 1){
425 if(data.length >= 1){
421 return data;
426 return data;
422 }
427 }
423 else{
428 else{
424 //just return dummy data for graph to plot itself
429 //just return dummy data for graph to plot itself
425 return [getDummyData('')];
430 return [getDummyData('')];
426 }
431 }
427
432
428 }
433 }
429
434
430 /**
435 /**
431 * redraw using new checkbox data
436 * redraw using new checkbox data
432 */
437 */
433 function plotchoiced(e,args){
438 function plotchoiced(e,args){
434 var cur_data = args[0];
439 var cur_data = args[0];
435 var cur_ranges = args[1];
440 var cur_ranges = args[1];
436
441
437 var new_data = [];
442 var new_data = [];
438 var inputs = choiceContainer.getElementsByTagName("input");
443 var inputs = choiceContainer.getElementsByTagName("input");
439
444
440 //show only checked labels
445 //show only checked labels
441 for(var i=0; i<inputs.length; i++) {
446 for(var i=0; i<inputs.length; i++) {
442 var checkbox_key = inputs[i].name;
447 var checkbox_key = inputs[i].name;
443
448
444 if(inputs[i].checked){
449 if(inputs[i].checked){
445 for(var d in cur_data){
450 for(var d in cur_data){
446 if(cur_data[d].label == checkbox_key){
451 if(cur_data[d].label == checkbox_key){
447 new_data.push(cur_data[d]);
452 new_data.push(cur_data[d]);
448 }
453 }
449 }
454 }
450 }
455 }
451 else{
456 else{
452 //push dummy data to not hide the label
457 //push dummy data to not hide the label
453 new_data.push(getDummyData(checkbox_key));
458 new_data.push(getDummyData(checkbox_key));
454 }
459 }
455 }
460 }
456
461
457 var new_options = YAHOO.lang.merge(plot_options, {
462 var new_options = YAHOO.lang.merge(plot_options, {
458 xaxis: {
463 xaxis: {
459 min: cur_ranges.xaxis.from,
464 min: cur_ranges.xaxis.from,
460 max: cur_ranges.xaxis.to,
465 max: cur_ranges.xaxis.to,
461 mode:"time",
466 mode:"time",
462 timeformat: "%d/%m",
467 timeformat: "%d/%m",
463 },
468 },
464 });
469 });
465 if (!new_data){
470 if (!new_data){
466 new_data = [[0,1]];
471 new_data = [[0,1]];
467 }
472 }
468 // do the zooming
473 // do the zooming
469 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
474 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
470
475
471 plot.subscribe("plotselected", plotselected);
476 plot.subscribe("plotselected", plotselected);
472
477
473 //resubscribe plothover
478 //resubscribe plothover
474 plot.subscribe("plothover", plothover);
479 plot.subscribe("plothover", plothover);
475
480
476 // don't fire event on the overview to prevent eternal loop
481 // don't fire event on the overview to prevent eternal loop
477 overview.setSelection(cur_ranges, true);
482 overview.setSelection(cur_ranges, true);
478
483
479 }
484 }
480
485
481 /**
486 /**
482 * plot only selected items from overview
487 * plot only selected items from overview
483 * @param ranges
488 * @param ranges
484 * @returns
489 * @returns
485 */
490 */
486 function plotselected(ranges,cur_data) {
491 function plotselected(ranges,cur_data) {
487 //updates the data for new plot
492 //updates the data for new plot
488 data = getDataAccordingToRanges(ranges);
493 data = getDataAccordingToRanges(ranges);
489 generateCheckboxes(data);
494 generateCheckboxes(data);
490
495
491 var new_options = YAHOO.lang.merge(plot_options, {
496 var new_options = YAHOO.lang.merge(plot_options, {
492 xaxis: {
497 xaxis: {
493 min: ranges.xaxis.from,
498 min: ranges.xaxis.from,
494 max: ranges.xaxis.to,
499 max: ranges.xaxis.to,
495 mode:"time",
500 mode:"time",
496 timeformat: "%d/%m",
501 timeformat: "%d/%m",
497 },
502 },
498 yaxis: {
503 yaxis: {
499 min: ranges.yaxis.from,
504 min: ranges.yaxis.from,
500 max: ranges.yaxis.to,
505 max: ranges.yaxis.to,
501 },
506 },
502
507
503 });
508 });
504 // do the zooming
509 // do the zooming
505 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
510 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
506
511
507 plot.subscribe("plotselected", plotselected);
512 plot.subscribe("plotselected", plotselected);
508
513
509 //resubscribe plothover
514 //resubscribe plothover
510 plot.subscribe("plothover", plothover);
515 plot.subscribe("plothover", plothover);
511
516
512 // don't fire event on the overview to prevent eternal loop
517 // don't fire event on the overview to prevent eternal loop
513 overview.setSelection(ranges, true);
518 overview.setSelection(ranges, true);
514
519
515 //resubscribe choiced
520 //resubscribe choiced
516 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
521 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
517 }
522 }
518
523
519 var previousPoint = null;
524 var previousPoint = null;
520
525
521 function plothover(o) {
526 function plothover(o) {
522 var pos = o.pos;
527 var pos = o.pos;
523 var item = o.item;
528 var item = o.item;
524
529
525 //YUD.get("x").innerHTML = pos.x.toFixed(2);
530 //YUD.get("x").innerHTML = pos.x.toFixed(2);
526 //YUD.get("y").innerHTML = pos.y.toFixed(2);
531 //YUD.get("y").innerHTML = pos.y.toFixed(2);
527 if (item) {
532 if (item) {
528 if (previousPoint != item.datapoint) {
533 if (previousPoint != item.datapoint) {
529 previousPoint = item.datapoint;
534 previousPoint = item.datapoint;
530
535
531 var tooltip = YUD.get("tooltip");
536 var tooltip = YUD.get("tooltip");
532 if(tooltip) {
537 if(tooltip) {
533 tooltip.parentNode.removeChild(tooltip);
538 tooltip.parentNode.removeChild(tooltip);
534 }
539 }
535 var x = item.datapoint.x.toFixed(2);
540 var x = item.datapoint.x.toFixed(2);
536 var y = item.datapoint.y.toFixed(2);
541 var y = item.datapoint.y.toFixed(2);
537
542
538 if (!item.series.label){
543 if (!item.series.label){
539 item.series.label = 'commits';
544 item.series.label = 'commits';
540 }
545 }
541 var d = new Date(x*1000);
546 var d = new Date(x*1000);
542 var fd = d.toDateString()
547 var fd = d.toDateString()
543 var nr_commits = parseInt(y);
548 var nr_commits = parseInt(y);
544
549
545 var cur_data = dataset[item.series.label].data[item.dataIndex];
550 var cur_data = dataset[item.series.label].data[item.dataIndex];
546 var added = cur_data.added;
551 var added = cur_data.added;
547 var changed = cur_data.changed;
552 var changed = cur_data.changed;
548 var removed = cur_data.removed;
553 var removed = cur_data.removed;
549
554
550 var nr_commits_suffix = " ${_('commits')} ";
555 var nr_commits_suffix = " ${_('commits')} ";
551 var added_suffix = " ${_('files added')} ";
556 var added_suffix = " ${_('files added')} ";
552 var changed_suffix = " ${_('files changed')} ";
557 var changed_suffix = " ${_('files changed')} ";
553 var removed_suffix = " ${_('files removed')} ";
558 var removed_suffix = " ${_('files removed')} ";
554
559
555
560
556 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
561 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
557 if(added==1){added_suffix=" ${_('file added')} ";}
562 if(added==1){added_suffix=" ${_('file added')} ";}
558 if(changed==1){changed_suffix=" ${_('file changed')} ";}
563 if(changed==1){changed_suffix=" ${_('file changed')} ";}
559 if(removed==1){removed_suffix=" ${_('file removed')} ";}
564 if(removed==1){removed_suffix=" ${_('file removed')} ";}
560
565
561 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
566 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
562 +'<br/>'+
567 +'<br/>'+
563 nr_commits + nr_commits_suffix+'<br/>'+
568 nr_commits + nr_commits_suffix+'<br/>'+
564 added + added_suffix +'<br/>'+
569 added + added_suffix +'<br/>'+
565 changed + changed_suffix + '<br/>'+
570 changed + changed_suffix + '<br/>'+
566 removed + removed_suffix + '<br/>');
571 removed + removed_suffix + '<br/>');
567 }
572 }
568 }
573 }
569 else {
574 else {
570 var tooltip = YUD.get("tooltip");
575 var tooltip = YUD.get("tooltip");
571
576
572 if(tooltip) {
577 if(tooltip) {
573 tooltip.parentNode.removeChild(tooltip);
578 tooltip.parentNode.removeChild(tooltip);
574 }
579 }
575 previousPoint = null;
580 previousPoint = null;
576 }
581 }
577 }
582 }
578
583
579 /**
584 /**
580 * MAIN EXECUTION
585 * MAIN EXECUTION
581 */
586 */
582
587
583 var data = getDataAccordingToRanges(initial_ranges);
588 var data = getDataAccordingToRanges(initial_ranges);
584 generateCheckboxes(data);
589 generateCheckboxes(data);
585
590
586 //main plot
591 //main plot
587 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
592 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
588
593
589 //overview
594 //overview
590 var overview = YAHOO.widget.Flot(overviewContainer, overview_dataset, overview_options);
595 var overview = YAHOO.widget.Flot(overviewContainer, overview_dataset, overview_options);
591
596
592 //show initial selection on overview
597 //show initial selection on overview
593 overview.setSelection(initial_ranges);
598 overview.setSelection(initial_ranges);
594
599
595 plot.subscribe("plotselected", plotselected);
600 plot.subscribe("plotselected", plotselected);
596
601
597 overview.subscribe("plotselected", function (ranges) {
602 overview.subscribe("plotselected", function (ranges) {
598 plot.setSelection(ranges);
603 plot.setSelection(ranges);
599 });
604 });
600
605
601 plot.subscribe("plothover", plothover);
606 plot.subscribe("plothover", plothover);
602
607
603 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
608 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
604 }
609 }
605 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
610 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
606 </script>
611 </script>
607
612
608 </div>
613 </div>
609 </div>
614 </div>
610
615
611 <div class="box">
616 <div class="box">
612 <div class="title">
617 <div class="title">
613 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
618 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
614 </div>
619 </div>
615 <div class="table">
620 <div class="table">
616 <div id="shortlog_data">
621 <div id="shortlog_data">
617 <%include file='../shortlog/shortlog_data.html'/>
622 <%include file='../shortlog/shortlog_data.html'/>
618 </div>
623 </div>
619 ##%if c.repo_changesets:
624 ##%if c.repo_changesets:
620 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
625 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
621 ##%endif
626 ##%endif
622 </div>
627 </div>
623 </div>
628 </div>
624 <div class="box">
629 <div class="box">
625 <div class="title">
630 <div class="title">
626 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
631 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
627 </div>
632 </div>
628 <div class="table">
633 <div class="table">
629 <%include file='../tags/tags_data.html'/>
634 <%include file='../tags/tags_data.html'/>
630 %if c.repo_changesets:
635 %if c.repo_changesets:
631 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
636 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
632 %endif
637 %endif
633 </div>
638 </div>
634 </div>
639 </div>
635 <div class="box">
640 <div class="box">
636 <div class="title">
641 <div class="title">
637 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
642 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
638 </div>
643 </div>
639 <div class="table">
644 <div class="table">
640 <%include file='../branches/branches_data.html'/>
645 <%include file='../branches/branches_data.html'/>
641 %if c.repo_changesets:
646 %if c.repo_changesets:
642 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
647 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
643 %endif
648 %endif
644 </div>
649 </div>
645 </div>
650 </div>
646
651
647 </%def> No newline at end of file
652 </%def>
General Comments 0
You need to be logged in to leave comments. Login now