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