##// END OF EJS Templates
ui: new file tree switcher...
marcink -
r3655:6431a1ec new-ui
parent child Browse files
Show More
@@ -1,1395 +1,1393 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import itertools
21 import itertools
22 import logging
22 import logging
23 import os
23 import os
24 import shutil
24 import shutil
25 import tempfile
25 import tempfile
26 import collections
26 import collections
27 import urllib
27 import urllib
28
28
29 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
30 from pyramid.view import view_config
30 from pyramid.view import view_config
31 from pyramid.renderers import render
31 from pyramid.renderers import render
32 from pyramid.response import Response
32 from pyramid.response import Response
33
33
34 import rhodecode
34 import rhodecode
35 from rhodecode.apps._base import RepoAppView
35 from rhodecode.apps._base import RepoAppView
36
36
37
37
38 from rhodecode.lib import diffs, helpers as h, rc_cache
38 from rhodecode.lib import diffs, helpers as h, rc_cache
39 from rhodecode.lib import audit_logger
39 from rhodecode.lib import audit_logger
40 from rhodecode.lib.view_utils import parse_path_ref
40 from rhodecode.lib.view_utils import parse_path_ref
41 from rhodecode.lib.exceptions import NonRelativePathError
41 from rhodecode.lib.exceptions import NonRelativePathError
42 from rhodecode.lib.codeblocks import (
42 from rhodecode.lib.codeblocks import (
43 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
43 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
44 from rhodecode.lib.utils2 import (
44 from rhodecode.lib.utils2 import (
45 convert_line_endings, detect_mode, safe_str, str2bool, safe_int)
45 convert_line_endings, detect_mode, safe_str, str2bool, safe_int)
46 from rhodecode.lib.auth import (
46 from rhodecode.lib.auth import (
47 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
47 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
48 from rhodecode.lib.vcs import path as vcspath
48 from rhodecode.lib.vcs import path as vcspath
49 from rhodecode.lib.vcs.backends.base import EmptyCommit
49 from rhodecode.lib.vcs.backends.base import EmptyCommit
50 from rhodecode.lib.vcs.conf import settings
50 from rhodecode.lib.vcs.conf import settings
51 from rhodecode.lib.vcs.nodes import FileNode
51 from rhodecode.lib.vcs.nodes import FileNode
52 from rhodecode.lib.vcs.exceptions import (
52 from rhodecode.lib.vcs.exceptions import (
53 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
53 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
54 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
54 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
55 NodeDoesNotExistError, CommitError, NodeError)
55 NodeDoesNotExistError, CommitError, NodeError)
56
56
57 from rhodecode.model.scm import ScmModel
57 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.db import Repository
58 from rhodecode.model.db import Repository
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class RepoFilesView(RepoAppView):
63 class RepoFilesView(RepoAppView):
64
64
65 @staticmethod
65 @staticmethod
66 def adjust_file_path_for_svn(f_path, repo):
66 def adjust_file_path_for_svn(f_path, repo):
67 """
67 """
68 Computes the relative path of `f_path`.
68 Computes the relative path of `f_path`.
69
69
70 This is mainly based on prefix matching of the recognized tags and
70 This is mainly based on prefix matching of the recognized tags and
71 branches in the underlying repository.
71 branches in the underlying repository.
72 """
72 """
73 tags_and_branches = itertools.chain(
73 tags_and_branches = itertools.chain(
74 repo.branches.iterkeys(),
74 repo.branches.iterkeys(),
75 repo.tags.iterkeys())
75 repo.tags.iterkeys())
76 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
76 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
77
77
78 for name in tags_and_branches:
78 for name in tags_and_branches:
79 if f_path.startswith('{}/'.format(name)):
79 if f_path.startswith('{}/'.format(name)):
80 f_path = vcspath.relpath(f_path, name)
80 f_path = vcspath.relpath(f_path, name)
81 break
81 break
82 return f_path
82 return f_path
83
83
84 def load_default_context(self):
84 def load_default_context(self):
85 c = self._get_local_tmpl_context(include_app_defaults=True)
85 c = self._get_local_tmpl_context(include_app_defaults=True)
86 c.rhodecode_repo = self.rhodecode_vcs_repo
86 c.rhodecode_repo = self.rhodecode_vcs_repo
87 c.enable_downloads = self.db_repo.enable_downloads
87 c.enable_downloads = self.db_repo.enable_downloads
88 return c
88 return c
89
89
90 def _ensure_not_locked(self):
90 def _ensure_not_locked(self):
91 _ = self.request.translate
91 _ = self.request.translate
92
92
93 repo = self.db_repo
93 repo = self.db_repo
94 if repo.enable_locking and repo.locked[0]:
94 if repo.enable_locking and repo.locked[0]:
95 h.flash(_('This repository has been locked by %s on %s')
95 h.flash(_('This repository has been locked by %s on %s')
96 % (h.person_by_id(repo.locked[0]),
96 % (h.person_by_id(repo.locked[0]),
97 h.format_date(h.time_to_datetime(repo.locked[1]))),
97 h.format_date(h.time_to_datetime(repo.locked[1]))),
98 'warning')
98 'warning')
99 files_url = h.route_path(
99 files_url = h.route_path(
100 'repo_files:default_path',
100 'repo_files:default_path',
101 repo_name=self.db_repo_name, commit_id='tip')
101 repo_name=self.db_repo_name, commit_id='tip')
102 raise HTTPFound(files_url)
102 raise HTTPFound(files_url)
103
103
104 def check_branch_permission(self, branch_name):
104 def check_branch_permission(self, branch_name):
105 _ = self.request.translate
105 _ = self.request.translate
106
106
107 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
107 rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
108 self.db_repo_name, branch_name)
108 self.db_repo_name, branch_name)
109 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
109 if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
110 h.flash(
110 h.flash(
111 _('Branch `{}` changes forbidden by rule {}.').format(branch_name, rule),
111 _('Branch `{}` changes forbidden by rule {}.').format(branch_name, rule),
112 'warning')
112 'warning')
113 files_url = h.route_path(
113 files_url = h.route_path(
114 'repo_files:default_path',
114 'repo_files:default_path',
115 repo_name=self.db_repo_name, commit_id='tip')
115 repo_name=self.db_repo_name, commit_id='tip')
116 raise HTTPFound(files_url)
116 raise HTTPFound(files_url)
117
117
118 def _get_commit_and_path(self):
118 def _get_commit_and_path(self):
119 default_commit_id = self.db_repo.landing_rev[1]
119 default_commit_id = self.db_repo.landing_rev[1]
120 default_f_path = '/'
120 default_f_path = '/'
121
121
122 commit_id = self.request.matchdict.get(
122 commit_id = self.request.matchdict.get(
123 'commit_id', default_commit_id)
123 'commit_id', default_commit_id)
124 f_path = self._get_f_path(self.request.matchdict, default_f_path)
124 f_path = self._get_f_path(self.request.matchdict, default_f_path)
125 return commit_id, f_path
125 return commit_id, f_path
126
126
127 def _get_default_encoding(self, c):
127 def _get_default_encoding(self, c):
128 enc_list = getattr(c, 'default_encodings', [])
128 enc_list = getattr(c, 'default_encodings', [])
129 return enc_list[0] if enc_list else 'UTF-8'
129 return enc_list[0] if enc_list else 'UTF-8'
130
130
131 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
131 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
132 """
132 """
133 This is a safe way to get commit. If an error occurs it redirects to
133 This is a safe way to get commit. If an error occurs it redirects to
134 tip with proper message
134 tip with proper message
135
135
136 :param commit_id: id of commit to fetch
136 :param commit_id: id of commit to fetch
137 :param redirect_after: toggle redirection
137 :param redirect_after: toggle redirection
138 """
138 """
139 _ = self.request.translate
139 _ = self.request.translate
140
140
141 try:
141 try:
142 return self.rhodecode_vcs_repo.get_commit(commit_id)
142 return self.rhodecode_vcs_repo.get_commit(commit_id)
143 except EmptyRepositoryError:
143 except EmptyRepositoryError:
144 if not redirect_after:
144 if not redirect_after:
145 return None
145 return None
146
146
147 _url = h.route_path(
147 _url = h.route_path(
148 'repo_files_add_file',
148 'repo_files_add_file',
149 repo_name=self.db_repo_name, commit_id=0, f_path='',
149 repo_name=self.db_repo_name, commit_id=0, f_path='',
150 _anchor='edit')
150 _anchor='edit')
151
151
152 if h.HasRepoPermissionAny(
152 if h.HasRepoPermissionAny(
153 'repository.write', 'repository.admin')(self.db_repo_name):
153 'repository.write', 'repository.admin')(self.db_repo_name):
154 add_new = h.link_to(
154 add_new = h.link_to(
155 _('Click here to add a new file.'), _url, class_="alert-link")
155 _('Click here to add a new file.'), _url, class_="alert-link")
156 else:
156 else:
157 add_new = ""
157 add_new = ""
158
158
159 h.flash(h.literal(
159 h.flash(h.literal(
160 _('There are no files yet. %s') % add_new), category='warning')
160 _('There are no files yet. %s') % add_new), category='warning')
161 raise HTTPFound(
161 raise HTTPFound(
162 h.route_path('repo_summary', repo_name=self.db_repo_name))
162 h.route_path('repo_summary', repo_name=self.db_repo_name))
163
163
164 except (CommitDoesNotExistError, LookupError):
164 except (CommitDoesNotExistError, LookupError):
165 msg = _('No such commit exists for this repository')
165 msg = _('No such commit exists for this repository')
166 h.flash(msg, category='error')
166 h.flash(msg, category='error')
167 raise HTTPNotFound()
167 raise HTTPNotFound()
168 except RepositoryError as e:
168 except RepositoryError as e:
169 h.flash(safe_str(h.escape(e)), category='error')
169 h.flash(safe_str(h.escape(e)), category='error')
170 raise HTTPNotFound()
170 raise HTTPNotFound()
171
171
172 def _get_filenode_or_redirect(self, commit_obj, path):
172 def _get_filenode_or_redirect(self, commit_obj, path):
173 """
173 """
174 Returns file_node, if error occurs or given path is directory,
174 Returns file_node, if error occurs or given path is directory,
175 it'll redirect to top level path
175 it'll redirect to top level path
176 """
176 """
177 _ = self.request.translate
177 _ = self.request.translate
178
178
179 try:
179 try:
180 file_node = commit_obj.get_node(path)
180 file_node = commit_obj.get_node(path)
181 if file_node.is_dir():
181 if file_node.is_dir():
182 raise RepositoryError('The given path is a directory')
182 raise RepositoryError('The given path is a directory')
183 except CommitDoesNotExistError:
183 except CommitDoesNotExistError:
184 log.exception('No such commit exists for this repository')
184 log.exception('No such commit exists for this repository')
185 h.flash(_('No such commit exists for this repository'), category='error')
185 h.flash(_('No such commit exists for this repository'), category='error')
186 raise HTTPNotFound()
186 raise HTTPNotFound()
187 except RepositoryError as e:
187 except RepositoryError as e:
188 log.warning('Repository error while fetching '
188 log.warning('Repository error while fetching '
189 'filenode `%s`. Err:%s', path, e)
189 'filenode `%s`. Err:%s', path, e)
190 h.flash(safe_str(h.escape(e)), category='error')
190 h.flash(safe_str(h.escape(e)), category='error')
191 raise HTTPNotFound()
191 raise HTTPNotFound()
192
192
193 return file_node
193 return file_node
194
194
195 def _is_valid_head(self, commit_id, repo):
195 def _is_valid_head(self, commit_id, repo):
196 branch_name = sha_commit_id = ''
196 branch_name = sha_commit_id = ''
197 is_head = False
197 is_head = False
198
198
199 if h.is_svn(repo) and not repo.is_empty():
199 if h.is_svn(repo) and not repo.is_empty():
200 # Note: Subversion only has one head.
200 # Note: Subversion only has one head.
201 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
201 if commit_id == repo.get_commit(commit_idx=-1).raw_id:
202 is_head = True
202 is_head = True
203 return branch_name, sha_commit_id, is_head
203 return branch_name, sha_commit_id, is_head
204
204
205 for _branch_name, branch_commit_id in repo.branches.items():
205 for _branch_name, branch_commit_id in repo.branches.items():
206 # simple case we pass in branch name, it's a HEAD
206 # simple case we pass in branch name, it's a HEAD
207 if commit_id == _branch_name:
207 if commit_id == _branch_name:
208 is_head = True
208 is_head = True
209 branch_name = _branch_name
209 branch_name = _branch_name
210 sha_commit_id = branch_commit_id
210 sha_commit_id = branch_commit_id
211 break
211 break
212 # case when we pass in full sha commit_id, which is a head
212 # case when we pass in full sha commit_id, which is a head
213 elif commit_id == branch_commit_id:
213 elif commit_id == branch_commit_id:
214 is_head = True
214 is_head = True
215 branch_name = _branch_name
215 branch_name = _branch_name
216 sha_commit_id = branch_commit_id
216 sha_commit_id = branch_commit_id
217 break
217 break
218
218
219 # checked branches, means we only need to try to get the branch/commit_sha
219 # checked branches, means we only need to try to get the branch/commit_sha
220 if not repo.is_empty:
220 if not repo.is_empty:
221 commit = repo.get_commit(commit_id=commit_id)
221 commit = repo.get_commit(commit_id=commit_id)
222 if commit:
222 if commit:
223 branch_name = commit.branch
223 branch_name = commit.branch
224 sha_commit_id = commit.raw_id
224 sha_commit_id = commit.raw_id
225
225
226 return branch_name, sha_commit_id, is_head
226 return branch_name, sha_commit_id, is_head
227
227
228 def _get_tree_at_commit(
228 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False):
229 self, c, commit_id, f_path, full_load=False):
230
229
231 repo_id = self.db_repo.repo_id
230 repo_id = self.db_repo.repo_id
232 force_recache = self.get_recache_flag()
231 force_recache = self.get_recache_flag()
233
232
234 cache_seconds = safe_int(
233 cache_seconds = safe_int(
235 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
234 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
236 cache_on = not force_recache and cache_seconds > 0
235 cache_on = not force_recache and cache_seconds > 0
237 log.debug(
236 log.debug(
238 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
237 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
239 'with caching: %s[TTL: %ss]' % (
238 'with caching: %s[TTL: %ss]' % (
240 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
239 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
241
240
242 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
241 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
243 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
242 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
244
243
245 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
244 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
246 condition=cache_on)
245 condition=cache_on)
247 def compute_file_tree(repo_id, commit_id, f_path, full_load):
246 def compute_file_tree(repo_id, commit_id, f_path, full_load):
248 log.debug('Generating cached file tree for repo_id: %s, %s, %s',
247 log.debug('Generating cached file tree for repo_id: %s, %s, %s',
249 repo_id, commit_id, f_path)
248 repo_id, commit_id, f_path)
250
249
251 c.full_load = full_load
250 c.full_load = full_load
252 return render(
251 return render(
253 'rhodecode:templates/files/files_browser_tree.mako',
252 'rhodecode:templates/files/files_browser_tree.mako',
254 self._get_template_context(c), self.request)
253 self._get_template_context(c), self.request)
255
254
256 return compute_file_tree(self.db_repo.repo_id, commit_id, f_path, full_load)
255 return compute_file_tree(self.db_repo.repo_id, commit_id, f_path, full_load)
257
256
258 def _get_archive_spec(self, fname):
257 def _get_archive_spec(self, fname):
259 log.debug('Detecting archive spec for: `%s`', fname)
258 log.debug('Detecting archive spec for: `%s`', fname)
260
259
261 fileformat = None
260 fileformat = None
262 ext = None
261 ext = None
263 content_type = None
262 content_type = None
264 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
263 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
265 content_type, extension = ext_data
264 content_type, extension = ext_data
266
265
267 if fname.endswith(extension):
266 if fname.endswith(extension):
268 fileformat = a_type
267 fileformat = a_type
269 log.debug('archive is of type: %s', fileformat)
268 log.debug('archive is of type: %s', fileformat)
270 ext = extension
269 ext = extension
271 break
270 break
272
271
273 if not fileformat:
272 if not fileformat:
274 raise ValueError()
273 raise ValueError()
275
274
276 # left over part of whole fname is the commit
275 # left over part of whole fname is the commit
277 commit_id = fname[:-len(ext)]
276 commit_id = fname[:-len(ext)]
278
277
279 return commit_id, ext, fileformat, content_type
278 return commit_id, ext, fileformat, content_type
280
279
281 @LoginRequired()
280 @LoginRequired()
282 @HasRepoPermissionAnyDecorator(
281 @HasRepoPermissionAnyDecorator(
283 'repository.read', 'repository.write', 'repository.admin')
282 'repository.read', 'repository.write', 'repository.admin')
284 @view_config(
283 @view_config(
285 route_name='repo_archivefile', request_method='GET',
284 route_name='repo_archivefile', request_method='GET',
286 renderer=None)
285 renderer=None)
287 def repo_archivefile(self):
286 def repo_archivefile(self):
288 # archive cache config
287 # archive cache config
289 from rhodecode import CONFIG
288 from rhodecode import CONFIG
290 _ = self.request.translate
289 _ = self.request.translate
291 self.load_default_context()
290 self.load_default_context()
292
291
293 fname = self.request.matchdict['fname']
292 fname = self.request.matchdict['fname']
294 subrepos = self.request.GET.get('subrepos') == 'true'
293 subrepos = self.request.GET.get('subrepos') == 'true'
295
294
296 if not self.db_repo.enable_downloads:
295 if not self.db_repo.enable_downloads:
297 return Response(_('Downloads disabled'))
296 return Response(_('Downloads disabled'))
298
297
299 try:
298 try:
300 commit_id, ext, fileformat, content_type = \
299 commit_id, ext, fileformat, content_type = \
301 self._get_archive_spec(fname)
300 self._get_archive_spec(fname)
302 except ValueError:
301 except ValueError:
303 return Response(_('Unknown archive type for: `{}`').format(
302 return Response(_('Unknown archive type for: `{}`').format(
304 h.escape(fname)))
303 h.escape(fname)))
305
304
306 try:
305 try:
307 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
306 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
308 except CommitDoesNotExistError:
307 except CommitDoesNotExistError:
309 return Response(_('Unknown commit_id {}').format(
308 return Response(_('Unknown commit_id {}').format(
310 h.escape(commit_id)))
309 h.escape(commit_id)))
311 except EmptyRepositoryError:
310 except EmptyRepositoryError:
312 return Response(_('Empty repository'))
311 return Response(_('Empty repository'))
313
312
314 archive_name = '%s-%s%s%s' % (
313 archive_name = '%s-%s%s%s' % (
315 safe_str(self.db_repo_name.replace('/', '_')),
314 safe_str(self.db_repo_name.replace('/', '_')),
316 '-sub' if subrepos else '',
315 '-sub' if subrepos else '',
317 safe_str(commit.short_id), ext)
316 safe_str(commit.short_id), ext)
318
317
319 use_cached_archive = False
318 use_cached_archive = False
320 archive_cache_enabled = CONFIG.get(
319 archive_cache_enabled = CONFIG.get(
321 'archive_cache_dir') and not self.request.GET.get('no_cache')
320 'archive_cache_dir') and not self.request.GET.get('no_cache')
322 cached_archive_path = None
321 cached_archive_path = None
323
322
324 if archive_cache_enabled:
323 if archive_cache_enabled:
325 # check if we it's ok to write
324 # check if we it's ok to write
326 if not os.path.isdir(CONFIG['archive_cache_dir']):
325 if not os.path.isdir(CONFIG['archive_cache_dir']):
327 os.makedirs(CONFIG['archive_cache_dir'])
326 os.makedirs(CONFIG['archive_cache_dir'])
328 cached_archive_path = os.path.join(
327 cached_archive_path = os.path.join(
329 CONFIG['archive_cache_dir'], archive_name)
328 CONFIG['archive_cache_dir'], archive_name)
330 if os.path.isfile(cached_archive_path):
329 if os.path.isfile(cached_archive_path):
331 log.debug('Found cached archive in %s', cached_archive_path)
330 log.debug('Found cached archive in %s', cached_archive_path)
332 fd, archive = None, cached_archive_path
331 fd, archive = None, cached_archive_path
333 use_cached_archive = True
332 use_cached_archive = True
334 else:
333 else:
335 log.debug('Archive %s is not yet cached', archive_name)
334 log.debug('Archive %s is not yet cached', archive_name)
336
335
337 if not use_cached_archive:
336 if not use_cached_archive:
338 # generate new archive
337 # generate new archive
339 fd, archive = tempfile.mkstemp()
338 fd, archive = tempfile.mkstemp()
340 log.debug('Creating new temp archive in %s', archive)
339 log.debug('Creating new temp archive in %s', archive)
341 try:
340 try:
342 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
341 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
343 except ImproperArchiveTypeError:
342 except ImproperArchiveTypeError:
344 return _('Unknown archive type')
343 return _('Unknown archive type')
345 if archive_cache_enabled:
344 if archive_cache_enabled:
346 # if we generated the archive and we have cache enabled
345 # if we generated the archive and we have cache enabled
347 # let's use this for future
346 # let's use this for future
348 log.debug('Storing new archive in %s', cached_archive_path)
347 log.debug('Storing new archive in %s', cached_archive_path)
349 shutil.move(archive, cached_archive_path)
348 shutil.move(archive, cached_archive_path)
350 archive = cached_archive_path
349 archive = cached_archive_path
351
350
352 # store download action
351 # store download action
353 audit_logger.store_web(
352 audit_logger.store_web(
354 'repo.archive.download', action_data={
353 'repo.archive.download', action_data={
355 'user_agent': self.request.user_agent,
354 'user_agent': self.request.user_agent,
356 'archive_name': archive_name,
355 'archive_name': archive_name,
357 'archive_spec': fname,
356 'archive_spec': fname,
358 'archive_cached': use_cached_archive},
357 'archive_cached': use_cached_archive},
359 user=self._rhodecode_user,
358 user=self._rhodecode_user,
360 repo=self.db_repo,
359 repo=self.db_repo,
361 commit=True
360 commit=True
362 )
361 )
363
362
364 def get_chunked_archive(archive_path):
363 def get_chunked_archive(archive_path):
365 with open(archive_path, 'rb') as stream:
364 with open(archive_path, 'rb') as stream:
366 while True:
365 while True:
367 data = stream.read(16 * 1024)
366 data = stream.read(16 * 1024)
368 if not data:
367 if not data:
369 if fd: # fd means we used temporary file
368 if fd: # fd means we used temporary file
370 os.close(fd)
369 os.close(fd)
371 if not archive_cache_enabled:
370 if not archive_cache_enabled:
372 log.debug('Destroying temp archive %s', archive_path)
371 log.debug('Destroying temp archive %s', archive_path)
373 os.remove(archive_path)
372 os.remove(archive_path)
374 break
373 break
375 yield data
374 yield data
376
375
377 response = Response(app_iter=get_chunked_archive(archive))
376 response = Response(app_iter=get_chunked_archive(archive))
378 response.content_disposition = str(
377 response.content_disposition = str(
379 'attachment; filename=%s' % archive_name)
378 'attachment; filename=%s' % archive_name)
380 response.content_type = str(content_type)
379 response.content_type = str(content_type)
381
380
382 return response
381 return response
383
382
384 def _get_file_node(self, commit_id, f_path):
383 def _get_file_node(self, commit_id, f_path):
385 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
384 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
386 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
385 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
387 try:
386 try:
388 node = commit.get_node(f_path)
387 node = commit.get_node(f_path)
389 if node.is_dir():
388 if node.is_dir():
390 raise NodeError('%s path is a %s not a file'
389 raise NodeError('%s path is a %s not a file'
391 % (node, type(node)))
390 % (node, type(node)))
392 except NodeDoesNotExistError:
391 except NodeDoesNotExistError:
393 commit = EmptyCommit(
392 commit = EmptyCommit(
394 commit_id=commit_id,
393 commit_id=commit_id,
395 idx=commit.idx,
394 idx=commit.idx,
396 repo=commit.repository,
395 repo=commit.repository,
397 alias=commit.repository.alias,
396 alias=commit.repository.alias,
398 message=commit.message,
397 message=commit.message,
399 author=commit.author,
398 author=commit.author,
400 date=commit.date)
399 date=commit.date)
401 node = FileNode(f_path, '', commit=commit)
400 node = FileNode(f_path, '', commit=commit)
402 else:
401 else:
403 commit = EmptyCommit(
402 commit = EmptyCommit(
404 repo=self.rhodecode_vcs_repo,
403 repo=self.rhodecode_vcs_repo,
405 alias=self.rhodecode_vcs_repo.alias)
404 alias=self.rhodecode_vcs_repo.alias)
406 node = FileNode(f_path, '', commit=commit)
405 node = FileNode(f_path, '', commit=commit)
407 return node
406 return node
408
407
409 @LoginRequired()
408 @LoginRequired()
410 @HasRepoPermissionAnyDecorator(
409 @HasRepoPermissionAnyDecorator(
411 'repository.read', 'repository.write', 'repository.admin')
410 'repository.read', 'repository.write', 'repository.admin')
412 @view_config(
411 @view_config(
413 route_name='repo_files_diff', request_method='GET',
412 route_name='repo_files_diff', request_method='GET',
414 renderer=None)
413 renderer=None)
415 def repo_files_diff(self):
414 def repo_files_diff(self):
416 c = self.load_default_context()
415 c = self.load_default_context()
417 f_path = self._get_f_path(self.request.matchdict)
416 f_path = self._get_f_path(self.request.matchdict)
418 diff1 = self.request.GET.get('diff1', '')
417 diff1 = self.request.GET.get('diff1', '')
419 diff2 = self.request.GET.get('diff2', '')
418 diff2 = self.request.GET.get('diff2', '')
420
419
421 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
420 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
422
421
423 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
422 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
424 line_context = self.request.GET.get('context', 3)
423 line_context = self.request.GET.get('context', 3)
425
424
426 if not any((diff1, diff2)):
425 if not any((diff1, diff2)):
427 h.flash(
426 h.flash(
428 'Need query parameter "diff1" or "diff2" to generate a diff.',
427 'Need query parameter "diff1" or "diff2" to generate a diff.',
429 category='error')
428 category='error')
430 raise HTTPBadRequest()
429 raise HTTPBadRequest()
431
430
432 c.action = self.request.GET.get('diff')
431 c.action = self.request.GET.get('diff')
433 if c.action not in ['download', 'raw']:
432 if c.action not in ['download', 'raw']:
434 compare_url = h.route_path(
433 compare_url = h.route_path(
435 'repo_compare',
434 'repo_compare',
436 repo_name=self.db_repo_name,
435 repo_name=self.db_repo_name,
437 source_ref_type='rev',
436 source_ref_type='rev',
438 source_ref=diff1,
437 source_ref=diff1,
439 target_repo=self.db_repo_name,
438 target_repo=self.db_repo_name,
440 target_ref_type='rev',
439 target_ref_type='rev',
441 target_ref=diff2,
440 target_ref=diff2,
442 _query=dict(f_path=f_path))
441 _query=dict(f_path=f_path))
443 # redirect to new view if we render diff
442 # redirect to new view if we render diff
444 raise HTTPFound(compare_url)
443 raise HTTPFound(compare_url)
445
444
446 try:
445 try:
447 node1 = self._get_file_node(diff1, path1)
446 node1 = self._get_file_node(diff1, path1)
448 node2 = self._get_file_node(diff2, f_path)
447 node2 = self._get_file_node(diff2, f_path)
449 except (RepositoryError, NodeError):
448 except (RepositoryError, NodeError):
450 log.exception("Exception while trying to get node from repository")
449 log.exception("Exception while trying to get node from repository")
451 raise HTTPFound(
450 raise HTTPFound(
452 h.route_path('repo_files', repo_name=self.db_repo_name,
451 h.route_path('repo_files', repo_name=self.db_repo_name,
453 commit_id='tip', f_path=f_path))
452 commit_id='tip', f_path=f_path))
454
453
455 if all(isinstance(node.commit, EmptyCommit)
454 if all(isinstance(node.commit, EmptyCommit)
456 for node in (node1, node2)):
455 for node in (node1, node2)):
457 raise HTTPNotFound()
456 raise HTTPNotFound()
458
457
459 c.commit_1 = node1.commit
458 c.commit_1 = node1.commit
460 c.commit_2 = node2.commit
459 c.commit_2 = node2.commit
461
460
462 if c.action == 'download':
461 if c.action == 'download':
463 _diff = diffs.get_gitdiff(node1, node2,
462 _diff = diffs.get_gitdiff(node1, node2,
464 ignore_whitespace=ignore_whitespace,
463 ignore_whitespace=ignore_whitespace,
465 context=line_context)
464 context=line_context)
466 diff = diffs.DiffProcessor(_diff, format='gitdiff')
465 diff = diffs.DiffProcessor(_diff, format='gitdiff')
467
466
468 response = Response(self.path_filter.get_raw_patch(diff))
467 response = Response(self.path_filter.get_raw_patch(diff))
469 response.content_type = 'text/plain'
468 response.content_type = 'text/plain'
470 response.content_disposition = (
469 response.content_disposition = (
471 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
470 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
472 )
471 )
473 charset = self._get_default_encoding(c)
472 charset = self._get_default_encoding(c)
474 if charset:
473 if charset:
475 response.charset = charset
474 response.charset = charset
476 return response
475 return response
477
476
478 elif c.action == 'raw':
477 elif c.action == 'raw':
479 _diff = diffs.get_gitdiff(node1, node2,
478 _diff = diffs.get_gitdiff(node1, node2,
480 ignore_whitespace=ignore_whitespace,
479 ignore_whitespace=ignore_whitespace,
481 context=line_context)
480 context=line_context)
482 diff = diffs.DiffProcessor(_diff, format='gitdiff')
481 diff = diffs.DiffProcessor(_diff, format='gitdiff')
483
482
484 response = Response(self.path_filter.get_raw_patch(diff))
483 response = Response(self.path_filter.get_raw_patch(diff))
485 response.content_type = 'text/plain'
484 response.content_type = 'text/plain'
486 charset = self._get_default_encoding(c)
485 charset = self._get_default_encoding(c)
487 if charset:
486 if charset:
488 response.charset = charset
487 response.charset = charset
489 return response
488 return response
490
489
491 # in case we ever end up here
490 # in case we ever end up here
492 raise HTTPNotFound()
491 raise HTTPNotFound()
493
492
494 @LoginRequired()
493 @LoginRequired()
495 @HasRepoPermissionAnyDecorator(
494 @HasRepoPermissionAnyDecorator(
496 'repository.read', 'repository.write', 'repository.admin')
495 'repository.read', 'repository.write', 'repository.admin')
497 @view_config(
496 @view_config(
498 route_name='repo_files_diff_2way_redirect', request_method='GET',
497 route_name='repo_files_diff_2way_redirect', request_method='GET',
499 renderer=None)
498 renderer=None)
500 def repo_files_diff_2way_redirect(self):
499 def repo_files_diff_2way_redirect(self):
501 """
500 """
502 Kept only to make OLD links work
501 Kept only to make OLD links work
503 """
502 """
504 f_path = self._get_f_path_unchecked(self.request.matchdict)
503 f_path = self._get_f_path_unchecked(self.request.matchdict)
505 diff1 = self.request.GET.get('diff1', '')
504 diff1 = self.request.GET.get('diff1', '')
506 diff2 = self.request.GET.get('diff2', '')
505 diff2 = self.request.GET.get('diff2', '')
507
506
508 if not any((diff1, diff2)):
507 if not any((diff1, diff2)):
509 h.flash(
508 h.flash(
510 'Need query parameter "diff1" or "diff2" to generate a diff.',
509 'Need query parameter "diff1" or "diff2" to generate a diff.',
511 category='error')
510 category='error')
512 raise HTTPBadRequest()
511 raise HTTPBadRequest()
513
512
514 compare_url = h.route_path(
513 compare_url = h.route_path(
515 'repo_compare',
514 'repo_compare',
516 repo_name=self.db_repo_name,
515 repo_name=self.db_repo_name,
517 source_ref_type='rev',
516 source_ref_type='rev',
518 source_ref=diff1,
517 source_ref=diff1,
519 target_ref_type='rev',
518 target_ref_type='rev',
520 target_ref=diff2,
519 target_ref=diff2,
521 _query=dict(f_path=f_path, diffmode='sideside',
520 _query=dict(f_path=f_path, diffmode='sideside',
522 target_repo=self.db_repo_name,))
521 target_repo=self.db_repo_name,))
523 raise HTTPFound(compare_url)
522 raise HTTPFound(compare_url)
524
523
525 @LoginRequired()
524 @LoginRequired()
526 @HasRepoPermissionAnyDecorator(
525 @HasRepoPermissionAnyDecorator(
527 'repository.read', 'repository.write', 'repository.admin')
526 'repository.read', 'repository.write', 'repository.admin')
528 @view_config(
527 @view_config(
529 route_name='repo_files', request_method='GET',
528 route_name='repo_files', request_method='GET',
530 renderer=None)
529 renderer=None)
531 @view_config(
530 @view_config(
532 route_name='repo_files:default_path', request_method='GET',
531 route_name='repo_files:default_path', request_method='GET',
533 renderer=None)
532 renderer=None)
534 @view_config(
533 @view_config(
535 route_name='repo_files:default_commit', request_method='GET',
534 route_name='repo_files:default_commit', request_method='GET',
536 renderer=None)
535 renderer=None)
537 @view_config(
536 @view_config(
538 route_name='repo_files:rendered', request_method='GET',
537 route_name='repo_files:rendered', request_method='GET',
539 renderer=None)
538 renderer=None)
540 @view_config(
539 @view_config(
541 route_name='repo_files:annotated', request_method='GET',
540 route_name='repo_files:annotated', request_method='GET',
542 renderer=None)
541 renderer=None)
543 def repo_files(self):
542 def repo_files(self):
544 c = self.load_default_context()
543 c = self.load_default_context()
545
544
546 view_name = getattr(self.request.matched_route, 'name', None)
545 view_name = getattr(self.request.matched_route, 'name', None)
547
546
548 c.annotate = view_name == 'repo_files:annotated'
547 c.annotate = view_name == 'repo_files:annotated'
549 # default is false, but .rst/.md files later are auto rendered, we can
548 # default is false, but .rst/.md files later are auto rendered, we can
550 # overwrite auto rendering by setting this GET flag
549 # overwrite auto rendering by setting this GET flag
551 c.renderer = view_name == 'repo_files:rendered' or \
550 c.renderer = view_name == 'repo_files:rendered' or \
552 not self.request.GET.get('no-render', False)
551 not self.request.GET.get('no-render', False)
553
552
554 # redirect to given commit_id from form if given
553 # redirect to given commit_id from form if given
555 get_commit_id = self.request.GET.get('at_rev', None)
554 get_commit_id = self.request.GET.get('at_rev', None)
556 if get_commit_id:
555 if get_commit_id:
557 self._get_commit_or_redirect(get_commit_id)
556 self._get_commit_or_redirect(get_commit_id)
558
557
559 commit_id, f_path = self._get_commit_and_path()
558 commit_id, f_path = self._get_commit_and_path()
560 c.commit = self._get_commit_or_redirect(commit_id)
559 c.commit = self._get_commit_or_redirect(commit_id)
561 c.branch = self.request.GET.get('branch', None)
560 c.branch = self.request.GET.get('branch', None)
562 c.f_path = f_path
561 c.f_path = f_path
563
562
564 # prev link
563 # prev link
565 try:
564 try:
566 prev_commit = c.commit.prev(c.branch)
565 prev_commit = c.commit.prev(c.branch)
567 c.prev_commit = prev_commit
566 c.prev_commit = prev_commit
568 c.url_prev = h.route_path(
567 c.url_prev = h.route_path(
569 'repo_files', repo_name=self.db_repo_name,
568 'repo_files', repo_name=self.db_repo_name,
570 commit_id=prev_commit.raw_id, f_path=f_path)
569 commit_id=prev_commit.raw_id, f_path=f_path)
571 if c.branch:
570 if c.branch:
572 c.url_prev += '?branch=%s' % c.branch
571 c.url_prev += '?branch=%s' % c.branch
573 except (CommitDoesNotExistError, VCSError):
572 except (CommitDoesNotExistError, VCSError):
574 c.url_prev = '#'
573 c.url_prev = '#'
575 c.prev_commit = EmptyCommit()
574 c.prev_commit = EmptyCommit()
576
575
577 # next link
576 # next link
578 try:
577 try:
579 next_commit = c.commit.next(c.branch)
578 next_commit = c.commit.next(c.branch)
580 c.next_commit = next_commit
579 c.next_commit = next_commit
581 c.url_next = h.route_path(
580 c.url_next = h.route_path(
582 'repo_files', repo_name=self.db_repo_name,
581 'repo_files', repo_name=self.db_repo_name,
583 commit_id=next_commit.raw_id, f_path=f_path)
582 commit_id=next_commit.raw_id, f_path=f_path)
584 if c.branch:
583 if c.branch:
585 c.url_next += '?branch=%s' % c.branch
584 c.url_next += '?branch=%s' % c.branch
586 except (CommitDoesNotExistError, VCSError):
585 except (CommitDoesNotExistError, VCSError):
587 c.url_next = '#'
586 c.url_next = '#'
588 c.next_commit = EmptyCommit()
587 c.next_commit = EmptyCommit()
589
588
590 # files or dirs
589 # files or dirs
591 try:
590 try:
592 c.file = c.commit.get_node(f_path)
591 c.file = c.commit.get_node(f_path)
593 c.file_author = True
592 c.file_author = True
594 c.file_tree = ''
593 c.file_tree = ''
595
594
596 # load file content
595 # load file content
597 if c.file.is_file():
596 if c.file.is_file():
598 c.lf_node = c.file.get_largefile_node()
597 c.lf_node = c.file.get_largefile_node()
599
598
600 c.file_source_page = 'true'
599 c.file_source_page = 'true'
601 c.file_last_commit = c.file.last_commit
600 c.file_last_commit = c.file.last_commit
602 if c.file.size < c.visual.cut_off_limit_diff:
601 if c.file.size < c.visual.cut_off_limit_diff:
603 if c.annotate: # annotation has precedence over renderer
602 if c.annotate: # annotation has precedence over renderer
604 c.annotated_lines = filenode_as_annotated_lines_tokens(
603 c.annotated_lines = filenode_as_annotated_lines_tokens(
605 c.file
604 c.file
606 )
605 )
607 else:
606 else:
608 c.renderer = (
607 c.renderer = (
609 c.renderer and h.renderer_from_filename(c.file.path)
608 c.renderer and h.renderer_from_filename(c.file.path)
610 )
609 )
611 if not c.renderer:
610 if not c.renderer:
612 c.lines = filenode_as_lines_tokens(c.file)
611 c.lines = filenode_as_lines_tokens(c.file)
613
612
614 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
613 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
615 commit_id, self.rhodecode_vcs_repo)
614 commit_id, self.rhodecode_vcs_repo)
616 c.on_branch_head = is_head
615 c.on_branch_head = is_head
617
616
618 branch = c.commit.branch if (
617 branch = c.commit.branch if (
619 c.commit.branch and '/' not in c.commit.branch) else None
618 c.commit.branch and '/' not in c.commit.branch) else None
620 c.branch_or_raw_id = branch or c.commit.raw_id
619 c.branch_or_raw_id = branch or c.commit.raw_id
621 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
620 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
622
621
623 author = c.file_last_commit.author
622 author = c.file_last_commit.author
624 c.authors = [[
623 c.authors = [[
625 h.email(author),
624 h.email(author),
626 h.person(author, 'username_or_name_or_email'),
625 h.person(author, 'username_or_name_or_email'),
627 1
626 1
628 ]]
627 ]]
629
628
630 else: # load tree content at path
629 else: # load tree content at path
631 c.file_source_page = 'false'
630 c.file_source_page = 'false'
632 c.authors = []
631 c.authors = []
633 # this loads a simple tree without metadata to speed things up
632 # this loads a simple tree without metadata to speed things up
634 # later via ajax we call repo_nodetree_full and fetch whole
633 # later via ajax we call repo_nodetree_full and fetch whole
635 c.file_tree = self._get_tree_at_commit(
634 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path)
636 c, c.commit.raw_id, f_path)
637
635
638 except RepositoryError as e:
636 except RepositoryError as e:
639 h.flash(safe_str(h.escape(e)), category='error')
637 h.flash(safe_str(h.escape(e)), category='error')
640 raise HTTPNotFound()
638 raise HTTPNotFound()
641
639
642 if self.request.environ.get('HTTP_X_PJAX'):
640 if self.request.environ.get('HTTP_X_PJAX'):
643 html = render('rhodecode:templates/files/files_pjax.mako',
641 html = render('rhodecode:templates/files/files_pjax.mako',
644 self._get_template_context(c), self.request)
642 self._get_template_context(c), self.request)
645 else:
643 else:
646 html = render('rhodecode:templates/files/files.mako',
644 html = render('rhodecode:templates/files/files.mako',
647 self._get_template_context(c), self.request)
645 self._get_template_context(c), self.request)
648 return Response(html)
646 return Response(html)
649
647
650 @HasRepoPermissionAnyDecorator(
648 @HasRepoPermissionAnyDecorator(
651 'repository.read', 'repository.write', 'repository.admin')
649 'repository.read', 'repository.write', 'repository.admin')
652 @view_config(
650 @view_config(
653 route_name='repo_files:annotated_previous', request_method='GET',
651 route_name='repo_files:annotated_previous', request_method='GET',
654 renderer=None)
652 renderer=None)
655 def repo_files_annotated_previous(self):
653 def repo_files_annotated_previous(self):
656 self.load_default_context()
654 self.load_default_context()
657
655
658 commit_id, f_path = self._get_commit_and_path()
656 commit_id, f_path = self._get_commit_and_path()
659 commit = self._get_commit_or_redirect(commit_id)
657 commit = self._get_commit_or_redirect(commit_id)
660 prev_commit_id = commit.raw_id
658 prev_commit_id = commit.raw_id
661 line_anchor = self.request.GET.get('line_anchor')
659 line_anchor = self.request.GET.get('line_anchor')
662 is_file = False
660 is_file = False
663 try:
661 try:
664 _file = commit.get_node(f_path)
662 _file = commit.get_node(f_path)
665 is_file = _file.is_file()
663 is_file = _file.is_file()
666 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
664 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
667 pass
665 pass
668
666
669 if is_file:
667 if is_file:
670 history = commit.get_path_history(f_path)
668 history = commit.get_path_history(f_path)
671 prev_commit_id = history[1].raw_id \
669 prev_commit_id = history[1].raw_id \
672 if len(history) > 1 else prev_commit_id
670 if len(history) > 1 else prev_commit_id
673 prev_url = h.route_path(
671 prev_url = h.route_path(
674 'repo_files:annotated', repo_name=self.db_repo_name,
672 'repo_files:annotated', repo_name=self.db_repo_name,
675 commit_id=prev_commit_id, f_path=f_path,
673 commit_id=prev_commit_id, f_path=f_path,
676 _anchor='L{}'.format(line_anchor))
674 _anchor='L{}'.format(line_anchor))
677
675
678 raise HTTPFound(prev_url)
676 raise HTTPFound(prev_url)
679
677
680 @LoginRequired()
678 @LoginRequired()
681 @HasRepoPermissionAnyDecorator(
679 @HasRepoPermissionAnyDecorator(
682 'repository.read', 'repository.write', 'repository.admin')
680 'repository.read', 'repository.write', 'repository.admin')
683 @view_config(
681 @view_config(
684 route_name='repo_nodetree_full', request_method='GET',
682 route_name='repo_nodetree_full', request_method='GET',
685 renderer=None, xhr=True)
683 renderer=None, xhr=True)
686 @view_config(
684 @view_config(
687 route_name='repo_nodetree_full:default_path', request_method='GET',
685 route_name='repo_nodetree_full:default_path', request_method='GET',
688 renderer=None, xhr=True)
686 renderer=None, xhr=True)
689 def repo_nodetree_full(self):
687 def repo_nodetree_full(self):
690 """
688 """
691 Returns rendered html of file tree that contains commit date,
689 Returns rendered html of file tree that contains commit date,
692 author, commit_id for the specified combination of
690 author, commit_id for the specified combination of
693 repo, commit_id and file path
691 repo, commit_id and file path
694 """
692 """
695 c = self.load_default_context()
693 c = self.load_default_context()
696
694
697 commit_id, f_path = self._get_commit_and_path()
695 commit_id, f_path = self._get_commit_and_path()
698 commit = self._get_commit_or_redirect(commit_id)
696 commit = self._get_commit_or_redirect(commit_id)
699 try:
697 try:
700 dir_node = commit.get_node(f_path)
698 dir_node = commit.get_node(f_path)
701 except RepositoryError as e:
699 except RepositoryError as e:
702 return Response('error: {}'.format(h.escape(safe_str(e))))
700 return Response('error: {}'.format(h.escape(safe_str(e))))
703
701
704 if dir_node.is_file():
702 if dir_node.is_file():
705 return Response('')
703 return Response('')
706
704
707 c.file = dir_node
705 c.file = dir_node
708 c.commit = commit
706 c.commit = commit
709
707
710 html = self._get_tree_at_commit(
708 html = self._get_tree_at_commit(
711 c, commit.raw_id, dir_node.path, full_load=True)
709 c, commit.raw_id, dir_node.path, full_load=True)
712
710
713 return Response(html)
711 return Response(html)
714
712
715 def _get_attachement_headers(self, f_path):
713 def _get_attachement_headers(self, f_path):
716 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
714 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
717 safe_path = f_name.replace('"', '\\"')
715 safe_path = f_name.replace('"', '\\"')
718 encoded_path = urllib.quote(f_name)
716 encoded_path = urllib.quote(f_name)
719
717
720 return "attachment; " \
718 return "attachment; " \
721 "filename=\"{}\"; " \
719 "filename=\"{}\"; " \
722 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
720 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
723
721
724 @LoginRequired()
722 @LoginRequired()
725 @HasRepoPermissionAnyDecorator(
723 @HasRepoPermissionAnyDecorator(
726 'repository.read', 'repository.write', 'repository.admin')
724 'repository.read', 'repository.write', 'repository.admin')
727 @view_config(
725 @view_config(
728 route_name='repo_file_raw', request_method='GET',
726 route_name='repo_file_raw', request_method='GET',
729 renderer=None)
727 renderer=None)
730 def repo_file_raw(self):
728 def repo_file_raw(self):
731 """
729 """
732 Action for show as raw, some mimetypes are "rendered",
730 Action for show as raw, some mimetypes are "rendered",
733 those include images, icons.
731 those include images, icons.
734 """
732 """
735 c = self.load_default_context()
733 c = self.load_default_context()
736
734
737 commit_id, f_path = self._get_commit_and_path()
735 commit_id, f_path = self._get_commit_and_path()
738 commit = self._get_commit_or_redirect(commit_id)
736 commit = self._get_commit_or_redirect(commit_id)
739 file_node = self._get_filenode_or_redirect(commit, f_path)
737 file_node = self._get_filenode_or_redirect(commit, f_path)
740
738
741 raw_mimetype_mapping = {
739 raw_mimetype_mapping = {
742 # map original mimetype to a mimetype used for "show as raw"
740 # map original mimetype to a mimetype used for "show as raw"
743 # you can also provide a content-disposition to override the
741 # you can also provide a content-disposition to override the
744 # default "attachment" disposition.
742 # default "attachment" disposition.
745 # orig_type: (new_type, new_dispo)
743 # orig_type: (new_type, new_dispo)
746
744
747 # show images inline:
745 # show images inline:
748 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
746 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
749 # for example render an SVG with javascript inside or even render
747 # for example render an SVG with javascript inside or even render
750 # HTML.
748 # HTML.
751 'image/x-icon': ('image/x-icon', 'inline'),
749 'image/x-icon': ('image/x-icon', 'inline'),
752 'image/png': ('image/png', 'inline'),
750 'image/png': ('image/png', 'inline'),
753 'image/gif': ('image/gif', 'inline'),
751 'image/gif': ('image/gif', 'inline'),
754 'image/jpeg': ('image/jpeg', 'inline'),
752 'image/jpeg': ('image/jpeg', 'inline'),
755 'application/pdf': ('application/pdf', 'inline'),
753 'application/pdf': ('application/pdf', 'inline'),
756 }
754 }
757
755
758 mimetype = file_node.mimetype
756 mimetype = file_node.mimetype
759 try:
757 try:
760 mimetype, disposition = raw_mimetype_mapping[mimetype]
758 mimetype, disposition = raw_mimetype_mapping[mimetype]
761 except KeyError:
759 except KeyError:
762 # we don't know anything special about this, handle it safely
760 # we don't know anything special about this, handle it safely
763 if file_node.is_binary:
761 if file_node.is_binary:
764 # do same as download raw for binary files
762 # do same as download raw for binary files
765 mimetype, disposition = 'application/octet-stream', 'attachment'
763 mimetype, disposition = 'application/octet-stream', 'attachment'
766 else:
764 else:
767 # do not just use the original mimetype, but force text/plain,
765 # do not just use the original mimetype, but force text/plain,
768 # otherwise it would serve text/html and that might be unsafe.
766 # otherwise it would serve text/html and that might be unsafe.
769 # Note: underlying vcs library fakes text/plain mimetype if the
767 # Note: underlying vcs library fakes text/plain mimetype if the
770 # mimetype can not be determined and it thinks it is not
768 # mimetype can not be determined and it thinks it is not
771 # binary.This might lead to erroneous text display in some
769 # binary.This might lead to erroneous text display in some
772 # cases, but helps in other cases, like with text files
770 # cases, but helps in other cases, like with text files
773 # without extension.
771 # without extension.
774 mimetype, disposition = 'text/plain', 'inline'
772 mimetype, disposition = 'text/plain', 'inline'
775
773
776 if disposition == 'attachment':
774 if disposition == 'attachment':
777 disposition = self._get_attachement_headers(f_path)
775 disposition = self._get_attachement_headers(f_path)
778
776
779 def stream_node():
777 def stream_node():
780 yield file_node.raw_bytes
778 yield file_node.raw_bytes
781
779
782 response = Response(app_iter=stream_node())
780 response = Response(app_iter=stream_node())
783 response.content_disposition = disposition
781 response.content_disposition = disposition
784 response.content_type = mimetype
782 response.content_type = mimetype
785
783
786 charset = self._get_default_encoding(c)
784 charset = self._get_default_encoding(c)
787 if charset:
785 if charset:
788 response.charset = charset
786 response.charset = charset
789
787
790 return response
788 return response
791
789
792 @LoginRequired()
790 @LoginRequired()
793 @HasRepoPermissionAnyDecorator(
791 @HasRepoPermissionAnyDecorator(
794 'repository.read', 'repository.write', 'repository.admin')
792 'repository.read', 'repository.write', 'repository.admin')
795 @view_config(
793 @view_config(
796 route_name='repo_file_download', request_method='GET',
794 route_name='repo_file_download', request_method='GET',
797 renderer=None)
795 renderer=None)
798 @view_config(
796 @view_config(
799 route_name='repo_file_download:legacy', request_method='GET',
797 route_name='repo_file_download:legacy', request_method='GET',
800 renderer=None)
798 renderer=None)
801 def repo_file_download(self):
799 def repo_file_download(self):
802 c = self.load_default_context()
800 c = self.load_default_context()
803
801
804 commit_id, f_path = self._get_commit_and_path()
802 commit_id, f_path = self._get_commit_and_path()
805 commit = self._get_commit_or_redirect(commit_id)
803 commit = self._get_commit_or_redirect(commit_id)
806 file_node = self._get_filenode_or_redirect(commit, f_path)
804 file_node = self._get_filenode_or_redirect(commit, f_path)
807
805
808 if self.request.GET.get('lf'):
806 if self.request.GET.get('lf'):
809 # only if lf get flag is passed, we download this file
807 # only if lf get flag is passed, we download this file
810 # as LFS/Largefile
808 # as LFS/Largefile
811 lf_node = file_node.get_largefile_node()
809 lf_node = file_node.get_largefile_node()
812 if lf_node:
810 if lf_node:
813 # overwrite our pointer with the REAL large-file
811 # overwrite our pointer with the REAL large-file
814 file_node = lf_node
812 file_node = lf_node
815
813
816 disposition = self._get_attachement_headers(f_path)
814 disposition = self._get_attachement_headers(f_path)
817
815
818 def stream_node():
816 def stream_node():
819 yield file_node.raw_bytes
817 yield file_node.raw_bytes
820
818
821 response = Response(app_iter=stream_node())
819 response = Response(app_iter=stream_node())
822 response.content_disposition = disposition
820 response.content_disposition = disposition
823 response.content_type = file_node.mimetype
821 response.content_type = file_node.mimetype
824
822
825 charset = self._get_default_encoding(c)
823 charset = self._get_default_encoding(c)
826 if charset:
824 if charset:
827 response.charset = charset
825 response.charset = charset
828
826
829 return response
827 return response
830
828
831 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
829 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
832
830
833 cache_seconds = safe_int(
831 cache_seconds = safe_int(
834 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
832 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
835 cache_on = cache_seconds > 0
833 cache_on = cache_seconds > 0
836 log.debug(
834 log.debug(
837 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
835 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
838 'with caching: %s[TTL: %ss]' % (
836 'with caching: %s[TTL: %ss]' % (
839 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
837 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
840
838
841 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
839 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
842 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
840 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
843
841
844 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
842 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
845 condition=cache_on)
843 condition=cache_on)
846 def compute_file_search(repo_id, commit_id, f_path):
844 def compute_file_search(repo_id, commit_id, f_path):
847 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
845 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
848 repo_id, commit_id, f_path)
846 repo_id, commit_id, f_path)
849 try:
847 try:
850 _d, _f = ScmModel().get_nodes(
848 _d, _f = ScmModel().get_nodes(
851 repo_name, commit_id, f_path, flat=False)
849 repo_name, commit_id, f_path, flat=False)
852 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
850 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
853 log.exception(safe_str(e))
851 log.exception(safe_str(e))
854 h.flash(safe_str(h.escape(e)), category='error')
852 h.flash(safe_str(h.escape(e)), category='error')
855 raise HTTPFound(h.route_path(
853 raise HTTPFound(h.route_path(
856 'repo_files', repo_name=self.db_repo_name,
854 'repo_files', repo_name=self.db_repo_name,
857 commit_id='tip', f_path='/'))
855 commit_id='tip', f_path='/'))
858 return _d + _f
856 return _d + _f
859
857
860 return compute_file_search(self.db_repo.repo_id, commit_id, f_path)
858 return compute_file_search(self.db_repo.repo_id, commit_id, f_path)
861
859
862 @LoginRequired()
860 @LoginRequired()
863 @HasRepoPermissionAnyDecorator(
861 @HasRepoPermissionAnyDecorator(
864 'repository.read', 'repository.write', 'repository.admin')
862 'repository.read', 'repository.write', 'repository.admin')
865 @view_config(
863 @view_config(
866 route_name='repo_files_nodelist', request_method='GET',
864 route_name='repo_files_nodelist', request_method='GET',
867 renderer='json_ext', xhr=True)
865 renderer='json_ext', xhr=True)
868 def repo_nodelist(self):
866 def repo_nodelist(self):
869 self.load_default_context()
867 self.load_default_context()
870
868
871 commit_id, f_path = self._get_commit_and_path()
869 commit_id, f_path = self._get_commit_and_path()
872 commit = self._get_commit_or_redirect(commit_id)
870 commit = self._get_commit_or_redirect(commit_id)
873
871
874 metadata = self._get_nodelist_at_commit(
872 metadata = self._get_nodelist_at_commit(
875 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
873 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
876 return {'nodes': metadata}
874 return {'nodes': metadata}
877
875
878 def _create_references(
876 def _create_references(
879 self, branches_or_tags, symbolic_reference, f_path):
877 self, branches_or_tags, symbolic_reference, f_path):
880 items = []
878 items = []
881 for name, commit_id in branches_or_tags.items():
879 for name, commit_id in branches_or_tags.items():
882 sym_ref = symbolic_reference(commit_id, name, f_path)
880 sym_ref = symbolic_reference(commit_id, name, f_path)
883 items.append((sym_ref, name))
881 items.append((sym_ref, name))
884 return items
882 return items
885
883
886 def _symbolic_reference(self, commit_id, name, f_path):
884 def _symbolic_reference(self, commit_id, name, f_path):
887 return commit_id
885 return commit_id
888
886
889 def _symbolic_reference_svn(self, commit_id, name, f_path):
887 def _symbolic_reference_svn(self, commit_id, name, f_path):
890 new_f_path = vcspath.join(name, f_path)
888 new_f_path = vcspath.join(name, f_path)
891 return u'%s@%s' % (new_f_path, commit_id)
889 return u'%s@%s' % (new_f_path, commit_id)
892
890
893 def _get_node_history(self, commit_obj, f_path, commits=None):
891 def _get_node_history(self, commit_obj, f_path, commits=None):
894 """
892 """
895 get commit history for given node
893 get commit history for given node
896
894
897 :param commit_obj: commit to calculate history
895 :param commit_obj: commit to calculate history
898 :param f_path: path for node to calculate history for
896 :param f_path: path for node to calculate history for
899 :param commits: if passed don't calculate history and take
897 :param commits: if passed don't calculate history and take
900 commits defined in this list
898 commits defined in this list
901 """
899 """
902 _ = self.request.translate
900 _ = self.request.translate
903
901
904 # calculate history based on tip
902 # calculate history based on tip
905 tip = self.rhodecode_vcs_repo.get_commit()
903 tip = self.rhodecode_vcs_repo.get_commit()
906 if commits is None:
904 if commits is None:
907 pre_load = ["author", "branch"]
905 pre_load = ["author", "branch"]
908 try:
906 try:
909 commits = tip.get_path_history(f_path, pre_load=pre_load)
907 commits = tip.get_path_history(f_path, pre_load=pre_load)
910 except (NodeDoesNotExistError, CommitError):
908 except (NodeDoesNotExistError, CommitError):
911 # this node is not present at tip!
909 # this node is not present at tip!
912 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
910 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
913
911
914 history = []
912 history = []
915 commits_group = ([], _("Changesets"))
913 commits_group = ([], _("Changesets"))
916 for commit in commits:
914 for commit in commits:
917 branch = ' (%s)' % commit.branch if commit.branch else ''
915 branch = ' (%s)' % commit.branch if commit.branch else ''
918 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
916 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
919 commits_group[0].append((commit.raw_id, n_desc,))
917 commits_group[0].append((commit.raw_id, n_desc,))
920 history.append(commits_group)
918 history.append(commits_group)
921
919
922 symbolic_reference = self._symbolic_reference
920 symbolic_reference = self._symbolic_reference
923
921
924 if self.rhodecode_vcs_repo.alias == 'svn':
922 if self.rhodecode_vcs_repo.alias == 'svn':
925 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
923 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
926 f_path, self.rhodecode_vcs_repo)
924 f_path, self.rhodecode_vcs_repo)
927 if adjusted_f_path != f_path:
925 if adjusted_f_path != f_path:
928 log.debug(
926 log.debug(
929 'Recognized svn tag or branch in file "%s", using svn '
927 'Recognized svn tag or branch in file "%s", using svn '
930 'specific symbolic references', f_path)
928 'specific symbolic references', f_path)
931 f_path = adjusted_f_path
929 f_path = adjusted_f_path
932 symbolic_reference = self._symbolic_reference_svn
930 symbolic_reference = self._symbolic_reference_svn
933
931
934 branches = self._create_references(
932 branches = self._create_references(
935 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
933 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
936 branches_group = (branches, _("Branches"))
934 branches_group = (branches, _("Branches"))
937
935
938 tags = self._create_references(
936 tags = self._create_references(
939 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
937 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
940 tags_group = (tags, _("Tags"))
938 tags_group = (tags, _("Tags"))
941
939
942 history.append(branches_group)
940 history.append(branches_group)
943 history.append(tags_group)
941 history.append(tags_group)
944
942
945 return history, commits
943 return history, commits
946
944
947 @LoginRequired()
945 @LoginRequired()
948 @HasRepoPermissionAnyDecorator(
946 @HasRepoPermissionAnyDecorator(
949 'repository.read', 'repository.write', 'repository.admin')
947 'repository.read', 'repository.write', 'repository.admin')
950 @view_config(
948 @view_config(
951 route_name='repo_file_history', request_method='GET',
949 route_name='repo_file_history', request_method='GET',
952 renderer='json_ext')
950 renderer='json_ext')
953 def repo_file_history(self):
951 def repo_file_history(self):
954 self.load_default_context()
952 self.load_default_context()
955
953
956 commit_id, f_path = self._get_commit_and_path()
954 commit_id, f_path = self._get_commit_and_path()
957 commit = self._get_commit_or_redirect(commit_id)
955 commit = self._get_commit_or_redirect(commit_id)
958 file_node = self._get_filenode_or_redirect(commit, f_path)
956 file_node = self._get_filenode_or_redirect(commit, f_path)
959
957
960 if file_node.is_file():
958 if file_node.is_file():
961 file_history, _hist = self._get_node_history(commit, f_path)
959 file_history, _hist = self._get_node_history(commit, f_path)
962
960
963 res = []
961 res = []
964 for obj in file_history:
962 for obj in file_history:
965 res.append({
963 res.append({
966 'text': obj[1],
964 'text': obj[1],
967 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
965 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
968 })
966 })
969
967
970 data = {
968 data = {
971 'more': False,
969 'more': False,
972 'results': res
970 'results': res
973 }
971 }
974 return data
972 return data
975
973
976 log.warning('Cannot fetch history for directory')
974 log.warning('Cannot fetch history for directory')
977 raise HTTPBadRequest()
975 raise HTTPBadRequest()
978
976
979 @LoginRequired()
977 @LoginRequired()
980 @HasRepoPermissionAnyDecorator(
978 @HasRepoPermissionAnyDecorator(
981 'repository.read', 'repository.write', 'repository.admin')
979 'repository.read', 'repository.write', 'repository.admin')
982 @view_config(
980 @view_config(
983 route_name='repo_file_authors', request_method='GET',
981 route_name='repo_file_authors', request_method='GET',
984 renderer='rhodecode:templates/files/file_authors_box.mako')
982 renderer='rhodecode:templates/files/file_authors_box.mako')
985 def repo_file_authors(self):
983 def repo_file_authors(self):
986 c = self.load_default_context()
984 c = self.load_default_context()
987
985
988 commit_id, f_path = self._get_commit_and_path()
986 commit_id, f_path = self._get_commit_and_path()
989 commit = self._get_commit_or_redirect(commit_id)
987 commit = self._get_commit_or_redirect(commit_id)
990 file_node = self._get_filenode_or_redirect(commit, f_path)
988 file_node = self._get_filenode_or_redirect(commit, f_path)
991
989
992 if not file_node.is_file():
990 if not file_node.is_file():
993 raise HTTPBadRequest()
991 raise HTTPBadRequest()
994
992
995 c.file_last_commit = file_node.last_commit
993 c.file_last_commit = file_node.last_commit
996 if self.request.GET.get('annotate') == '1':
994 if self.request.GET.get('annotate') == '1':
997 # use _hist from annotation if annotation mode is on
995 # use _hist from annotation if annotation mode is on
998 commit_ids = set(x[1] for x in file_node.annotate)
996 commit_ids = set(x[1] for x in file_node.annotate)
999 _hist = (
997 _hist = (
1000 self.rhodecode_vcs_repo.get_commit(commit_id)
998 self.rhodecode_vcs_repo.get_commit(commit_id)
1001 for commit_id in commit_ids)
999 for commit_id in commit_ids)
1002 else:
1000 else:
1003 _f_history, _hist = self._get_node_history(commit, f_path)
1001 _f_history, _hist = self._get_node_history(commit, f_path)
1004 c.file_author = False
1002 c.file_author = False
1005
1003
1006 unique = collections.OrderedDict()
1004 unique = collections.OrderedDict()
1007 for commit in _hist:
1005 for commit in _hist:
1008 author = commit.author
1006 author = commit.author
1009 if author not in unique:
1007 if author not in unique:
1010 unique[commit.author] = [
1008 unique[commit.author] = [
1011 h.email(author),
1009 h.email(author),
1012 h.person(author, 'username_or_name_or_email'),
1010 h.person(author, 'username_or_name_or_email'),
1013 1 # counter
1011 1 # counter
1014 ]
1012 ]
1015
1013
1016 else:
1014 else:
1017 # increase counter
1015 # increase counter
1018 unique[commit.author][2] += 1
1016 unique[commit.author][2] += 1
1019
1017
1020 c.authors = [val for val in unique.values()]
1018 c.authors = [val for val in unique.values()]
1021
1019
1022 return self._get_template_context(c)
1020 return self._get_template_context(c)
1023
1021
1024 @LoginRequired()
1022 @LoginRequired()
1025 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1023 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1026 @view_config(
1024 @view_config(
1027 route_name='repo_files_remove_file', request_method='GET',
1025 route_name='repo_files_remove_file', request_method='GET',
1028 renderer='rhodecode:templates/files/files_delete.mako')
1026 renderer='rhodecode:templates/files/files_delete.mako')
1029 def repo_files_remove_file(self):
1027 def repo_files_remove_file(self):
1030 _ = self.request.translate
1028 _ = self.request.translate
1031 c = self.load_default_context()
1029 c = self.load_default_context()
1032 commit_id, f_path = self._get_commit_and_path()
1030 commit_id, f_path = self._get_commit_and_path()
1033
1031
1034 self._ensure_not_locked()
1032 self._ensure_not_locked()
1035 _branch_name, _sha_commit_id, is_head = \
1033 _branch_name, _sha_commit_id, is_head = \
1036 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1034 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1037
1035
1038 if not is_head:
1036 if not is_head:
1039 h.flash(_('You can only delete files with commit '
1037 h.flash(_('You can only delete files with commit '
1040 'being a valid branch head.'), category='warning')
1038 'being a valid branch head.'), category='warning')
1041 raise HTTPFound(
1039 raise HTTPFound(
1042 h.route_path('repo_files',
1040 h.route_path('repo_files',
1043 repo_name=self.db_repo_name, commit_id='tip',
1041 repo_name=self.db_repo_name, commit_id='tip',
1044 f_path=f_path))
1042 f_path=f_path))
1045
1043
1046 self.check_branch_permission(_branch_name)
1044 self.check_branch_permission(_branch_name)
1047 c.commit = self._get_commit_or_redirect(commit_id)
1045 c.commit = self._get_commit_or_redirect(commit_id)
1048 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1046 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1049
1047
1050 c.default_message = _(
1048 c.default_message = _(
1051 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1049 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1052 c.f_path = f_path
1050 c.f_path = f_path
1053
1051
1054 return self._get_template_context(c)
1052 return self._get_template_context(c)
1055
1053
1056 @LoginRequired()
1054 @LoginRequired()
1057 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1055 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1058 @CSRFRequired()
1056 @CSRFRequired()
1059 @view_config(
1057 @view_config(
1060 route_name='repo_files_delete_file', request_method='POST',
1058 route_name='repo_files_delete_file', request_method='POST',
1061 renderer=None)
1059 renderer=None)
1062 def repo_files_delete_file(self):
1060 def repo_files_delete_file(self):
1063 _ = self.request.translate
1061 _ = self.request.translate
1064
1062
1065 c = self.load_default_context()
1063 c = self.load_default_context()
1066 commit_id, f_path = self._get_commit_and_path()
1064 commit_id, f_path = self._get_commit_and_path()
1067
1065
1068 self._ensure_not_locked()
1066 self._ensure_not_locked()
1069 _branch_name, _sha_commit_id, is_head = \
1067 _branch_name, _sha_commit_id, is_head = \
1070 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1068 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1071
1069
1072 if not is_head:
1070 if not is_head:
1073 h.flash(_('You can only delete files with commit '
1071 h.flash(_('You can only delete files with commit '
1074 'being a valid branch head.'), category='warning')
1072 'being a valid branch head.'), category='warning')
1075 raise HTTPFound(
1073 raise HTTPFound(
1076 h.route_path('repo_files',
1074 h.route_path('repo_files',
1077 repo_name=self.db_repo_name, commit_id='tip',
1075 repo_name=self.db_repo_name, commit_id='tip',
1078 f_path=f_path))
1076 f_path=f_path))
1079 self.check_branch_permission(_branch_name)
1077 self.check_branch_permission(_branch_name)
1080
1078
1081 c.commit = self._get_commit_or_redirect(commit_id)
1079 c.commit = self._get_commit_or_redirect(commit_id)
1082 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1080 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1083
1081
1084 c.default_message = _(
1082 c.default_message = _(
1085 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1083 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1086 c.f_path = f_path
1084 c.f_path = f_path
1087 node_path = f_path
1085 node_path = f_path
1088 author = self._rhodecode_db_user.full_contact
1086 author = self._rhodecode_db_user.full_contact
1089 message = self.request.POST.get('message') or c.default_message
1087 message = self.request.POST.get('message') or c.default_message
1090 try:
1088 try:
1091 nodes = {
1089 nodes = {
1092 node_path: {
1090 node_path: {
1093 'content': ''
1091 'content': ''
1094 }
1092 }
1095 }
1093 }
1096 ScmModel().delete_nodes(
1094 ScmModel().delete_nodes(
1097 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1095 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1098 message=message,
1096 message=message,
1099 nodes=nodes,
1097 nodes=nodes,
1100 parent_commit=c.commit,
1098 parent_commit=c.commit,
1101 author=author,
1099 author=author,
1102 )
1100 )
1103
1101
1104 h.flash(
1102 h.flash(
1105 _('Successfully deleted file `{}`').format(
1103 _('Successfully deleted file `{}`').format(
1106 h.escape(f_path)), category='success')
1104 h.escape(f_path)), category='success')
1107 except Exception:
1105 except Exception:
1108 log.exception('Error during commit operation')
1106 log.exception('Error during commit operation')
1109 h.flash(_('Error occurred during commit'), category='error')
1107 h.flash(_('Error occurred during commit'), category='error')
1110 raise HTTPFound(
1108 raise HTTPFound(
1111 h.route_path('repo_commit', repo_name=self.db_repo_name,
1109 h.route_path('repo_commit', repo_name=self.db_repo_name,
1112 commit_id='tip'))
1110 commit_id='tip'))
1113
1111
1114 @LoginRequired()
1112 @LoginRequired()
1115 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1113 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1116 @view_config(
1114 @view_config(
1117 route_name='repo_files_edit_file', request_method='GET',
1115 route_name='repo_files_edit_file', request_method='GET',
1118 renderer='rhodecode:templates/files/files_edit.mako')
1116 renderer='rhodecode:templates/files/files_edit.mako')
1119 def repo_files_edit_file(self):
1117 def repo_files_edit_file(self):
1120 _ = self.request.translate
1118 _ = self.request.translate
1121 c = self.load_default_context()
1119 c = self.load_default_context()
1122 commit_id, f_path = self._get_commit_and_path()
1120 commit_id, f_path = self._get_commit_and_path()
1123
1121
1124 self._ensure_not_locked()
1122 self._ensure_not_locked()
1125 _branch_name, _sha_commit_id, is_head = \
1123 _branch_name, _sha_commit_id, is_head = \
1126 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1124 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1127
1125
1128 if not is_head:
1126 if not is_head:
1129 h.flash(_('You can only edit files with commit '
1127 h.flash(_('You can only edit files with commit '
1130 'being a valid branch head.'), category='warning')
1128 'being a valid branch head.'), category='warning')
1131 raise HTTPFound(
1129 raise HTTPFound(
1132 h.route_path('repo_files',
1130 h.route_path('repo_files',
1133 repo_name=self.db_repo_name, commit_id='tip',
1131 repo_name=self.db_repo_name, commit_id='tip',
1134 f_path=f_path))
1132 f_path=f_path))
1135 self.check_branch_permission(_branch_name)
1133 self.check_branch_permission(_branch_name)
1136
1134
1137 c.commit = self._get_commit_or_redirect(commit_id)
1135 c.commit = self._get_commit_or_redirect(commit_id)
1138 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1136 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1139
1137
1140 if c.file.is_binary:
1138 if c.file.is_binary:
1141 files_url = h.route_path(
1139 files_url = h.route_path(
1142 'repo_files',
1140 'repo_files',
1143 repo_name=self.db_repo_name,
1141 repo_name=self.db_repo_name,
1144 commit_id=c.commit.raw_id, f_path=f_path)
1142 commit_id=c.commit.raw_id, f_path=f_path)
1145 raise HTTPFound(files_url)
1143 raise HTTPFound(files_url)
1146
1144
1147 c.default_message = _(
1145 c.default_message = _(
1148 'Edited file {} via RhodeCode Enterprise').format(f_path)
1146 'Edited file {} via RhodeCode Enterprise').format(f_path)
1149 c.f_path = f_path
1147 c.f_path = f_path
1150
1148
1151 return self._get_template_context(c)
1149 return self._get_template_context(c)
1152
1150
1153 @LoginRequired()
1151 @LoginRequired()
1154 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1152 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1155 @CSRFRequired()
1153 @CSRFRequired()
1156 @view_config(
1154 @view_config(
1157 route_name='repo_files_update_file', request_method='POST',
1155 route_name='repo_files_update_file', request_method='POST',
1158 renderer=None)
1156 renderer=None)
1159 def repo_files_update_file(self):
1157 def repo_files_update_file(self):
1160 _ = self.request.translate
1158 _ = self.request.translate
1161 c = self.load_default_context()
1159 c = self.load_default_context()
1162 commit_id, f_path = self._get_commit_and_path()
1160 commit_id, f_path = self._get_commit_and_path()
1163
1161
1164 self._ensure_not_locked()
1162 self._ensure_not_locked()
1165 _branch_name, _sha_commit_id, is_head = \
1163 _branch_name, _sha_commit_id, is_head = \
1166 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1164 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1167
1165
1168 if not is_head:
1166 if not is_head:
1169 h.flash(_('You can only edit files with commit '
1167 h.flash(_('You can only edit files with commit '
1170 'being a valid branch head.'), category='warning')
1168 'being a valid branch head.'), category='warning')
1171 raise HTTPFound(
1169 raise HTTPFound(
1172 h.route_path('repo_files',
1170 h.route_path('repo_files',
1173 repo_name=self.db_repo_name, commit_id='tip',
1171 repo_name=self.db_repo_name, commit_id='tip',
1174 f_path=f_path))
1172 f_path=f_path))
1175
1173
1176 self.check_branch_permission(_branch_name)
1174 self.check_branch_permission(_branch_name)
1177
1175
1178 c.commit = self._get_commit_or_redirect(commit_id)
1176 c.commit = self._get_commit_or_redirect(commit_id)
1179 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1177 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1180
1178
1181 if c.file.is_binary:
1179 if c.file.is_binary:
1182 raise HTTPFound(
1180 raise HTTPFound(
1183 h.route_path('repo_files',
1181 h.route_path('repo_files',
1184 repo_name=self.db_repo_name,
1182 repo_name=self.db_repo_name,
1185 commit_id=c.commit.raw_id,
1183 commit_id=c.commit.raw_id,
1186 f_path=f_path))
1184 f_path=f_path))
1187
1185
1188 c.default_message = _(
1186 c.default_message = _(
1189 'Edited file {} via RhodeCode Enterprise').format(f_path)
1187 'Edited file {} via RhodeCode Enterprise').format(f_path)
1190 c.f_path = f_path
1188 c.f_path = f_path
1191 old_content = c.file.content
1189 old_content = c.file.content
1192 sl = old_content.splitlines(1)
1190 sl = old_content.splitlines(1)
1193 first_line = sl[0] if sl else ''
1191 first_line = sl[0] if sl else ''
1194
1192
1195 r_post = self.request.POST
1193 r_post = self.request.POST
1196 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1194 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1197 line_ending_mode = detect_mode(first_line, 0)
1195 line_ending_mode = detect_mode(first_line, 0)
1198 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1196 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1199
1197
1200 message = r_post.get('message') or c.default_message
1198 message = r_post.get('message') or c.default_message
1201 org_f_path = c.file.unicode_path
1199 org_f_path = c.file.unicode_path
1202 filename = r_post['filename']
1200 filename = r_post['filename']
1203 org_filename = c.file.name
1201 org_filename = c.file.name
1204
1202
1205 if content == old_content and filename == org_filename:
1203 if content == old_content and filename == org_filename:
1206 h.flash(_('No changes'), category='warning')
1204 h.flash(_('No changes'), category='warning')
1207 raise HTTPFound(
1205 raise HTTPFound(
1208 h.route_path('repo_commit', repo_name=self.db_repo_name,
1206 h.route_path('repo_commit', repo_name=self.db_repo_name,
1209 commit_id='tip'))
1207 commit_id='tip'))
1210 try:
1208 try:
1211 mapping = {
1209 mapping = {
1212 org_f_path: {
1210 org_f_path: {
1213 'org_filename': org_f_path,
1211 'org_filename': org_f_path,
1214 'filename': os.path.join(c.file.dir_path, filename),
1212 'filename': os.path.join(c.file.dir_path, filename),
1215 'content': content,
1213 'content': content,
1216 'lexer': '',
1214 'lexer': '',
1217 'op': 'mod',
1215 'op': 'mod',
1218 'mode': c.file.mode
1216 'mode': c.file.mode
1219 }
1217 }
1220 }
1218 }
1221
1219
1222 ScmModel().update_nodes(
1220 ScmModel().update_nodes(
1223 user=self._rhodecode_db_user.user_id,
1221 user=self._rhodecode_db_user.user_id,
1224 repo=self.db_repo,
1222 repo=self.db_repo,
1225 message=message,
1223 message=message,
1226 nodes=mapping,
1224 nodes=mapping,
1227 parent_commit=c.commit,
1225 parent_commit=c.commit,
1228 )
1226 )
1229
1227
1230 h.flash(
1228 h.flash(
1231 _('Successfully committed changes to file `{}`').format(
1229 _('Successfully committed changes to file `{}`').format(
1232 h.escape(f_path)), category='success')
1230 h.escape(f_path)), category='success')
1233 except Exception:
1231 except Exception:
1234 log.exception('Error occurred during commit')
1232 log.exception('Error occurred during commit')
1235 h.flash(_('Error occurred during commit'), category='error')
1233 h.flash(_('Error occurred during commit'), category='error')
1236 raise HTTPFound(
1234 raise HTTPFound(
1237 h.route_path('repo_commit', repo_name=self.db_repo_name,
1235 h.route_path('repo_commit', repo_name=self.db_repo_name,
1238 commit_id='tip'))
1236 commit_id='tip'))
1239
1237
1240 @LoginRequired()
1238 @LoginRequired()
1241 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1239 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1242 @view_config(
1240 @view_config(
1243 route_name='repo_files_add_file', request_method='GET',
1241 route_name='repo_files_add_file', request_method='GET',
1244 renderer='rhodecode:templates/files/files_add.mako')
1242 renderer='rhodecode:templates/files/files_add.mako')
1245 def repo_files_add_file(self):
1243 def repo_files_add_file(self):
1246 _ = self.request.translate
1244 _ = self.request.translate
1247 c = self.load_default_context()
1245 c = self.load_default_context()
1248 commit_id, f_path = self._get_commit_and_path()
1246 commit_id, f_path = self._get_commit_and_path()
1249
1247
1250 self._ensure_not_locked()
1248 self._ensure_not_locked()
1251
1249
1252 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1250 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1253 if c.commit is None:
1251 if c.commit is None:
1254 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1252 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1255 c.default_message = (_('Added file via RhodeCode Enterprise'))
1253 c.default_message = (_('Added file via RhodeCode Enterprise'))
1256 c.f_path = f_path.lstrip('/') # ensure not relative path
1254 c.f_path = f_path.lstrip('/') # ensure not relative path
1257
1255
1258 if self.rhodecode_vcs_repo.is_empty:
1256 if self.rhodecode_vcs_repo.is_empty:
1259 # for empty repository we cannot check for current branch, we rely on
1257 # for empty repository we cannot check for current branch, we rely on
1260 # c.commit.branch instead
1258 # c.commit.branch instead
1261 _branch_name = c.commit.branch
1259 _branch_name = c.commit.branch
1262 is_head = True
1260 is_head = True
1263 else:
1261 else:
1264 _branch_name, _sha_commit_id, is_head = \
1262 _branch_name, _sha_commit_id, is_head = \
1265 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1263 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1266
1264
1267 if not is_head:
1265 if not is_head:
1268 h.flash(_('You can only add files with commit '
1266 h.flash(_('You can only add files with commit '
1269 'being a valid branch head.'), category='warning')
1267 'being a valid branch head.'), category='warning')
1270 raise HTTPFound(
1268 raise HTTPFound(
1271 h.route_path('repo_files',
1269 h.route_path('repo_files',
1272 repo_name=self.db_repo_name, commit_id='tip',
1270 repo_name=self.db_repo_name, commit_id='tip',
1273 f_path=f_path))
1271 f_path=f_path))
1274
1272
1275 self.check_branch_permission(_branch_name)
1273 self.check_branch_permission(_branch_name)
1276
1274
1277 return self._get_template_context(c)
1275 return self._get_template_context(c)
1278
1276
1279 @LoginRequired()
1277 @LoginRequired()
1280 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1278 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1281 @CSRFRequired()
1279 @CSRFRequired()
1282 @view_config(
1280 @view_config(
1283 route_name='repo_files_create_file', request_method='POST',
1281 route_name='repo_files_create_file', request_method='POST',
1284 renderer=None)
1282 renderer=None)
1285 def repo_files_create_file(self):
1283 def repo_files_create_file(self):
1286 _ = self.request.translate
1284 _ = self.request.translate
1287 c = self.load_default_context()
1285 c = self.load_default_context()
1288 commit_id, f_path = self._get_commit_and_path()
1286 commit_id, f_path = self._get_commit_and_path()
1289
1287
1290 self._ensure_not_locked()
1288 self._ensure_not_locked()
1291
1289
1292 r_post = self.request.POST
1290 r_post = self.request.POST
1293
1291
1294 c.commit = self._get_commit_or_redirect(
1292 c.commit = self._get_commit_or_redirect(
1295 commit_id, redirect_after=False)
1293 commit_id, redirect_after=False)
1296 if c.commit is None:
1294 if c.commit is None:
1297 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1295 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1298
1296
1299 if self.rhodecode_vcs_repo.is_empty:
1297 if self.rhodecode_vcs_repo.is_empty:
1300 # for empty repository we cannot check for current branch, we rely on
1298 # for empty repository we cannot check for current branch, we rely on
1301 # c.commit.branch instead
1299 # c.commit.branch instead
1302 _branch_name = c.commit.branch
1300 _branch_name = c.commit.branch
1303 is_head = True
1301 is_head = True
1304 else:
1302 else:
1305 _branch_name, _sha_commit_id, is_head = \
1303 _branch_name, _sha_commit_id, is_head = \
1306 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1304 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1307
1305
1308 if not is_head:
1306 if not is_head:
1309 h.flash(_('You can only add files with commit '
1307 h.flash(_('You can only add files with commit '
1310 'being a valid branch head.'), category='warning')
1308 'being a valid branch head.'), category='warning')
1311 raise HTTPFound(
1309 raise HTTPFound(
1312 h.route_path('repo_files',
1310 h.route_path('repo_files',
1313 repo_name=self.db_repo_name, commit_id='tip',
1311 repo_name=self.db_repo_name, commit_id='tip',
1314 f_path=f_path))
1312 f_path=f_path))
1315
1313
1316 self.check_branch_permission(_branch_name)
1314 self.check_branch_permission(_branch_name)
1317
1315
1318 c.default_message = (_('Added file via RhodeCode Enterprise'))
1316 c.default_message = (_('Added file via RhodeCode Enterprise'))
1319 c.f_path = f_path
1317 c.f_path = f_path
1320 unix_mode = 0
1318 unix_mode = 0
1321 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1319 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1322
1320
1323 message = r_post.get('message') or c.default_message
1321 message = r_post.get('message') or c.default_message
1324 filename = r_post.get('filename')
1322 filename = r_post.get('filename')
1325 location = r_post.get('location', '') # dir location
1323 location = r_post.get('location', '') # dir location
1326 file_obj = r_post.get('upload_file', None)
1324 file_obj = r_post.get('upload_file', None)
1327
1325
1328 if file_obj is not None and hasattr(file_obj, 'filename'):
1326 if file_obj is not None and hasattr(file_obj, 'filename'):
1329 filename = r_post.get('filename_upload')
1327 filename = r_post.get('filename_upload')
1330 content = file_obj.file
1328 content = file_obj.file
1331
1329
1332 if hasattr(content, 'file'):
1330 if hasattr(content, 'file'):
1333 # non posix systems store real file under file attr
1331 # non posix systems store real file under file attr
1334 content = content.file
1332 content = content.file
1335
1333
1336 if self.rhodecode_vcs_repo.is_empty:
1334 if self.rhodecode_vcs_repo.is_empty:
1337 default_redirect_url = h.route_path(
1335 default_redirect_url = h.route_path(
1338 'repo_summary', repo_name=self.db_repo_name)
1336 'repo_summary', repo_name=self.db_repo_name)
1339 else:
1337 else:
1340 default_redirect_url = h.route_path(
1338 default_redirect_url = h.route_path(
1341 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1339 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1342
1340
1343 # If there's no commit, redirect to repo summary
1341 # If there's no commit, redirect to repo summary
1344 if type(c.commit) is EmptyCommit:
1342 if type(c.commit) is EmptyCommit:
1345 redirect_url = h.route_path(
1343 redirect_url = h.route_path(
1346 'repo_summary', repo_name=self.db_repo_name)
1344 'repo_summary', repo_name=self.db_repo_name)
1347 else:
1345 else:
1348 redirect_url = default_redirect_url
1346 redirect_url = default_redirect_url
1349
1347
1350 if not filename:
1348 if not filename:
1351 h.flash(_('No filename'), category='warning')
1349 h.flash(_('No filename'), category='warning')
1352 raise HTTPFound(redirect_url)
1350 raise HTTPFound(redirect_url)
1353
1351
1354 # extract the location from filename,
1352 # extract the location from filename,
1355 # allows using foo/bar.txt syntax to create subdirectories
1353 # allows using foo/bar.txt syntax to create subdirectories
1356 subdir_loc = filename.rsplit('/', 1)
1354 subdir_loc = filename.rsplit('/', 1)
1357 if len(subdir_loc) == 2:
1355 if len(subdir_loc) == 2:
1358 location = os.path.join(location, subdir_loc[0])
1356 location = os.path.join(location, subdir_loc[0])
1359
1357
1360 # strip all crap out of file, just leave the basename
1358 # strip all crap out of file, just leave the basename
1361 filename = os.path.basename(filename)
1359 filename = os.path.basename(filename)
1362 node_path = os.path.join(location, filename)
1360 node_path = os.path.join(location, filename)
1363 author = self._rhodecode_db_user.full_contact
1361 author = self._rhodecode_db_user.full_contact
1364
1362
1365 try:
1363 try:
1366 nodes = {
1364 nodes = {
1367 node_path: {
1365 node_path: {
1368 'content': content
1366 'content': content
1369 }
1367 }
1370 }
1368 }
1371 ScmModel().create_nodes(
1369 ScmModel().create_nodes(
1372 user=self._rhodecode_db_user.user_id,
1370 user=self._rhodecode_db_user.user_id,
1373 repo=self.db_repo,
1371 repo=self.db_repo,
1374 message=message,
1372 message=message,
1375 nodes=nodes,
1373 nodes=nodes,
1376 parent_commit=c.commit,
1374 parent_commit=c.commit,
1377 author=author,
1375 author=author,
1378 )
1376 )
1379
1377
1380 h.flash(
1378 h.flash(
1381 _('Successfully committed new file `{}`').format(
1379 _('Successfully committed new file `{}`').format(
1382 h.escape(node_path)), category='success')
1380 h.escape(node_path)), category='success')
1383 except NonRelativePathError:
1381 except NonRelativePathError:
1384 log.exception('Non Relative path found')
1382 log.exception('Non Relative path found')
1385 h.flash(_(
1383 h.flash(_(
1386 'The location specified must be a relative path and must not '
1384 'The location specified must be a relative path and must not '
1387 'contain .. in the path'), category='warning')
1385 'contain .. in the path'), category='warning')
1388 raise HTTPFound(default_redirect_url)
1386 raise HTTPFound(default_redirect_url)
1389 except (NodeError, NodeAlreadyExistsError) as e:
1387 except (NodeError, NodeAlreadyExistsError) as e:
1390 h.flash(_(h.escape(e)), category='error')
1388 h.flash(_(h.escape(e)), category='error')
1391 except Exception:
1389 except Exception:
1392 log.exception('Error occurred during commit')
1390 log.exception('Error occurred during commit')
1393 h.flash(_('Error occurred during commit'), category='error')
1391 h.flash(_('Error occurred during commit'), category='error')
1394
1392
1395 raise HTTPFound(default_redirect_url)
1393 raise HTTPFound(default_redirect_url)
@@ -1,393 +1,393 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import string
22 import string
23 import rhodecode
23 import rhodecode
24
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.lib.view_utils import get_format_ref_id
27 from rhodecode.lib.view_utils import get_format_ref_id
28 from rhodecode.apps._base import RepoAppView
28 from rhodecode.apps._base import RepoAppView
29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
30 from rhodecode.lib import helpers as h, rc_cache
30 from rhodecode.lib import helpers as h, rc_cache
31 from rhodecode.lib.utils2 import safe_str, safe_int
31 from rhodecode.lib.utils2 import safe_str, safe_int
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
33 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.vcs.backends.base import EmptyCommit
35 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.exceptions import (
36 from rhodecode.lib.vcs.exceptions import (
37 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
37 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
38 from rhodecode.model.db import Statistics, CacheKey, User
38 from rhodecode.model.db import Statistics, CacheKey, User
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 from rhodecode.model.repo import ReadmeFinder
40 from rhodecode.model.repo import ReadmeFinder
41 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.scm import ScmModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class RepoSummaryView(RepoAppView):
46 class RepoSummaryView(RepoAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context(include_app_defaults=True)
49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 c.rhodecode_repo = None
50 c.rhodecode_repo = None
51 if not c.repository_requirements_missing:
51 if not c.repository_requirements_missing:
52 c.rhodecode_repo = self.rhodecode_vcs_repo
52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 return c
53 return c
54
54
55 def _get_readme_data(self, db_repo, renderer_type):
55 def _get_readme_data(self, db_repo, renderer_type):
56
56
57 log.debug('Looking for README file')
57 log.debug('Looking for README file')
58
58
59 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
59 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
60 db_repo.repo_id, CacheKey.CACHE_TYPE_README)
60 db_repo.repo_id, CacheKey.CACHE_TYPE_README)
61 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
61 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
62 repo_id=self.db_repo.repo_id)
62 repo_id=self.db_repo.repo_id)
63 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
63 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
64
64
65 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
65 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
66 def generate_repo_readme(repo_id, _repo_name, _renderer_type):
66 def generate_repo_readme(repo_id, _repo_name, _renderer_type):
67 readme_data = None
67 readme_data = None
68 readme_node = None
68 readme_node = None
69 readme_filename = None
69 readme_filename = None
70 commit = self._get_landing_commit_or_none(db_repo)
70 commit = self._get_landing_commit_or_none(db_repo)
71 if commit:
71 if commit:
72 log.debug("Searching for a README file.")
72 log.debug("Searching for a README file.")
73 readme_node = ReadmeFinder(_renderer_type).search(commit)
73 readme_node = ReadmeFinder(_renderer_type).search(commit)
74 if readme_node:
74 if readme_node:
75 relative_urls = {
75 relative_urls = {
76 'raw': h.route_path(
76 'raw': h.route_path(
77 'repo_file_raw', repo_name=_repo_name,
77 'repo_file_raw', repo_name=_repo_name,
78 commit_id=commit.raw_id, f_path=readme_node.path),
78 commit_id=commit.raw_id, f_path=readme_node.path),
79 'standard': h.route_path(
79 'standard': h.route_path(
80 'repo_files', repo_name=_repo_name,
80 'repo_files', repo_name=_repo_name,
81 commit_id=commit.raw_id, f_path=readme_node.path),
81 commit_id=commit.raw_id, f_path=readme_node.path),
82 }
82 }
83 readme_data = self._render_readme_or_none(
83 readme_data = self._render_readme_or_none(
84 commit, readme_node, relative_urls)
84 commit, readme_node, relative_urls)
85 readme_filename = readme_node.path
85 readme_filename = readme_node.path
86 return readme_data, readme_filename
86 return readme_data, readme_filename
87
87
88 inv_context_manager = rc_cache.InvalidationContext(
88 inv_context_manager = rc_cache.InvalidationContext(
89 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
89 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
90 with inv_context_manager as invalidation_context:
90 with inv_context_manager as invalidation_context:
91 args = (db_repo.repo_id, db_repo.repo_name, renderer_type,)
91 args = (db_repo.repo_id, db_repo.repo_name, renderer_type,)
92 # re-compute and store cache if we get invalidate signal
92 # re-compute and store cache if we get invalidate signal
93 if invalidation_context.should_invalidate():
93 if invalidation_context.should_invalidate():
94 instance = generate_repo_readme.refresh(*args)
94 instance = generate_repo_readme.refresh(*args)
95 else:
95 else:
96 instance = generate_repo_readme(*args)
96 instance = generate_repo_readme(*args)
97
97
98 log.debug(
98 log.debug(
99 'Repo readme generated and computed in %.3fs',
99 'Repo readme generated and computed in %.3fs',
100 inv_context_manager.compute_time)
100 inv_context_manager.compute_time)
101 return instance
101 return instance
102
102
103 def _get_landing_commit_or_none(self, db_repo):
103 def _get_landing_commit_or_none(self, db_repo):
104 log.debug("Getting the landing commit.")
104 log.debug("Getting the landing commit.")
105 try:
105 try:
106 commit = db_repo.get_landing_commit()
106 commit = db_repo.get_landing_commit()
107 if not isinstance(commit, EmptyCommit):
107 if not isinstance(commit, EmptyCommit):
108 return commit
108 return commit
109 else:
109 else:
110 log.debug("Repository is empty, no README to render.")
110 log.debug("Repository is empty, no README to render.")
111 except CommitError:
111 except CommitError:
112 log.exception(
112 log.exception(
113 "Problem getting commit when trying to render the README.")
113 "Problem getting commit when trying to render the README.")
114
114
115 def _render_readme_or_none(self, commit, readme_node, relative_urls):
115 def _render_readme_or_none(self, commit, readme_node, relative_urls):
116 log.debug(
116 log.debug(
117 'Found README file `%s` rendering...', readme_node.path)
117 'Found README file `%s` rendering...', readme_node.path)
118 renderer = MarkupRenderer()
118 renderer = MarkupRenderer()
119 try:
119 try:
120 html_source = renderer.render(
120 html_source = renderer.render(
121 readme_node.content, filename=readme_node.path)
121 readme_node.content, filename=readme_node.path)
122 if relative_urls:
122 if relative_urls:
123 return relative_links(html_source, relative_urls)
123 return relative_links(html_source, relative_urls)
124 return html_source
124 return html_source
125 except Exception:
125 except Exception:
126 log.exception(
126 log.exception(
127 "Exception while trying to render the README")
127 "Exception while trying to render the README")
128
128
129 def _load_commits_context(self, c):
129 def _load_commits_context(self, c):
130 p = safe_int(self.request.GET.get('page'), 1)
130 p = safe_int(self.request.GET.get('page'), 1)
131 size = safe_int(self.request.GET.get('size'), 10)
131 size = safe_int(self.request.GET.get('size'), 10)
132
132
133 def url_generator(**kw):
133 def url_generator(**kw):
134 query_params = {
134 query_params = {
135 'size': size
135 'size': size
136 }
136 }
137 query_params.update(kw)
137 query_params.update(kw)
138 return h.route_path(
138 return h.route_path(
139 'repo_summary_commits',
139 'repo_summary_commits',
140 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
140 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
141
141
142 pre_load = ['author', 'branch', 'date', 'message']
142 pre_load = ['author', 'branch', 'date', 'message']
143 try:
143 try:
144 collection = self.rhodecode_vcs_repo.get_commits(
144 collection = self.rhodecode_vcs_repo.get_commits(
145 pre_load=pre_load, translate_tags=False)
145 pre_load=pre_load, translate_tags=False)
146 except EmptyRepositoryError:
146 except EmptyRepositoryError:
147 collection = self.rhodecode_vcs_repo
147 collection = self.rhodecode_vcs_repo
148
148
149 c.repo_commits = h.RepoPage(
149 c.repo_commits = h.RepoPage(
150 collection, page=p, items_per_page=size, url=url_generator)
150 collection, page=p, items_per_page=size, url=url_generator)
151 page_ids = [x.raw_id for x in c.repo_commits]
151 page_ids = [x.raw_id for x in c.repo_commits]
152 c.comments = self.db_repo.get_comments(page_ids)
152 c.comments = self.db_repo.get_comments(page_ids)
153 c.statuses = self.db_repo.statuses(page_ids)
153 c.statuses = self.db_repo.statuses(page_ids)
154
154
155 def _prepare_and_set_clone_url(self, c):
155 def _prepare_and_set_clone_url(self, c):
156 username = ''
156 username = ''
157 if self._rhodecode_user.username != User.DEFAULT_USER:
157 if self._rhodecode_user.username != User.DEFAULT_USER:
158 username = safe_str(self._rhodecode_user.username)
158 username = safe_str(self._rhodecode_user.username)
159
159
160 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
160 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
161 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
161 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
162
162
163 if '{repo}' in _def_clone_uri:
163 if '{repo}' in _def_clone_uri:
164 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
164 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
165 elif '{repoid}' in _def_clone_uri:
165 elif '{repoid}' in _def_clone_uri:
166 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
166 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
167
167
168 c.clone_repo_url = self.db_repo.clone_url(
168 c.clone_repo_url = self.db_repo.clone_url(
169 user=username, uri_tmpl=_def_clone_uri)
169 user=username, uri_tmpl=_def_clone_uri)
170 c.clone_repo_url_id = self.db_repo.clone_url(
170 c.clone_repo_url_id = self.db_repo.clone_url(
171 user=username, uri_tmpl=_def_clone_uri_id)
171 user=username, uri_tmpl=_def_clone_uri_id)
172 c.clone_repo_url_ssh = self.db_repo.clone_url(
172 c.clone_repo_url_ssh = self.db_repo.clone_url(
173 uri_tmpl=_def_clone_uri_ssh, ssh=True)
173 uri_tmpl=_def_clone_uri_ssh, ssh=True)
174
174
175 @LoginRequired()
175 @LoginRequired()
176 @HasRepoPermissionAnyDecorator(
176 @HasRepoPermissionAnyDecorator(
177 'repository.read', 'repository.write', 'repository.admin')
177 'repository.read', 'repository.write', 'repository.admin')
178 @view_config(
178 @view_config(
179 route_name='repo_summary_commits', request_method='GET',
179 route_name='repo_summary_commits', request_method='GET',
180 renderer='rhodecode:templates/summary/summary_commits.mako')
180 renderer='rhodecode:templates/summary/summary_commits.mako')
181 def summary_commits(self):
181 def summary_commits(self):
182 c = self.load_default_context()
182 c = self.load_default_context()
183 self._prepare_and_set_clone_url(c)
183 self._prepare_and_set_clone_url(c)
184 self._load_commits_context(c)
184 self._load_commits_context(c)
185 return self._get_template_context(c)
185 return self._get_template_context(c)
186
186
187 @LoginRequired()
187 @LoginRequired()
188 @HasRepoPermissionAnyDecorator(
188 @HasRepoPermissionAnyDecorator(
189 'repository.read', 'repository.write', 'repository.admin')
189 'repository.read', 'repository.write', 'repository.admin')
190 @view_config(
190 @view_config(
191 route_name='repo_summary', request_method='GET',
191 route_name='repo_summary', request_method='GET',
192 renderer='rhodecode:templates/summary/summary.mako')
192 renderer='rhodecode:templates/summary/summary.mako')
193 @view_config(
193 @view_config(
194 route_name='repo_summary_slash', request_method='GET',
194 route_name='repo_summary_slash', request_method='GET',
195 renderer='rhodecode:templates/summary/summary.mako')
195 renderer='rhodecode:templates/summary/summary.mako')
196 @view_config(
196 @view_config(
197 route_name='repo_summary_explicit', request_method='GET',
197 route_name='repo_summary_explicit', request_method='GET',
198 renderer='rhodecode:templates/summary/summary.mako')
198 renderer='rhodecode:templates/summary/summary.mako')
199 def summary(self):
199 def summary(self):
200 c = self.load_default_context()
200 c = self.load_default_context()
201
201
202 # Prepare the clone URL
202 # Prepare the clone URL
203 self._prepare_and_set_clone_url(c)
203 self._prepare_and_set_clone_url(c)
204
204
205 # If enabled, get statistics data
205 # If enabled, get statistics data
206
206
207 c.show_stats = bool(self.db_repo.enable_statistics)
207 c.show_stats = bool(self.db_repo.enable_statistics)
208
208
209 stats = Session().query(Statistics) \
209 stats = Session().query(Statistics) \
210 .filter(Statistics.repository == self.db_repo) \
210 .filter(Statistics.repository == self.db_repo) \
211 .scalar()
211 .scalar()
212
212
213 c.stats_percentage = 0
213 c.stats_percentage = 0
214
214
215 if stats and stats.languages:
215 if stats and stats.languages:
216 c.no_data = False is self.db_repo.enable_statistics
216 c.no_data = False is self.db_repo.enable_statistics
217 lang_stats_d = json.loads(stats.languages)
217 lang_stats_d = json.loads(stats.languages)
218
218
219 # Sort first by decreasing count and second by the file extension,
219 # Sort first by decreasing count and second by the file extension,
220 # so we have a consistent output.
220 # so we have a consistent output.
221 lang_stats_items = sorted(lang_stats_d.iteritems(),
221 lang_stats_items = sorted(lang_stats_d.iteritems(),
222 key=lambda k: (-k[1], k[0]))[:10]
222 key=lambda k: (-k[1], k[0]))[:10]
223 lang_stats = [(x, {"count": y,
223 lang_stats = [(x, {"count": y,
224 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
224 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
225 for x, y in lang_stats_items]
225 for x, y in lang_stats_items]
226
226
227 c.trending_languages = json.dumps(lang_stats)
227 c.trending_languages = json.dumps(lang_stats)
228 else:
228 else:
229 c.no_data = True
229 c.no_data = True
230 c.trending_languages = json.dumps({})
230 c.trending_languages = json.dumps({})
231
231
232 scm_model = ScmModel()
232 scm_model = ScmModel()
233 c.enable_downloads = self.db_repo.enable_downloads
233 c.enable_downloads = self.db_repo.enable_downloads
234 c.repository_followers = scm_model.get_followers(self.db_repo)
234 c.repository_followers = scm_model.get_followers(self.db_repo)
235 c.repository_forks = scm_model.get_forks(self.db_repo)
235 c.repository_forks = scm_model.get_forks(self.db_repo)
236 c.repository_is_user_following = scm_model.is_following_repo(
236 c.repository_is_user_following = scm_model.is_following_repo(
237 self.db_repo_name, self._rhodecode_user.user_id)
237 self.db_repo_name, self._rhodecode_user.user_id)
238
238
239 # first interaction with the VCS instance after here...
239 # first interaction with the VCS instance after here...
240 if c.repository_requirements_missing:
240 if c.repository_requirements_missing:
241 self.request.override_renderer = \
241 self.request.override_renderer = \
242 'rhodecode:templates/summary/missing_requirements.mako'
242 'rhodecode:templates/summary/missing_requirements.mako'
243 return self._get_template_context(c)
243 return self._get_template_context(c)
244
244
245 c.readme_data, c.readme_file = \
245 c.readme_data, c.readme_file = \
246 self._get_readme_data(self.db_repo, c.visual.default_renderer)
246 self._get_readme_data(self.db_repo, c.visual.default_renderer)
247
247
248 # loads the summary commits template context
248 # loads the summary commits template context
249 self._load_commits_context(c)
249 self._load_commits_context(c)
250
250
251 return self._get_template_context(c)
251 return self._get_template_context(c)
252
252
253 def get_request_commit_id(self):
253 def get_request_commit_id(self):
254 return self.request.matchdict['commit_id']
254 return self.request.matchdict['commit_id']
255
255
256 @LoginRequired()
256 @LoginRequired()
257 @HasRepoPermissionAnyDecorator(
257 @HasRepoPermissionAnyDecorator(
258 'repository.read', 'repository.write', 'repository.admin')
258 'repository.read', 'repository.write', 'repository.admin')
259 @view_config(
259 @view_config(
260 route_name='repo_stats', request_method='GET',
260 route_name='repo_stats', request_method='GET',
261 renderer='json_ext')
261 renderer='json_ext')
262 def repo_stats(self):
262 def repo_stats(self):
263 commit_id = self.get_request_commit_id()
263 commit_id = self.get_request_commit_id()
264 show_stats = bool(self.db_repo.enable_statistics)
264 show_stats = bool(self.db_repo.enable_statistics)
265 repo_id = self.db_repo.repo_id
265 repo_id = self.db_repo.repo_id
266
266
267 cache_seconds = safe_int(
267 cache_seconds = safe_int(
268 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
268 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
269 cache_on = cache_seconds > 0
269 cache_on = cache_seconds > 0
270 log.debug(
270 log.debug(
271 'Computing REPO TREE for repo_id %s commit_id `%s` '
271 'Computing REPO TREE for repo_id %s commit_id `%s` '
272 'with caching: %s[TTL: %ss]' % (
272 'with caching: %s[TTL: %ss]' % (
273 repo_id, commit_id, cache_on, cache_seconds or 0))
273 repo_id, commit_id, cache_on, cache_seconds or 0))
274
274
275 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
275 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
276 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
276 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
277
277
278 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
278 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
279 condition=cache_on)
279 condition=cache_on)
280 def compute_stats(repo_id, commit_id, show_stats):
280 def compute_stats(repo_id, commit_id, show_stats):
281 code_stats = {}
281 code_stats = {}
282 size = 0
282 size = 0
283 try:
283 try:
284 scm_instance = self.db_repo.scm_instance()
284 scm_instance = self.db_repo.scm_instance()
285 commit = scm_instance.get_commit(commit_id)
285 commit = scm_instance.get_commit(commit_id)
286
286
287 for node in commit.get_filenodes_generator():
287 for node in commit.get_filenodes_generator():
288 size += node.size
288 size += node.size
289 if not show_stats:
289 if not show_stats:
290 continue
290 continue
291 ext = string.lower(node.extension)
291 ext = string.lower(node.extension)
292 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
292 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
293 if ext_info:
293 if ext_info:
294 if ext in code_stats:
294 if ext in code_stats:
295 code_stats[ext]['count'] += 1
295 code_stats[ext]['count'] += 1
296 else:
296 else:
297 code_stats[ext] = {"count": 1, "desc": ext_info}
297 code_stats[ext] = {"count": 1, "desc": ext_info}
298 except (EmptyRepositoryError, CommitDoesNotExistError):
298 except (EmptyRepositoryError, CommitDoesNotExistError):
299 pass
299 pass
300 return {'size': h.format_byte_size_binary(size),
300 return {'size': h.format_byte_size_binary(size),
301 'code_stats': code_stats}
301 'code_stats': code_stats}
302
302
303 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
303 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
304 return stats
304 return stats
305
305
306 @LoginRequired()
306 @LoginRequired()
307 @HasRepoPermissionAnyDecorator(
307 @HasRepoPermissionAnyDecorator(
308 'repository.read', 'repository.write', 'repository.admin')
308 'repository.read', 'repository.write', 'repository.admin')
309 @view_config(
309 @view_config(
310 route_name='repo_refs_data', request_method='GET',
310 route_name='repo_refs_data', request_method='GET',
311 renderer='json_ext')
311 renderer='json_ext')
312 def repo_refs_data(self):
312 def repo_refs_data(self):
313 _ = self.request.translate
313 _ = self.request.translate
314 self.load_default_context()
314 self.load_default_context()
315
315
316 repo = self.rhodecode_vcs_repo
316 repo = self.rhodecode_vcs_repo
317 refs_to_create = [
317 refs_to_create = [
318 (_("Branch"), repo.branches, 'branch'),
318 (_("Branch"), repo.branches, 'branch'),
319 (_("Tag"), repo.tags, 'tag'),
319 (_("Tag"), repo.tags, 'tag'),
320 (_("Bookmark"), repo.bookmarks, 'book'),
320 (_("Bookmark"), repo.bookmarks, 'book'),
321 ]
321 ]
322 res = self._create_reference_data(
322 res = self._create_reference_data(
323 repo, self.db_repo_name, refs_to_create)
323 repo, self.db_repo_name, refs_to_create)
324 data = {
324 data = {
325 'more': False,
325 'more': False,
326 'results': res
326 'results': res
327 }
327 }
328 return data
328 return data
329
329
330 @LoginRequired()
330 @LoginRequired()
331 @HasRepoPermissionAnyDecorator(
331 @HasRepoPermissionAnyDecorator(
332 'repository.read', 'repository.write', 'repository.admin')
332 'repository.read', 'repository.write', 'repository.admin')
333 @view_config(
333 @view_config(
334 route_name='repo_refs_changelog_data', request_method='GET',
334 route_name='repo_refs_changelog_data', request_method='GET',
335 renderer='json_ext')
335 renderer='json_ext')
336 def repo_refs_changelog_data(self):
336 def repo_refs_changelog_data(self):
337 _ = self.request.translate
337 _ = self.request.translate
338 self.load_default_context()
338 self.load_default_context()
339
339
340 repo = self.rhodecode_vcs_repo
340 repo = self.rhodecode_vcs_repo
341
341
342 refs_to_create = [
342 refs_to_create = [
343 (_("Branches"), repo.branches, 'branch'),
343 (_("Branches"), repo.branches, 'branch'),
344 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
344 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
345 # TODO: enable when vcs can handle bookmarks filters
345 # TODO: enable when vcs can handle bookmarks filters
346 # (_("Bookmarks"), repo.bookmarks, "book"),
346 # (_("Bookmarks"), repo.bookmarks, "book"),
347 ]
347 ]
348 res = self._create_reference_data(
348 res = self._create_reference_data(
349 repo, self.db_repo_name, refs_to_create)
349 repo, self.db_repo_name, refs_to_create)
350 data = {
350 data = {
351 'more': False,
351 'more': False,
352 'results': res
352 'results': res
353 }
353 }
354 return data
354 return data
355
355
356 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
356 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
357 format_ref_id = get_format_ref_id(repo)
357 format_ref_id = get_format_ref_id(repo)
358
358
359 result = []
359 result = []
360 for title, refs, ref_type in refs_to_create:
360 for title, refs, ref_type in refs_to_create:
361 if refs:
361 if refs:
362 result.append({
362 result.append({
363 'text': title,
363 'text': title,
364 'children': self._create_reference_items(
364 'children': self._create_reference_items(
365 repo, full_repo_name, refs, ref_type,
365 repo, full_repo_name, refs, ref_type,
366 format_ref_id),
366 format_ref_id),
367 })
367 })
368 return result
368 return result
369
369
370 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
370 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
371 format_ref_id):
372 result = []
371 result = []
373 is_svn = h.is_svn(repo)
372 is_svn = h.is_svn(repo)
374 for ref_name, raw_id in refs.iteritems():
373 for ref_name, raw_id in refs.iteritems():
375 files_url = self._create_files_url(
374 files_url = self._create_files_url(
376 repo, full_repo_name, ref_name, raw_id, is_svn)
375 repo, full_repo_name, ref_name, raw_id, is_svn)
377 result.append({
376 result.append({
378 'text': ref_name,
377 'text': ref_name,
379 'id': format_ref_id(ref_name, raw_id),
378 'id': format_ref_id(ref_name, raw_id),
380 'raw_id': raw_id,
379 'raw_id': raw_id,
381 'type': ref_type,
380 'type': ref_type,
382 'files_url': files_url,
381 'files_url': files_url,
382 'idx': 0,
383 })
383 })
384 return result
384 return result
385
385
386 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
386 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
387 use_commit_id = '/' in ref_name or is_svn
387 use_commit_id = '/' in ref_name or is_svn
388 return h.route_path(
388 return h.route_path(
389 'repo_files',
389 'repo_files',
390 repo_name=full_repo_name,
390 repo_name=full_repo_name,
391 f_path=ref_name if is_svn else '',
391 f_path=ref_name if is_svn else '',
392 commit_id=raw_id if use_commit_id else ref_name,
392 commit_id=raw_id if use_commit_id else ref_name,
393 _query=dict(at=ref_name))
393 _query=dict(at=ref_name))
@@ -1,2042 +1,2045 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Helper functions
22 Helper functions
23
23
24 Consists of functions to typically be used within templates, but also
24 Consists of functions to typically be used within templates, but also
25 available to Controllers. This module is available to both as 'h'.
25 available to Controllers. This module is available to both as 'h'.
26 """
26 """
27
27
28 import os
28 import os
29 import random
29 import random
30 import hashlib
30 import hashlib
31 import StringIO
31 import StringIO
32 import textwrap
32 import textwrap
33 import urllib
33 import urllib
34 import math
34 import math
35 import logging
35 import logging
36 import re
36 import re
37 import time
37 import time
38 import string
38 import string
39 import hashlib
39 import hashlib
40 from collections import OrderedDict
40 from collections import OrderedDict
41
41
42 import pygments
42 import pygments
43 import itertools
43 import itertools
44 import fnmatch
44 import fnmatch
45 import bleach
45 import bleach
46
46
47 from pyramid import compat
47 from pyramid import compat
48 from datetime import datetime
48 from datetime import datetime
49 from functools import partial
49 from functools import partial
50 from pygments.formatters.html import HtmlFormatter
50 from pygments.formatters.html import HtmlFormatter
51 from pygments.lexers import (
51 from pygments.lexers import (
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
53
53
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55
55
56 from webhelpers.html import literal, HTML, escape
56 from webhelpers.html import literal, HTML, escape
57 from webhelpers.html.tools import *
57 from webhelpers.html.tools import *
58 from webhelpers.html.builder import make_tag
58 from webhelpers.html.builder import make_tag
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
62 submit, text, password, textarea, title, ul, xml_declaration, radio
62 submit, text, password, textarea, title, ul, xml_declaration, radio
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
67 replace_whitespace, urlify, truncate, wrap_paragraphs
67 replace_whitespace, urlify, truncate, wrap_paragraphs
68 from webhelpers.date import time_ago_in_words
68 from webhelpers.date import time_ago_in_words
69 from webhelpers.paginate import Page as _Page
69 from webhelpers.paginate import Page as _Page
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
72 from webhelpers2.number import format_byte_size
72 from webhelpers2.number import format_byte_size
73
73
74 from rhodecode.lib.action_parser import action_parser
74 from rhodecode.lib.action_parser import action_parser
75 from rhodecode.lib.ext_json import json
75 from rhodecode.lib.ext_json import json
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
79 AttributeDict, safe_int, md5, md5_safe
79 AttributeDict, safe_int, md5, md5_safe
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
85 from rhodecode.model.changeset_status import ChangesetStatusModel
85 from rhodecode.model.changeset_status import ChangesetStatusModel
86 from rhodecode.model.db import Permission, User, Repository
86 from rhodecode.model.db import Permission, User, Repository
87 from rhodecode.model.repo_group import RepoGroupModel
87 from rhodecode.model.repo_group import RepoGroupModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
89
89
90
90
91 log = logging.getLogger(__name__)
91 log = logging.getLogger(__name__)
92
92
93
93
94 DEFAULT_USER = User.DEFAULT_USER
94 DEFAULT_USER = User.DEFAULT_USER
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
96
96
97
97
98 def asset(path, ver=None, **kwargs):
98 def asset(path, ver=None, **kwargs):
99 """
99 """
100 Helper to generate a static asset file path for rhodecode assets
100 Helper to generate a static asset file path for rhodecode assets
101
101
102 eg. h.asset('images/image.png', ver='3923')
102 eg. h.asset('images/image.png', ver='3923')
103
103
104 :param path: path of asset
104 :param path: path of asset
105 :param ver: optional version query param to append as ?ver=
105 :param ver: optional version query param to append as ?ver=
106 """
106 """
107 request = get_current_request()
107 request = get_current_request()
108 query = {}
108 query = {}
109 query.update(kwargs)
109 query.update(kwargs)
110 if ver:
110 if ver:
111 query = {'ver': ver}
111 query = {'ver': ver}
112 return request.static_path(
112 return request.static_path(
113 'rhodecode:public/{}'.format(path), _query=query)
113 'rhodecode:public/{}'.format(path), _query=query)
114
114
115
115
116 default_html_escape_table = {
116 default_html_escape_table = {
117 ord('&'): u'&amp;',
117 ord('&'): u'&amp;',
118 ord('<'): u'&lt;',
118 ord('<'): u'&lt;',
119 ord('>'): u'&gt;',
119 ord('>'): u'&gt;',
120 ord('"'): u'&quot;',
120 ord('"'): u'&quot;',
121 ord("'"): u'&#39;',
121 ord("'"): u'&#39;',
122 }
122 }
123
123
124
124
125 def html_escape(text, html_escape_table=default_html_escape_table):
125 def html_escape(text, html_escape_table=default_html_escape_table):
126 """Produce entities within text."""
126 """Produce entities within text."""
127 return text.translate(html_escape_table)
127 return text.translate(html_escape_table)
128
128
129
129
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
131 """
131 """
132 Truncate string ``s`` at the first occurrence of ``sub``.
132 Truncate string ``s`` at the first occurrence of ``sub``.
133
133
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
135 """
135 """
136 suffix_if_chopped = suffix_if_chopped or ''
136 suffix_if_chopped = suffix_if_chopped or ''
137 pos = s.find(sub)
137 pos = s.find(sub)
138 if pos == -1:
138 if pos == -1:
139 return s
139 return s
140
140
141 if inclusive:
141 if inclusive:
142 pos += len(sub)
142 pos += len(sub)
143
143
144 chopped = s[:pos]
144 chopped = s[:pos]
145 left = s[pos:].strip()
145 left = s[pos:].strip()
146
146
147 if left and suffix_if_chopped:
147 if left and suffix_if_chopped:
148 chopped += suffix_if_chopped
148 chopped += suffix_if_chopped
149
149
150 return chopped
150 return chopped
151
151
152
152
153 def shorter(text, size=20):
153 def shorter(text, size=20):
154 postfix = '...'
154 postfix = '...'
155 if len(text) > size:
155 if len(text) > size:
156 return text[:size - len(postfix)] + postfix
156 return text[:size - len(postfix)] + postfix
157 return text
157 return text
158
158
159
159
160 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
160 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
161 """
161 """
162 Reset button
162 Reset button
163 """
163 """
164 _set_input_attrs(attrs, type, name, value)
164 _set_input_attrs(attrs, type, name, value)
165 _set_id_attr(attrs, id, name)
165 _set_id_attr(attrs, id, name)
166 convert_boolean_attrs(attrs, ["disabled"])
166 convert_boolean_attrs(attrs, ["disabled"])
167 return HTML.input(**attrs)
167 return HTML.input(**attrs)
168
168
169 reset = _reset
169 reset = _reset
170 safeid = _make_safe_id_component
170 safeid = _make_safe_id_component
171
171
172
172
173 def branding(name, length=40):
173 def branding(name, length=40):
174 return truncate(name, length, indicator="")
174 return truncate(name, length, indicator="")
175
175
176
176
177 def FID(raw_id, path):
177 def FID(raw_id, path):
178 """
178 """
179 Creates a unique ID for filenode based on it's hash of path and commit
179 Creates a unique ID for filenode based on it's hash of path and commit
180 it's safe to use in urls
180 it's safe to use in urls
181
181
182 :param raw_id:
182 :param raw_id:
183 :param path:
183 :param path:
184 """
184 """
185
185
186 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
186 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
187
187
188
188
189 class _GetError(object):
189 class _GetError(object):
190 """Get error from form_errors, and represent it as span wrapped error
190 """Get error from form_errors, and represent it as span wrapped error
191 message
191 message
192
192
193 :param field_name: field to fetch errors for
193 :param field_name: field to fetch errors for
194 :param form_errors: form errors dict
194 :param form_errors: form errors dict
195 """
195 """
196
196
197 def __call__(self, field_name, form_errors):
197 def __call__(self, field_name, form_errors):
198 tmpl = """<span class="error_msg">%s</span>"""
198 tmpl = """<span class="error_msg">%s</span>"""
199 if form_errors and field_name in form_errors:
199 if form_errors and field_name in form_errors:
200 return literal(tmpl % form_errors.get(field_name))
200 return literal(tmpl % form_errors.get(field_name))
201
201
202 get_error = _GetError()
202 get_error = _GetError()
203
203
204
204
205 class _ToolTip(object):
205 class _ToolTip(object):
206
206
207 def __call__(self, tooltip_title, trim_at=50):
207 def __call__(self, tooltip_title, trim_at=50):
208 """
208 """
209 Special function just to wrap our text into nice formatted
209 Special function just to wrap our text into nice formatted
210 autowrapped text
210 autowrapped text
211
211
212 :param tooltip_title:
212 :param tooltip_title:
213 """
213 """
214 tooltip_title = escape(tooltip_title)
214 tooltip_title = escape(tooltip_title)
215 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
215 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
216 return tooltip_title
216 return tooltip_title
217 tooltip = _ToolTip()
217 tooltip = _ToolTip()
218
218
219
219
220 def files_breadcrumbs(repo_name, commit_id, file_path):
220 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None):
221 if isinstance(file_path, str):
221 if isinstance(file_path, str):
222 file_path = safe_unicode(file_path)
222 file_path = safe_unicode(file_path)
223 route_qry = {'at': at_ref} if at_ref else None
223
224
224 # TODO: johbo: Is this always a url like path, or is this operating
225 # TODO: johbo: Is this always a url like path, or is this operating
225 # system dependent?
226 # system dependent?
226 path_segments = file_path.split('/')
227 path_segments = file_path.split('/')
227
228
228 repo_name_html = escape(repo_name)
229 repo_name_html = escape(repo_name)
229 if len(path_segments) == 1 and path_segments[0] == '':
230 if len(path_segments) == 1 and path_segments[0] == '':
230 url_segments = [repo_name_html]
231 url_segments = [repo_name_html]
231 else:
232 else:
232 url_segments = [
233 url_segments = [
233 link_to(
234 link_to(
234 repo_name_html,
235 repo_name_html,
235 route_path(
236 route_path(
236 'repo_files',
237 'repo_files',
237 repo_name=repo_name,
238 repo_name=repo_name,
238 commit_id=commit_id,
239 commit_id=commit_id,
239 f_path=''),
240 f_path='',
240 class_='pjax-link')]
241 _query=route_qry),
242 )]
241
243
242 last_cnt = len(path_segments) - 1
244 last_cnt = len(path_segments) - 1
243 for cnt, segment in enumerate(path_segments):
245 for cnt, segment in enumerate(path_segments):
244 if not segment:
246 if not segment:
245 continue
247 continue
246 segment_html = escape(segment)
248 segment_html = escape(segment)
247
249
248 if cnt != last_cnt:
250 if cnt != last_cnt:
249 url_segments.append(
251 url_segments.append(
250 link_to(
252 link_to(
251 segment_html,
253 segment_html,
252 route_path(
254 route_path(
253 'repo_files',
255 'repo_files',
254 repo_name=repo_name,
256 repo_name=repo_name,
255 commit_id=commit_id,
257 commit_id=commit_id,
256 f_path='/'.join(path_segments[:cnt + 1])),
258 f_path='/'.join(path_segments[:cnt + 1]),
257 class_='pjax-link'))
259 _query=route_qry),
260 ))
258 else:
261 else:
259 url_segments.append(segment_html)
262 url_segments.append(segment_html)
260
263
261 return literal('/'.join(url_segments))
264 return literal('/'.join(url_segments))
262
265
263
266
264 def code_highlight(code, lexer, formatter, use_hl_filter=False):
267 def code_highlight(code, lexer, formatter, use_hl_filter=False):
265 """
268 """
266 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
269 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
267
270
268 If ``outfile`` is given and a valid file object (an object
271 If ``outfile`` is given and a valid file object (an object
269 with a ``write`` method), the result will be written to it, otherwise
272 with a ``write`` method), the result will be written to it, otherwise
270 it is returned as a string.
273 it is returned as a string.
271 """
274 """
272 if use_hl_filter:
275 if use_hl_filter:
273 # add HL filter
276 # add HL filter
274 from rhodecode.lib.index import search_utils
277 from rhodecode.lib.index import search_utils
275 lexer.add_filter(search_utils.ElasticSearchHLFilter())
278 lexer.add_filter(search_utils.ElasticSearchHLFilter())
276 return pygments.format(pygments.lex(code, lexer), formatter)
279 return pygments.format(pygments.lex(code, lexer), formatter)
277
280
278
281
279 class CodeHtmlFormatter(HtmlFormatter):
282 class CodeHtmlFormatter(HtmlFormatter):
280 """
283 """
281 My code Html Formatter for source codes
284 My code Html Formatter for source codes
282 """
285 """
283
286
284 def wrap(self, source, outfile):
287 def wrap(self, source, outfile):
285 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
288 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
286
289
287 def _wrap_code(self, source):
290 def _wrap_code(self, source):
288 for cnt, it in enumerate(source):
291 for cnt, it in enumerate(source):
289 i, t = it
292 i, t = it
290 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
293 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
291 yield i, t
294 yield i, t
292
295
293 def _wrap_tablelinenos(self, inner):
296 def _wrap_tablelinenos(self, inner):
294 dummyoutfile = StringIO.StringIO()
297 dummyoutfile = StringIO.StringIO()
295 lncount = 0
298 lncount = 0
296 for t, line in inner:
299 for t, line in inner:
297 if t:
300 if t:
298 lncount += 1
301 lncount += 1
299 dummyoutfile.write(line)
302 dummyoutfile.write(line)
300
303
301 fl = self.linenostart
304 fl = self.linenostart
302 mw = len(str(lncount + fl - 1))
305 mw = len(str(lncount + fl - 1))
303 sp = self.linenospecial
306 sp = self.linenospecial
304 st = self.linenostep
307 st = self.linenostep
305 la = self.lineanchors
308 la = self.lineanchors
306 aln = self.anchorlinenos
309 aln = self.anchorlinenos
307 nocls = self.noclasses
310 nocls = self.noclasses
308 if sp:
311 if sp:
309 lines = []
312 lines = []
310
313
311 for i in range(fl, fl + lncount):
314 for i in range(fl, fl + lncount):
312 if i % st == 0:
315 if i % st == 0:
313 if i % sp == 0:
316 if i % sp == 0:
314 if aln:
317 if aln:
315 lines.append('<a href="#%s%d" class="special">%*d</a>' %
318 lines.append('<a href="#%s%d" class="special">%*d</a>' %
316 (la, i, mw, i))
319 (la, i, mw, i))
317 else:
320 else:
318 lines.append('<span class="special">%*d</span>' % (mw, i))
321 lines.append('<span class="special">%*d</span>' % (mw, i))
319 else:
322 else:
320 if aln:
323 if aln:
321 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
324 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
322 else:
325 else:
323 lines.append('%*d' % (mw, i))
326 lines.append('%*d' % (mw, i))
324 else:
327 else:
325 lines.append('')
328 lines.append('')
326 ls = '\n'.join(lines)
329 ls = '\n'.join(lines)
327 else:
330 else:
328 lines = []
331 lines = []
329 for i in range(fl, fl + lncount):
332 for i in range(fl, fl + lncount):
330 if i % st == 0:
333 if i % st == 0:
331 if aln:
334 if aln:
332 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
335 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
333 else:
336 else:
334 lines.append('%*d' % (mw, i))
337 lines.append('%*d' % (mw, i))
335 else:
338 else:
336 lines.append('')
339 lines.append('')
337 ls = '\n'.join(lines)
340 ls = '\n'.join(lines)
338
341
339 # in case you wonder about the seemingly redundant <div> here: since the
342 # in case you wonder about the seemingly redundant <div> here: since the
340 # content in the other cell also is wrapped in a div, some browsers in
343 # content in the other cell also is wrapped in a div, some browsers in
341 # some configurations seem to mess up the formatting...
344 # some configurations seem to mess up the formatting...
342 if nocls:
345 if nocls:
343 yield 0, ('<table class="%stable">' % self.cssclass +
346 yield 0, ('<table class="%stable">' % self.cssclass +
344 '<tr><td><div class="linenodiv" '
347 '<tr><td><div class="linenodiv" '
345 'style="background-color: #f0f0f0; padding-right: 10px">'
348 'style="background-color: #f0f0f0; padding-right: 10px">'
346 '<pre style="line-height: 125%">' +
349 '<pre style="line-height: 125%">' +
347 ls + '</pre></div></td><td id="hlcode" class="code">')
350 ls + '</pre></div></td><td id="hlcode" class="code">')
348 else:
351 else:
349 yield 0, ('<table class="%stable">' % self.cssclass +
352 yield 0, ('<table class="%stable">' % self.cssclass +
350 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
353 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
351 ls + '</pre></div></td><td id="hlcode" class="code">')
354 ls + '</pre></div></td><td id="hlcode" class="code">')
352 yield 0, dummyoutfile.getvalue()
355 yield 0, dummyoutfile.getvalue()
353 yield 0, '</td></tr></table>'
356 yield 0, '</td></tr></table>'
354
357
355
358
356 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
359 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
357 def __init__(self, **kw):
360 def __init__(self, **kw):
358 # only show these line numbers if set
361 # only show these line numbers if set
359 self.only_lines = kw.pop('only_line_numbers', [])
362 self.only_lines = kw.pop('only_line_numbers', [])
360 self.query_terms = kw.pop('query_terms', [])
363 self.query_terms = kw.pop('query_terms', [])
361 self.max_lines = kw.pop('max_lines', 5)
364 self.max_lines = kw.pop('max_lines', 5)
362 self.line_context = kw.pop('line_context', 3)
365 self.line_context = kw.pop('line_context', 3)
363 self.url = kw.pop('url', None)
366 self.url = kw.pop('url', None)
364
367
365 super(CodeHtmlFormatter, self).__init__(**kw)
368 super(CodeHtmlFormatter, self).__init__(**kw)
366
369
367 def _wrap_code(self, source):
370 def _wrap_code(self, source):
368 for cnt, it in enumerate(source):
371 for cnt, it in enumerate(source):
369 i, t = it
372 i, t = it
370 t = '<pre>%s</pre>' % t
373 t = '<pre>%s</pre>' % t
371 yield i, t
374 yield i, t
372
375
373 def _wrap_tablelinenos(self, inner):
376 def _wrap_tablelinenos(self, inner):
374 yield 0, '<table class="code-highlight %stable">' % self.cssclass
377 yield 0, '<table class="code-highlight %stable">' % self.cssclass
375
378
376 last_shown_line_number = 0
379 last_shown_line_number = 0
377 current_line_number = 1
380 current_line_number = 1
378
381
379 for t, line in inner:
382 for t, line in inner:
380 if not t:
383 if not t:
381 yield t, line
384 yield t, line
382 continue
385 continue
383
386
384 if current_line_number in self.only_lines:
387 if current_line_number in self.only_lines:
385 if last_shown_line_number + 1 != current_line_number:
388 if last_shown_line_number + 1 != current_line_number:
386 yield 0, '<tr>'
389 yield 0, '<tr>'
387 yield 0, '<td class="line">...</td>'
390 yield 0, '<td class="line">...</td>'
388 yield 0, '<td id="hlcode" class="code"></td>'
391 yield 0, '<td id="hlcode" class="code"></td>'
389 yield 0, '</tr>'
392 yield 0, '</tr>'
390
393
391 yield 0, '<tr>'
394 yield 0, '<tr>'
392 if self.url:
395 if self.url:
393 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
396 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
394 self.url, current_line_number, current_line_number)
397 self.url, current_line_number, current_line_number)
395 else:
398 else:
396 yield 0, '<td class="line"><a href="">%i</a></td>' % (
399 yield 0, '<td class="line"><a href="">%i</a></td>' % (
397 current_line_number)
400 current_line_number)
398 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
401 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
399 yield 0, '</tr>'
402 yield 0, '</tr>'
400
403
401 last_shown_line_number = current_line_number
404 last_shown_line_number = current_line_number
402
405
403 current_line_number += 1
406 current_line_number += 1
404
407
405 yield 0, '</table>'
408 yield 0, '</table>'
406
409
407
410
408 def hsv_to_rgb(h, s, v):
411 def hsv_to_rgb(h, s, v):
409 """ Convert hsv color values to rgb """
412 """ Convert hsv color values to rgb """
410
413
411 if s == 0.0:
414 if s == 0.0:
412 return v, v, v
415 return v, v, v
413 i = int(h * 6.0) # XXX assume int() truncates!
416 i = int(h * 6.0) # XXX assume int() truncates!
414 f = (h * 6.0) - i
417 f = (h * 6.0) - i
415 p = v * (1.0 - s)
418 p = v * (1.0 - s)
416 q = v * (1.0 - s * f)
419 q = v * (1.0 - s * f)
417 t = v * (1.0 - s * (1.0 - f))
420 t = v * (1.0 - s * (1.0 - f))
418 i = i % 6
421 i = i % 6
419 if i == 0:
422 if i == 0:
420 return v, t, p
423 return v, t, p
421 if i == 1:
424 if i == 1:
422 return q, v, p
425 return q, v, p
423 if i == 2:
426 if i == 2:
424 return p, v, t
427 return p, v, t
425 if i == 3:
428 if i == 3:
426 return p, q, v
429 return p, q, v
427 if i == 4:
430 if i == 4:
428 return t, p, v
431 return t, p, v
429 if i == 5:
432 if i == 5:
430 return v, p, q
433 return v, p, q
431
434
432
435
433 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
436 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
434 """
437 """
435 Generator for getting n of evenly distributed colors using
438 Generator for getting n of evenly distributed colors using
436 hsv color and golden ratio. It always return same order of colors
439 hsv color and golden ratio. It always return same order of colors
437
440
438 :param n: number of colors to generate
441 :param n: number of colors to generate
439 :param saturation: saturation of returned colors
442 :param saturation: saturation of returned colors
440 :param lightness: lightness of returned colors
443 :param lightness: lightness of returned colors
441 :returns: RGB tuple
444 :returns: RGB tuple
442 """
445 """
443
446
444 golden_ratio = 0.618033988749895
447 golden_ratio = 0.618033988749895
445 h = 0.22717784590367374
448 h = 0.22717784590367374
446
449
447 for _ in xrange(n):
450 for _ in xrange(n):
448 h += golden_ratio
451 h += golden_ratio
449 h %= 1
452 h %= 1
450 HSV_tuple = [h, saturation, lightness]
453 HSV_tuple = [h, saturation, lightness]
451 RGB_tuple = hsv_to_rgb(*HSV_tuple)
454 RGB_tuple = hsv_to_rgb(*HSV_tuple)
452 yield map(lambda x: str(int(x * 256)), RGB_tuple)
455 yield map(lambda x: str(int(x * 256)), RGB_tuple)
453
456
454
457
455 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
458 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
456 """
459 """
457 Returns a function which when called with an argument returns a unique
460 Returns a function which when called with an argument returns a unique
458 color for that argument, eg.
461 color for that argument, eg.
459
462
460 :param n: number of colors to generate
463 :param n: number of colors to generate
461 :param saturation: saturation of returned colors
464 :param saturation: saturation of returned colors
462 :param lightness: lightness of returned colors
465 :param lightness: lightness of returned colors
463 :returns: css RGB string
466 :returns: css RGB string
464
467
465 >>> color_hash = color_hasher()
468 >>> color_hash = color_hasher()
466 >>> color_hash('hello')
469 >>> color_hash('hello')
467 'rgb(34, 12, 59)'
470 'rgb(34, 12, 59)'
468 >>> color_hash('hello')
471 >>> color_hash('hello')
469 'rgb(34, 12, 59)'
472 'rgb(34, 12, 59)'
470 >>> color_hash('other')
473 >>> color_hash('other')
471 'rgb(90, 224, 159)'
474 'rgb(90, 224, 159)'
472 """
475 """
473
476
474 color_dict = {}
477 color_dict = {}
475 cgenerator = unique_color_generator(
478 cgenerator = unique_color_generator(
476 saturation=saturation, lightness=lightness)
479 saturation=saturation, lightness=lightness)
477
480
478 def get_color_string(thing):
481 def get_color_string(thing):
479 if thing in color_dict:
482 if thing in color_dict:
480 col = color_dict[thing]
483 col = color_dict[thing]
481 else:
484 else:
482 col = color_dict[thing] = cgenerator.next()
485 col = color_dict[thing] = cgenerator.next()
483 return "rgb(%s)" % (', '.join(col))
486 return "rgb(%s)" % (', '.join(col))
484
487
485 return get_color_string
488 return get_color_string
486
489
487
490
488 def get_lexer_safe(mimetype=None, filepath=None):
491 def get_lexer_safe(mimetype=None, filepath=None):
489 """
492 """
490 Tries to return a relevant pygments lexer using mimetype/filepath name,
493 Tries to return a relevant pygments lexer using mimetype/filepath name,
491 defaulting to plain text if none could be found
494 defaulting to plain text if none could be found
492 """
495 """
493 lexer = None
496 lexer = None
494 try:
497 try:
495 if mimetype:
498 if mimetype:
496 lexer = get_lexer_for_mimetype(mimetype)
499 lexer = get_lexer_for_mimetype(mimetype)
497 if not lexer:
500 if not lexer:
498 lexer = get_lexer_for_filename(filepath)
501 lexer = get_lexer_for_filename(filepath)
499 except pygments.util.ClassNotFound:
502 except pygments.util.ClassNotFound:
500 pass
503 pass
501
504
502 if not lexer:
505 if not lexer:
503 lexer = get_lexer_by_name('text')
506 lexer = get_lexer_by_name('text')
504
507
505 return lexer
508 return lexer
506
509
507
510
508 def get_lexer_for_filenode(filenode):
511 def get_lexer_for_filenode(filenode):
509 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
512 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
510 return lexer
513 return lexer
511
514
512
515
513 def pygmentize(filenode, **kwargs):
516 def pygmentize(filenode, **kwargs):
514 """
517 """
515 pygmentize function using pygments
518 pygmentize function using pygments
516
519
517 :param filenode:
520 :param filenode:
518 """
521 """
519 lexer = get_lexer_for_filenode(filenode)
522 lexer = get_lexer_for_filenode(filenode)
520 return literal(code_highlight(filenode.content, lexer,
523 return literal(code_highlight(filenode.content, lexer,
521 CodeHtmlFormatter(**kwargs)))
524 CodeHtmlFormatter(**kwargs)))
522
525
523
526
524 def is_following_repo(repo_name, user_id):
527 def is_following_repo(repo_name, user_id):
525 from rhodecode.model.scm import ScmModel
528 from rhodecode.model.scm import ScmModel
526 return ScmModel().is_following_repo(repo_name, user_id)
529 return ScmModel().is_following_repo(repo_name, user_id)
527
530
528
531
529 class _Message(object):
532 class _Message(object):
530 """A message returned by ``Flash.pop_messages()``.
533 """A message returned by ``Flash.pop_messages()``.
531
534
532 Converting the message to a string returns the message text. Instances
535 Converting the message to a string returns the message text. Instances
533 also have the following attributes:
536 also have the following attributes:
534
537
535 * ``message``: the message text.
538 * ``message``: the message text.
536 * ``category``: the category specified when the message was created.
539 * ``category``: the category specified when the message was created.
537 """
540 """
538
541
539 def __init__(self, category, message):
542 def __init__(self, category, message):
540 self.category = category
543 self.category = category
541 self.message = message
544 self.message = message
542
545
543 def __str__(self):
546 def __str__(self):
544 return self.message
547 return self.message
545
548
546 __unicode__ = __str__
549 __unicode__ = __str__
547
550
548 def __html__(self):
551 def __html__(self):
549 return escape(safe_unicode(self.message))
552 return escape(safe_unicode(self.message))
550
553
551
554
552 class Flash(object):
555 class Flash(object):
553 # List of allowed categories. If None, allow any category.
556 # List of allowed categories. If None, allow any category.
554 categories = ["warning", "notice", "error", "success"]
557 categories = ["warning", "notice", "error", "success"]
555
558
556 # Default category if none is specified.
559 # Default category if none is specified.
557 default_category = "notice"
560 default_category = "notice"
558
561
559 def __init__(self, session_key="flash", categories=None,
562 def __init__(self, session_key="flash", categories=None,
560 default_category=None):
563 default_category=None):
561 """
564 """
562 Instantiate a ``Flash`` object.
565 Instantiate a ``Flash`` object.
563
566
564 ``session_key`` is the key to save the messages under in the user's
567 ``session_key`` is the key to save the messages under in the user's
565 session.
568 session.
566
569
567 ``categories`` is an optional list which overrides the default list
570 ``categories`` is an optional list which overrides the default list
568 of categories.
571 of categories.
569
572
570 ``default_category`` overrides the default category used for messages
573 ``default_category`` overrides the default category used for messages
571 when none is specified.
574 when none is specified.
572 """
575 """
573 self.session_key = session_key
576 self.session_key = session_key
574 if categories is not None:
577 if categories is not None:
575 self.categories = categories
578 self.categories = categories
576 if default_category is not None:
579 if default_category is not None:
577 self.default_category = default_category
580 self.default_category = default_category
578 if self.categories and self.default_category not in self.categories:
581 if self.categories and self.default_category not in self.categories:
579 raise ValueError(
582 raise ValueError(
580 "unrecognized default category %r" % (self.default_category,))
583 "unrecognized default category %r" % (self.default_category,))
581
584
582 def pop_messages(self, session=None, request=None):
585 def pop_messages(self, session=None, request=None):
583 """
586 """
584 Return all accumulated messages and delete them from the session.
587 Return all accumulated messages and delete them from the session.
585
588
586 The return value is a list of ``Message`` objects.
589 The return value is a list of ``Message`` objects.
587 """
590 """
588 messages = []
591 messages = []
589
592
590 if not session:
593 if not session:
591 if not request:
594 if not request:
592 request = get_current_request()
595 request = get_current_request()
593 session = request.session
596 session = request.session
594
597
595 # Pop the 'old' pylons flash messages. They are tuples of the form
598 # Pop the 'old' pylons flash messages. They are tuples of the form
596 # (category, message)
599 # (category, message)
597 for cat, msg in session.pop(self.session_key, []):
600 for cat, msg in session.pop(self.session_key, []):
598 messages.append(_Message(cat, msg))
601 messages.append(_Message(cat, msg))
599
602
600 # Pop the 'new' pyramid flash messages for each category as list
603 # Pop the 'new' pyramid flash messages for each category as list
601 # of strings.
604 # of strings.
602 for cat in self.categories:
605 for cat in self.categories:
603 for msg in session.pop_flash(queue=cat):
606 for msg in session.pop_flash(queue=cat):
604 messages.append(_Message(cat, msg))
607 messages.append(_Message(cat, msg))
605 # Map messages from the default queue to the 'notice' category.
608 # Map messages from the default queue to the 'notice' category.
606 for msg in session.pop_flash():
609 for msg in session.pop_flash():
607 messages.append(_Message('notice', msg))
610 messages.append(_Message('notice', msg))
608
611
609 session.save()
612 session.save()
610 return messages
613 return messages
611
614
612 def json_alerts(self, session=None, request=None):
615 def json_alerts(self, session=None, request=None):
613 payloads = []
616 payloads = []
614 messages = flash.pop_messages(session=session, request=request)
617 messages = flash.pop_messages(session=session, request=request)
615 if messages:
618 if messages:
616 for message in messages:
619 for message in messages:
617 subdata = {}
620 subdata = {}
618 if hasattr(message.message, 'rsplit'):
621 if hasattr(message.message, 'rsplit'):
619 flash_data = message.message.rsplit('|DELIM|', 1)
622 flash_data = message.message.rsplit('|DELIM|', 1)
620 org_message = flash_data[0]
623 org_message = flash_data[0]
621 if len(flash_data) > 1:
624 if len(flash_data) > 1:
622 subdata = json.loads(flash_data[1])
625 subdata = json.loads(flash_data[1])
623 else:
626 else:
624 org_message = message.message
627 org_message = message.message
625 payloads.append({
628 payloads.append({
626 'message': {
629 'message': {
627 'message': u'{}'.format(org_message),
630 'message': u'{}'.format(org_message),
628 'level': message.category,
631 'level': message.category,
629 'force': True,
632 'force': True,
630 'subdata': subdata
633 'subdata': subdata
631 }
634 }
632 })
635 })
633 return json.dumps(payloads)
636 return json.dumps(payloads)
634
637
635 def __call__(self, message, category=None, ignore_duplicate=False,
638 def __call__(self, message, category=None, ignore_duplicate=False,
636 session=None, request=None):
639 session=None, request=None):
637
640
638 if not session:
641 if not session:
639 if not request:
642 if not request:
640 request = get_current_request()
643 request = get_current_request()
641 session = request.session
644 session = request.session
642
645
643 session.flash(
646 session.flash(
644 message, queue=category, allow_duplicate=not ignore_duplicate)
647 message, queue=category, allow_duplicate=not ignore_duplicate)
645
648
646
649
647 flash = Flash()
650 flash = Flash()
648
651
649 #==============================================================================
652 #==============================================================================
650 # SCM FILTERS available via h.
653 # SCM FILTERS available via h.
651 #==============================================================================
654 #==============================================================================
652 from rhodecode.lib.vcs.utils import author_name, author_email
655 from rhodecode.lib.vcs.utils import author_name, author_email
653 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
656 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
654 from rhodecode.model.db import User, ChangesetStatus
657 from rhodecode.model.db import User, ChangesetStatus
655
658
656 capitalize = lambda x: x.capitalize()
659 capitalize = lambda x: x.capitalize()
657 email = author_email
660 email = author_email
658 short_id = lambda x: x[:12]
661 short_id = lambda x: x[:12]
659 hide_credentials = lambda x: ''.join(credentials_filter(x))
662 hide_credentials = lambda x: ''.join(credentials_filter(x))
660
663
661
664
662 import pytz
665 import pytz
663 import tzlocal
666 import tzlocal
664 local_timezone = tzlocal.get_localzone()
667 local_timezone = tzlocal.get_localzone()
665
668
666
669
667 def age_component(datetime_iso, value=None, time_is_local=False):
670 def age_component(datetime_iso, value=None, time_is_local=False):
668 title = value or format_date(datetime_iso)
671 title = value or format_date(datetime_iso)
669 tzinfo = '+00:00'
672 tzinfo = '+00:00'
670
673
671 # detect if we have a timezone info, otherwise, add it
674 # detect if we have a timezone info, otherwise, add it
672 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
675 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
673 force_timezone = os.environ.get('RC_TIMEZONE', '')
676 force_timezone = os.environ.get('RC_TIMEZONE', '')
674 if force_timezone:
677 if force_timezone:
675 force_timezone = pytz.timezone(force_timezone)
678 force_timezone = pytz.timezone(force_timezone)
676 timezone = force_timezone or local_timezone
679 timezone = force_timezone or local_timezone
677 offset = timezone.localize(datetime_iso).strftime('%z')
680 offset = timezone.localize(datetime_iso).strftime('%z')
678 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
681 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
679
682
680 return literal(
683 return literal(
681 '<time class="timeago tooltip" '
684 '<time class="timeago tooltip" '
682 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
685 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
683 datetime_iso, title, tzinfo))
686 datetime_iso, title, tzinfo))
684
687
685
688
686 def _shorten_commit_id(commit_id, commit_len=None):
689 def _shorten_commit_id(commit_id, commit_len=None):
687 if commit_len is None:
690 if commit_len is None:
688 request = get_current_request()
691 request = get_current_request()
689 commit_len = request.call_context.visual.show_sha_length
692 commit_len = request.call_context.visual.show_sha_length
690 return commit_id[:commit_len]
693 return commit_id[:commit_len]
691
694
692
695
693 def show_id(commit, show_idx=None, commit_len=None):
696 def show_id(commit, show_idx=None, commit_len=None):
694 """
697 """
695 Configurable function that shows ID
698 Configurable function that shows ID
696 by default it's r123:fffeeefffeee
699 by default it's r123:fffeeefffeee
697
700
698 :param commit: commit instance
701 :param commit: commit instance
699 """
702 """
700 if show_idx is None:
703 if show_idx is None:
701 request = get_current_request()
704 request = get_current_request()
702 show_idx = request.call_context.visual.show_revision_number
705 show_idx = request.call_context.visual.show_revision_number
703
706
704 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
707 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
705 if show_idx:
708 if show_idx:
706 return 'r%s:%s' % (commit.idx, raw_id)
709 return 'r%s:%s' % (commit.idx, raw_id)
707 else:
710 else:
708 return '%s' % (raw_id, )
711 return '%s' % (raw_id, )
709
712
710
713
711 def format_date(date):
714 def format_date(date):
712 """
715 """
713 use a standardized formatting for dates used in RhodeCode
716 use a standardized formatting for dates used in RhodeCode
714
717
715 :param date: date/datetime object
718 :param date: date/datetime object
716 :return: formatted date
719 :return: formatted date
717 """
720 """
718
721
719 if date:
722 if date:
720 _fmt = "%a, %d %b %Y %H:%M:%S"
723 _fmt = "%a, %d %b %Y %H:%M:%S"
721 return safe_unicode(date.strftime(_fmt))
724 return safe_unicode(date.strftime(_fmt))
722
725
723 return u""
726 return u""
724
727
725
728
726 class _RepoChecker(object):
729 class _RepoChecker(object):
727
730
728 def __init__(self, backend_alias):
731 def __init__(self, backend_alias):
729 self._backend_alias = backend_alias
732 self._backend_alias = backend_alias
730
733
731 def __call__(self, repository):
734 def __call__(self, repository):
732 if hasattr(repository, 'alias'):
735 if hasattr(repository, 'alias'):
733 _type = repository.alias
736 _type = repository.alias
734 elif hasattr(repository, 'repo_type'):
737 elif hasattr(repository, 'repo_type'):
735 _type = repository.repo_type
738 _type = repository.repo_type
736 else:
739 else:
737 _type = repository
740 _type = repository
738 return _type == self._backend_alias
741 return _type == self._backend_alias
739
742
740
743
741 is_git = _RepoChecker('git')
744 is_git = _RepoChecker('git')
742 is_hg = _RepoChecker('hg')
745 is_hg = _RepoChecker('hg')
743 is_svn = _RepoChecker('svn')
746 is_svn = _RepoChecker('svn')
744
747
745
748
746 def get_repo_type_by_name(repo_name):
749 def get_repo_type_by_name(repo_name):
747 repo = Repository.get_by_repo_name(repo_name)
750 repo = Repository.get_by_repo_name(repo_name)
748 if repo:
751 if repo:
749 return repo.repo_type
752 return repo.repo_type
750
753
751
754
752 def is_svn_without_proxy(repository):
755 def is_svn_without_proxy(repository):
753 if is_svn(repository):
756 if is_svn(repository):
754 from rhodecode.model.settings import VcsSettingsModel
757 from rhodecode.model.settings import VcsSettingsModel
755 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
758 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
756 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
759 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
757 return False
760 return False
758
761
759
762
760 def discover_user(author):
763 def discover_user(author):
761 """
764 """
762 Tries to discover RhodeCode User based on the autho string. Author string
765 Tries to discover RhodeCode User based on the autho string. Author string
763 is typically `FirstName LastName <email@address.com>`
766 is typically `FirstName LastName <email@address.com>`
764 """
767 """
765
768
766 # if author is already an instance use it for extraction
769 # if author is already an instance use it for extraction
767 if isinstance(author, User):
770 if isinstance(author, User):
768 return author
771 return author
769
772
770 # Valid email in the attribute passed, see if they're in the system
773 # Valid email in the attribute passed, see if they're in the system
771 _email = author_email(author)
774 _email = author_email(author)
772 if _email != '':
775 if _email != '':
773 user = User.get_by_email(_email, case_insensitive=True, cache=True)
776 user = User.get_by_email(_email, case_insensitive=True, cache=True)
774 if user is not None:
777 if user is not None:
775 return user
778 return user
776
779
777 # Maybe it's a username, we try to extract it and fetch by username ?
780 # Maybe it's a username, we try to extract it and fetch by username ?
778 _author = author_name(author)
781 _author = author_name(author)
779 user = User.get_by_username(_author, case_insensitive=True, cache=True)
782 user = User.get_by_username(_author, case_insensitive=True, cache=True)
780 if user is not None:
783 if user is not None:
781 return user
784 return user
782
785
783 return None
786 return None
784
787
785
788
786 def email_or_none(author):
789 def email_or_none(author):
787 # extract email from the commit string
790 # extract email from the commit string
788 _email = author_email(author)
791 _email = author_email(author)
789
792
790 # If we have an email, use it, otherwise
793 # If we have an email, use it, otherwise
791 # see if it contains a username we can get an email from
794 # see if it contains a username we can get an email from
792 if _email != '':
795 if _email != '':
793 return _email
796 return _email
794 else:
797 else:
795 user = User.get_by_username(
798 user = User.get_by_username(
796 author_name(author), case_insensitive=True, cache=True)
799 author_name(author), case_insensitive=True, cache=True)
797
800
798 if user is not None:
801 if user is not None:
799 return user.email
802 return user.email
800
803
801 # No valid email, not a valid user in the system, none!
804 # No valid email, not a valid user in the system, none!
802 return None
805 return None
803
806
804
807
805 def link_to_user(author, length=0, **kwargs):
808 def link_to_user(author, length=0, **kwargs):
806 user = discover_user(author)
809 user = discover_user(author)
807 # user can be None, but if we have it already it means we can re-use it
810 # user can be None, but if we have it already it means we can re-use it
808 # in the person() function, so we save 1 intensive-query
811 # in the person() function, so we save 1 intensive-query
809 if user:
812 if user:
810 author = user
813 author = user
811
814
812 display_person = person(author, 'username_or_name_or_email')
815 display_person = person(author, 'username_or_name_or_email')
813 if length:
816 if length:
814 display_person = shorter(display_person, length)
817 display_person = shorter(display_person, length)
815
818
816 if user:
819 if user:
817 return link_to(
820 return link_to(
818 escape(display_person),
821 escape(display_person),
819 route_path('user_profile', username=user.username),
822 route_path('user_profile', username=user.username),
820 **kwargs)
823 **kwargs)
821 else:
824 else:
822 return escape(display_person)
825 return escape(display_person)
823
826
824
827
825 def link_to_group(users_group_name, **kwargs):
828 def link_to_group(users_group_name, **kwargs):
826 return link_to(
829 return link_to(
827 escape(users_group_name),
830 escape(users_group_name),
828 route_path('user_group_profile', user_group_name=users_group_name),
831 route_path('user_group_profile', user_group_name=users_group_name),
829 **kwargs)
832 **kwargs)
830
833
831
834
832 def person(author, show_attr="username_and_name"):
835 def person(author, show_attr="username_and_name"):
833 user = discover_user(author)
836 user = discover_user(author)
834 if user:
837 if user:
835 return getattr(user, show_attr)
838 return getattr(user, show_attr)
836 else:
839 else:
837 _author = author_name(author)
840 _author = author_name(author)
838 _email = email(author)
841 _email = email(author)
839 return _author or _email
842 return _author or _email
840
843
841
844
842 def author_string(email):
845 def author_string(email):
843 if email:
846 if email:
844 user = User.get_by_email(email, case_insensitive=True, cache=True)
847 user = User.get_by_email(email, case_insensitive=True, cache=True)
845 if user:
848 if user:
846 if user.first_name or user.last_name:
849 if user.first_name or user.last_name:
847 return '%s %s &lt;%s&gt;' % (
850 return '%s %s &lt;%s&gt;' % (
848 user.first_name, user.last_name, email)
851 user.first_name, user.last_name, email)
849 else:
852 else:
850 return email
853 return email
851 else:
854 else:
852 return email
855 return email
853 else:
856 else:
854 return None
857 return None
855
858
856
859
857 def person_by_id(id_, show_attr="username_and_name"):
860 def person_by_id(id_, show_attr="username_and_name"):
858 # attr to return from fetched user
861 # attr to return from fetched user
859 person_getter = lambda usr: getattr(usr, show_attr)
862 person_getter = lambda usr: getattr(usr, show_attr)
860
863
861 #maybe it's an ID ?
864 #maybe it's an ID ?
862 if str(id_).isdigit() or isinstance(id_, int):
865 if str(id_).isdigit() or isinstance(id_, int):
863 id_ = int(id_)
866 id_ = int(id_)
864 user = User.get(id_)
867 user = User.get(id_)
865 if user is not None:
868 if user is not None:
866 return person_getter(user)
869 return person_getter(user)
867 return id_
870 return id_
868
871
869
872
870 def gravatar_with_user(request, author, show_disabled=False):
873 def gravatar_with_user(request, author, show_disabled=False):
871 _render = request.get_partial_renderer(
874 _render = request.get_partial_renderer(
872 'rhodecode:templates/base/base.mako')
875 'rhodecode:templates/base/base.mako')
873 return _render('gravatar_with_user', author, show_disabled=show_disabled)
876 return _render('gravatar_with_user', author, show_disabled=show_disabled)
874
877
875
878
876 tags_paterns = OrderedDict((
879 tags_paterns = OrderedDict((
877 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
880 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
878 '<div class="metatag" tag="lang">\\2</div>')),
881 '<div class="metatag" tag="lang">\\2</div>')),
879
882
880 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
883 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
881 '<div class="metatag" tag="see">see: \\1 </div>')),
884 '<div class="metatag" tag="see">see: \\1 </div>')),
882
885
883 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
886 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
884 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
887 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
885
888
886 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
889 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
887 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
890 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
888
891
889 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
892 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
890 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
893 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
891
894
892 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
895 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
893 '<div class="metatag" tag="state \\1">\\1</div>')),
896 '<div class="metatag" tag="state \\1">\\1</div>')),
894
897
895 # label in grey
898 # label in grey
896 ('label', (re.compile(r'\[([a-z]+)\]'),
899 ('label', (re.compile(r'\[([a-z]+)\]'),
897 '<div class="metatag" tag="label">\\1</div>')),
900 '<div class="metatag" tag="label">\\1</div>')),
898
901
899 # generic catch all in grey
902 # generic catch all in grey
900 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
903 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
901 '<div class="metatag" tag="generic">\\1</div>')),
904 '<div class="metatag" tag="generic">\\1</div>')),
902 ))
905 ))
903
906
904
907
905 def extract_metatags(value):
908 def extract_metatags(value):
906 """
909 """
907 Extract supported meta-tags from given text value
910 Extract supported meta-tags from given text value
908 """
911 """
909 tags = []
912 tags = []
910 if not value:
913 if not value:
911 return tags, ''
914 return tags, ''
912
915
913 for key, val in tags_paterns.items():
916 for key, val in tags_paterns.items():
914 pat, replace_html = val
917 pat, replace_html = val
915 tags.extend([(key, x.group()) for x in pat.finditer(value)])
918 tags.extend([(key, x.group()) for x in pat.finditer(value)])
916 value = pat.sub('', value)
919 value = pat.sub('', value)
917
920
918 return tags, value
921 return tags, value
919
922
920
923
921 def style_metatag(tag_type, value):
924 def style_metatag(tag_type, value):
922 """
925 """
923 converts tags from value into html equivalent
926 converts tags from value into html equivalent
924 """
927 """
925 if not value:
928 if not value:
926 return ''
929 return ''
927
930
928 html_value = value
931 html_value = value
929 tag_data = tags_paterns.get(tag_type)
932 tag_data = tags_paterns.get(tag_type)
930 if tag_data:
933 if tag_data:
931 pat, replace_html = tag_data
934 pat, replace_html = tag_data
932 # convert to plain `unicode` instead of a markup tag to be used in
935 # convert to plain `unicode` instead of a markup tag to be used in
933 # regex expressions. safe_unicode doesn't work here
936 # regex expressions. safe_unicode doesn't work here
934 html_value = pat.sub(replace_html, unicode(value))
937 html_value = pat.sub(replace_html, unicode(value))
935
938
936 return html_value
939 return html_value
937
940
938
941
939 def bool2icon(value, show_at_false=True):
942 def bool2icon(value, show_at_false=True):
940 """
943 """
941 Returns boolean value of a given value, represented as html element with
944 Returns boolean value of a given value, represented as html element with
942 classes that will represent icons
945 classes that will represent icons
943
946
944 :param value: given value to convert to html node
947 :param value: given value to convert to html node
945 """
948 """
946
949
947 if value: # does bool conversion
950 if value: # does bool conversion
948 return HTML.tag('i', class_="icon-true")
951 return HTML.tag('i', class_="icon-true")
949 else: # not true as bool
952 else: # not true as bool
950 if show_at_false:
953 if show_at_false:
951 return HTML.tag('i', class_="icon-false")
954 return HTML.tag('i', class_="icon-false")
952 return HTML.tag('i')
955 return HTML.tag('i')
953
956
954 #==============================================================================
957 #==============================================================================
955 # PERMS
958 # PERMS
956 #==============================================================================
959 #==============================================================================
957 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
960 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
958 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
961 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
959 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
962 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
960 csrf_token_key
963 csrf_token_key
961
964
962
965
963 #==============================================================================
966 #==============================================================================
964 # GRAVATAR URL
967 # GRAVATAR URL
965 #==============================================================================
968 #==============================================================================
966 class InitialsGravatar(object):
969 class InitialsGravatar(object):
967 def __init__(self, email_address, first_name, last_name, size=30,
970 def __init__(self, email_address, first_name, last_name, size=30,
968 background=None, text_color='#fff'):
971 background=None, text_color='#fff'):
969 self.size = size
972 self.size = size
970 self.first_name = first_name
973 self.first_name = first_name
971 self.last_name = last_name
974 self.last_name = last_name
972 self.email_address = email_address
975 self.email_address = email_address
973 self.background = background or self.str2color(email_address)
976 self.background = background or self.str2color(email_address)
974 self.text_color = text_color
977 self.text_color = text_color
975
978
976 def get_color_bank(self):
979 def get_color_bank(self):
977 """
980 """
978 returns a predefined list of colors that gravatars can use.
981 returns a predefined list of colors that gravatars can use.
979 Those are randomized distinct colors that guarantee readability and
982 Those are randomized distinct colors that guarantee readability and
980 uniqueness.
983 uniqueness.
981
984
982 generated with: http://phrogz.net/css/distinct-colors.html
985 generated with: http://phrogz.net/css/distinct-colors.html
983 """
986 """
984 return [
987 return [
985 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
988 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
986 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
989 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
987 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
990 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
988 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
991 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
989 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
992 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
990 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
993 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
991 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
994 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
992 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
995 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
993 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
996 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
994 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
997 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
995 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
998 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
996 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
999 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
997 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1000 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
998 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1001 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
999 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1002 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1000 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1003 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1001 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1004 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1002 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1005 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1003 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1006 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1004 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1007 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1005 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1008 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1006 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1009 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1007 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1010 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1008 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1011 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1009 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1012 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1010 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1013 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1011 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1014 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1012 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1015 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1013 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1016 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1014 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1017 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1015 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1018 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1016 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1019 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1017 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1020 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1018 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1021 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1019 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1022 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1020 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1023 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1021 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1024 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1022 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1025 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1023 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1026 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1024 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1027 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1025 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1028 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1026 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1029 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1027 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1030 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1028 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1031 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1029 '#4f8c46', '#368dd9', '#5c0073'
1032 '#4f8c46', '#368dd9', '#5c0073'
1030 ]
1033 ]
1031
1034
1032 def rgb_to_hex_color(self, rgb_tuple):
1035 def rgb_to_hex_color(self, rgb_tuple):
1033 """
1036 """
1034 Converts an rgb_tuple passed to an hex color.
1037 Converts an rgb_tuple passed to an hex color.
1035
1038
1036 :param rgb_tuple: tuple with 3 ints represents rgb color space
1039 :param rgb_tuple: tuple with 3 ints represents rgb color space
1037 """
1040 """
1038 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1041 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1039
1042
1040 def email_to_int_list(self, email_str):
1043 def email_to_int_list(self, email_str):
1041 """
1044 """
1042 Get every byte of the hex digest value of email and turn it to integer.
1045 Get every byte of the hex digest value of email and turn it to integer.
1043 It's going to be always between 0-255
1046 It's going to be always between 0-255
1044 """
1047 """
1045 digest = md5_safe(email_str.lower())
1048 digest = md5_safe(email_str.lower())
1046 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1049 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1047
1050
1048 def pick_color_bank_index(self, email_str, color_bank):
1051 def pick_color_bank_index(self, email_str, color_bank):
1049 return self.email_to_int_list(email_str)[0] % len(color_bank)
1052 return self.email_to_int_list(email_str)[0] % len(color_bank)
1050
1053
1051 def str2color(self, email_str):
1054 def str2color(self, email_str):
1052 """
1055 """
1053 Tries to map in a stable algorithm an email to color
1056 Tries to map in a stable algorithm an email to color
1054
1057
1055 :param email_str:
1058 :param email_str:
1056 """
1059 """
1057 color_bank = self.get_color_bank()
1060 color_bank = self.get_color_bank()
1058 # pick position (module it's length so we always find it in the
1061 # pick position (module it's length so we always find it in the
1059 # bank even if it's smaller than 256 values
1062 # bank even if it's smaller than 256 values
1060 pos = self.pick_color_bank_index(email_str, color_bank)
1063 pos = self.pick_color_bank_index(email_str, color_bank)
1061 return color_bank[pos]
1064 return color_bank[pos]
1062
1065
1063 def normalize_email(self, email_address):
1066 def normalize_email(self, email_address):
1064 import unicodedata
1067 import unicodedata
1065 # default host used to fill in the fake/missing email
1068 # default host used to fill in the fake/missing email
1066 default_host = u'localhost'
1069 default_host = u'localhost'
1067
1070
1068 if not email_address:
1071 if not email_address:
1069 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1072 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1070
1073
1071 email_address = safe_unicode(email_address)
1074 email_address = safe_unicode(email_address)
1072
1075
1073 if u'@' not in email_address:
1076 if u'@' not in email_address:
1074 email_address = u'%s@%s' % (email_address, default_host)
1077 email_address = u'%s@%s' % (email_address, default_host)
1075
1078
1076 if email_address.endswith(u'@'):
1079 if email_address.endswith(u'@'):
1077 email_address = u'%s%s' % (email_address, default_host)
1080 email_address = u'%s%s' % (email_address, default_host)
1078
1081
1079 email_address = unicodedata.normalize('NFKD', email_address)\
1082 email_address = unicodedata.normalize('NFKD', email_address)\
1080 .encode('ascii', 'ignore')
1083 .encode('ascii', 'ignore')
1081 return email_address
1084 return email_address
1082
1085
1083 def get_initials(self):
1086 def get_initials(self):
1084 """
1087 """
1085 Returns 2 letter initials calculated based on the input.
1088 Returns 2 letter initials calculated based on the input.
1086 The algorithm picks first given email address, and takes first letter
1089 The algorithm picks first given email address, and takes first letter
1087 of part before @, and then the first letter of server name. In case
1090 of part before @, and then the first letter of server name. In case
1088 the part before @ is in a format of `somestring.somestring2` it replaces
1091 the part before @ is in a format of `somestring.somestring2` it replaces
1089 the server letter with first letter of somestring2
1092 the server letter with first letter of somestring2
1090
1093
1091 In case function was initialized with both first and lastname, this
1094 In case function was initialized with both first and lastname, this
1092 overrides the extraction from email by first letter of the first and
1095 overrides the extraction from email by first letter of the first and
1093 last name. We add special logic to that functionality, In case Full name
1096 last name. We add special logic to that functionality, In case Full name
1094 is compound, like Guido Von Rossum, we use last part of the last name
1097 is compound, like Guido Von Rossum, we use last part of the last name
1095 (Von Rossum) picking `R`.
1098 (Von Rossum) picking `R`.
1096
1099
1097 Function also normalizes the non-ascii characters to they ascii
1100 Function also normalizes the non-ascii characters to they ascii
1098 representation, eg Δ„ => A
1101 representation, eg Δ„ => A
1099 """
1102 """
1100 import unicodedata
1103 import unicodedata
1101 # replace non-ascii to ascii
1104 # replace non-ascii to ascii
1102 first_name = unicodedata.normalize(
1105 first_name = unicodedata.normalize(
1103 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1106 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1104 last_name = unicodedata.normalize(
1107 last_name = unicodedata.normalize(
1105 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1108 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1106
1109
1107 # do NFKD encoding, and also make sure email has proper format
1110 # do NFKD encoding, and also make sure email has proper format
1108 email_address = self.normalize_email(self.email_address)
1111 email_address = self.normalize_email(self.email_address)
1109
1112
1110 # first push the email initials
1113 # first push the email initials
1111 prefix, server = email_address.split('@', 1)
1114 prefix, server = email_address.split('@', 1)
1112
1115
1113 # check if prefix is maybe a 'first_name.last_name' syntax
1116 # check if prefix is maybe a 'first_name.last_name' syntax
1114 _dot_split = prefix.rsplit('.', 1)
1117 _dot_split = prefix.rsplit('.', 1)
1115 if len(_dot_split) == 2 and _dot_split[1]:
1118 if len(_dot_split) == 2 and _dot_split[1]:
1116 initials = [_dot_split[0][0], _dot_split[1][0]]
1119 initials = [_dot_split[0][0], _dot_split[1][0]]
1117 else:
1120 else:
1118 initials = [prefix[0], server[0]]
1121 initials = [prefix[0], server[0]]
1119
1122
1120 # then try to replace either first_name or last_name
1123 # then try to replace either first_name or last_name
1121 fn_letter = (first_name or " ")[0].strip()
1124 fn_letter = (first_name or " ")[0].strip()
1122 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1125 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1123
1126
1124 if fn_letter:
1127 if fn_letter:
1125 initials[0] = fn_letter
1128 initials[0] = fn_letter
1126
1129
1127 if ln_letter:
1130 if ln_letter:
1128 initials[1] = ln_letter
1131 initials[1] = ln_letter
1129
1132
1130 return ''.join(initials).upper()
1133 return ''.join(initials).upper()
1131
1134
1132 def get_img_data_by_type(self, font_family, img_type):
1135 def get_img_data_by_type(self, font_family, img_type):
1133 default_user = """
1136 default_user = """
1134 <svg xmlns="http://www.w3.org/2000/svg"
1137 <svg xmlns="http://www.w3.org/2000/svg"
1135 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1138 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1136 viewBox="-15 -10 439.165 429.164"
1139 viewBox="-15 -10 439.165 429.164"
1137
1140
1138 xml:space="preserve"
1141 xml:space="preserve"
1139 style="background:{background};" >
1142 style="background:{background};" >
1140
1143
1141 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1144 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1142 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1145 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1143 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1146 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1144 168.596,153.916,216.671,
1147 168.596,153.916,216.671,
1145 204.583,216.671z" fill="{text_color}"/>
1148 204.583,216.671z" fill="{text_color}"/>
1146 <path d="M407.164,374.717L360.88,
1149 <path d="M407.164,374.717L360.88,
1147 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1150 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1148 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1151 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1149 15.366-44.203,23.488-69.076,23.488c-24.877,
1152 15.366-44.203,23.488-69.076,23.488c-24.877,
1150 0-48.762-8.122-69.078-23.488
1153 0-48.762-8.122-69.078-23.488
1151 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1154 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1152 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1155 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1153 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1156 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1154 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1157 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1155 19.402-10.527 C409.699,390.129,
1158 19.402-10.527 C409.699,390.129,
1156 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1159 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1157 </svg>""".format(
1160 </svg>""".format(
1158 size=self.size,
1161 size=self.size,
1159 background='#979797', # @grey4
1162 background='#979797', # @grey4
1160 text_color=self.text_color,
1163 text_color=self.text_color,
1161 font_family=font_family)
1164 font_family=font_family)
1162
1165
1163 return {
1166 return {
1164 "default_user": default_user
1167 "default_user": default_user
1165 }[img_type]
1168 }[img_type]
1166
1169
1167 def get_img_data(self, svg_type=None):
1170 def get_img_data(self, svg_type=None):
1168 """
1171 """
1169 generates the svg metadata for image
1172 generates the svg metadata for image
1170 """
1173 """
1171 fonts = [
1174 fonts = [
1172 '-apple-system',
1175 '-apple-system',
1173 'BlinkMacSystemFont',
1176 'BlinkMacSystemFont',
1174 'Segoe UI',
1177 'Segoe UI',
1175 'Roboto',
1178 'Roboto',
1176 'Oxygen-Sans',
1179 'Oxygen-Sans',
1177 'Ubuntu',
1180 'Ubuntu',
1178 'Cantarell',
1181 'Cantarell',
1179 'Helvetica Neue',
1182 'Helvetica Neue',
1180 'sans-serif'
1183 'sans-serif'
1181 ]
1184 ]
1182 font_family = ','.join(fonts)
1185 font_family = ','.join(fonts)
1183 if svg_type:
1186 if svg_type:
1184 return self.get_img_data_by_type(font_family, svg_type)
1187 return self.get_img_data_by_type(font_family, svg_type)
1185
1188
1186 initials = self.get_initials()
1189 initials = self.get_initials()
1187 img_data = """
1190 img_data = """
1188 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1191 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1189 width="{size}" height="{size}"
1192 width="{size}" height="{size}"
1190 style="width: 100%; height: 100%; background-color: {background}"
1193 style="width: 100%; height: 100%; background-color: {background}"
1191 viewBox="0 0 {size} {size}">
1194 viewBox="0 0 {size} {size}">
1192 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1195 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1193 pointer-events="auto" fill="{text_color}"
1196 pointer-events="auto" fill="{text_color}"
1194 font-family="{font_family}"
1197 font-family="{font_family}"
1195 style="font-weight: 400; font-size: {f_size}px;">{text}
1198 style="font-weight: 400; font-size: {f_size}px;">{text}
1196 </text>
1199 </text>
1197 </svg>""".format(
1200 </svg>""".format(
1198 size=self.size,
1201 size=self.size,
1199 f_size=self.size/2.05, # scale the text inside the box nicely
1202 f_size=self.size/2.05, # scale the text inside the box nicely
1200 background=self.background,
1203 background=self.background,
1201 text_color=self.text_color,
1204 text_color=self.text_color,
1202 text=initials.upper(),
1205 text=initials.upper(),
1203 font_family=font_family)
1206 font_family=font_family)
1204
1207
1205 return img_data
1208 return img_data
1206
1209
1207 def generate_svg(self, svg_type=None):
1210 def generate_svg(self, svg_type=None):
1208 img_data = self.get_img_data(svg_type)
1211 img_data = self.get_img_data(svg_type)
1209 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1212 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1210
1213
1211
1214
1212 def initials_gravatar(email_address, first_name, last_name, size=30):
1215 def initials_gravatar(email_address, first_name, last_name, size=30):
1213 svg_type = None
1216 svg_type = None
1214 if email_address == User.DEFAULT_USER_EMAIL:
1217 if email_address == User.DEFAULT_USER_EMAIL:
1215 svg_type = 'default_user'
1218 svg_type = 'default_user'
1216 klass = InitialsGravatar(email_address, first_name, last_name, size)
1219 klass = InitialsGravatar(email_address, first_name, last_name, size)
1217 return klass.generate_svg(svg_type=svg_type)
1220 return klass.generate_svg(svg_type=svg_type)
1218
1221
1219
1222
1220 def gravatar_url(email_address, size=30, request=None):
1223 def gravatar_url(email_address, size=30, request=None):
1221 request = get_current_request()
1224 request = get_current_request()
1222 _use_gravatar = request.call_context.visual.use_gravatar
1225 _use_gravatar = request.call_context.visual.use_gravatar
1223 _gravatar_url = request.call_context.visual.gravatar_url
1226 _gravatar_url = request.call_context.visual.gravatar_url
1224
1227
1225 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1228 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1226
1229
1227 email_address = email_address or User.DEFAULT_USER_EMAIL
1230 email_address = email_address or User.DEFAULT_USER_EMAIL
1228 if isinstance(email_address, unicode):
1231 if isinstance(email_address, unicode):
1229 # hashlib crashes on unicode items
1232 # hashlib crashes on unicode items
1230 email_address = safe_str(email_address)
1233 email_address = safe_str(email_address)
1231
1234
1232 # empty email or default user
1235 # empty email or default user
1233 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1236 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1234 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1237 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1235
1238
1236 if _use_gravatar:
1239 if _use_gravatar:
1237 # TODO: Disuse pyramid thread locals. Think about another solution to
1240 # TODO: Disuse pyramid thread locals. Think about another solution to
1238 # get the host and schema here.
1241 # get the host and schema here.
1239 request = get_current_request()
1242 request = get_current_request()
1240 tmpl = safe_str(_gravatar_url)
1243 tmpl = safe_str(_gravatar_url)
1241 tmpl = tmpl.replace('{email}', email_address)\
1244 tmpl = tmpl.replace('{email}', email_address)\
1242 .replace('{md5email}', md5_safe(email_address.lower())) \
1245 .replace('{md5email}', md5_safe(email_address.lower())) \
1243 .replace('{netloc}', request.host)\
1246 .replace('{netloc}', request.host)\
1244 .replace('{scheme}', request.scheme)\
1247 .replace('{scheme}', request.scheme)\
1245 .replace('{size}', safe_str(size))
1248 .replace('{size}', safe_str(size))
1246 return tmpl
1249 return tmpl
1247 else:
1250 else:
1248 return initials_gravatar(email_address, '', '', size=size)
1251 return initials_gravatar(email_address, '', '', size=size)
1249
1252
1250
1253
1251 class Page(_Page):
1254 class Page(_Page):
1252 """
1255 """
1253 Custom pager to match rendering style with paginator
1256 Custom pager to match rendering style with paginator
1254 """
1257 """
1255
1258
1256 def _get_pos(self, cur_page, max_page, items):
1259 def _get_pos(self, cur_page, max_page, items):
1257 edge = (items / 2) + 1
1260 edge = (items / 2) + 1
1258 if (cur_page <= edge):
1261 if (cur_page <= edge):
1259 radius = max(items / 2, items - cur_page)
1262 radius = max(items / 2, items - cur_page)
1260 elif (max_page - cur_page) < edge:
1263 elif (max_page - cur_page) < edge:
1261 radius = (items - 1) - (max_page - cur_page)
1264 radius = (items - 1) - (max_page - cur_page)
1262 else:
1265 else:
1263 radius = items / 2
1266 radius = items / 2
1264
1267
1265 left = max(1, (cur_page - (radius)))
1268 left = max(1, (cur_page - (radius)))
1266 right = min(max_page, cur_page + (radius))
1269 right = min(max_page, cur_page + (radius))
1267 return left, cur_page, right
1270 return left, cur_page, right
1268
1271
1269 def _range(self, regexp_match):
1272 def _range(self, regexp_match):
1270 """
1273 """
1271 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1274 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1272
1275
1273 Arguments:
1276 Arguments:
1274
1277
1275 regexp_match
1278 regexp_match
1276 A "re" (regular expressions) match object containing the
1279 A "re" (regular expressions) match object containing the
1277 radius of linked pages around the current page in
1280 radius of linked pages around the current page in
1278 regexp_match.group(1) as a string
1281 regexp_match.group(1) as a string
1279
1282
1280 This function is supposed to be called as a callable in
1283 This function is supposed to be called as a callable in
1281 re.sub.
1284 re.sub.
1282
1285
1283 """
1286 """
1284 radius = int(regexp_match.group(1))
1287 radius = int(regexp_match.group(1))
1285
1288
1286 # Compute the first and last page number within the radius
1289 # Compute the first and last page number within the radius
1287 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1290 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1288 # -> leftmost_page = 5
1291 # -> leftmost_page = 5
1289 # -> rightmost_page = 9
1292 # -> rightmost_page = 9
1290 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1293 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1291 self.last_page,
1294 self.last_page,
1292 (radius * 2) + 1)
1295 (radius * 2) + 1)
1293 nav_items = []
1296 nav_items = []
1294
1297
1295 # Create a link to the first page (unless we are on the first page
1298 # Create a link to the first page (unless we are on the first page
1296 # or there would be no need to insert '..' spacers)
1299 # or there would be no need to insert '..' spacers)
1297 if self.page != self.first_page and self.first_page < leftmost_page:
1300 if self.page != self.first_page and self.first_page < leftmost_page:
1298 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1301 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1299
1302
1300 # Insert dots if there are pages between the first page
1303 # Insert dots if there are pages between the first page
1301 # and the currently displayed page range
1304 # and the currently displayed page range
1302 if leftmost_page - self.first_page > 1:
1305 if leftmost_page - self.first_page > 1:
1303 # Wrap in a SPAN tag if nolink_attr is set
1306 # Wrap in a SPAN tag if nolink_attr is set
1304 text = '..'
1307 text = '..'
1305 if self.dotdot_attr:
1308 if self.dotdot_attr:
1306 text = HTML.span(c=text, **self.dotdot_attr)
1309 text = HTML.span(c=text, **self.dotdot_attr)
1307 nav_items.append(text)
1310 nav_items.append(text)
1308
1311
1309 for thispage in xrange(leftmost_page, rightmost_page + 1):
1312 for thispage in xrange(leftmost_page, rightmost_page + 1):
1310 # Hilight the current page number and do not use a link
1313 # Hilight the current page number and do not use a link
1311 if thispage == self.page:
1314 if thispage == self.page:
1312 text = '%s' % (thispage,)
1315 text = '%s' % (thispage,)
1313 # Wrap in a SPAN tag if nolink_attr is set
1316 # Wrap in a SPAN tag if nolink_attr is set
1314 if self.curpage_attr:
1317 if self.curpage_attr:
1315 text = HTML.span(c=text, **self.curpage_attr)
1318 text = HTML.span(c=text, **self.curpage_attr)
1316 nav_items.append(text)
1319 nav_items.append(text)
1317 # Otherwise create just a link to that page
1320 # Otherwise create just a link to that page
1318 else:
1321 else:
1319 text = '%s' % (thispage,)
1322 text = '%s' % (thispage,)
1320 nav_items.append(self._pagerlink(thispage, text))
1323 nav_items.append(self._pagerlink(thispage, text))
1321
1324
1322 # Insert dots if there are pages between the displayed
1325 # Insert dots if there are pages between the displayed
1323 # page numbers and the end of the page range
1326 # page numbers and the end of the page range
1324 if self.last_page - rightmost_page > 1:
1327 if self.last_page - rightmost_page > 1:
1325 text = '..'
1328 text = '..'
1326 # Wrap in a SPAN tag if nolink_attr is set
1329 # Wrap in a SPAN tag if nolink_attr is set
1327 if self.dotdot_attr:
1330 if self.dotdot_attr:
1328 text = HTML.span(c=text, **self.dotdot_attr)
1331 text = HTML.span(c=text, **self.dotdot_attr)
1329 nav_items.append(text)
1332 nav_items.append(text)
1330
1333
1331 # Create a link to the very last page (unless we are on the last
1334 # Create a link to the very last page (unless we are on the last
1332 # page or there would be no need to insert '..' spacers)
1335 # page or there would be no need to insert '..' spacers)
1333 if self.page != self.last_page and rightmost_page < self.last_page:
1336 if self.page != self.last_page and rightmost_page < self.last_page:
1334 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1337 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1335
1338
1336 ## prerender links
1339 ## prerender links
1337 #_page_link = url.current()
1340 #_page_link = url.current()
1338 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1341 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1339 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1342 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1340 return self.separator.join(nav_items)
1343 return self.separator.join(nav_items)
1341
1344
1342 def pager(self, format='~2~', page_param='page', partial_param='partial',
1345 def pager(self, format='~2~', page_param='page', partial_param='partial',
1343 show_if_single_page=False, separator=' ', onclick=None,
1346 show_if_single_page=False, separator=' ', onclick=None,
1344 symbol_first='<<', symbol_last='>>',
1347 symbol_first='<<', symbol_last='>>',
1345 symbol_previous='<', symbol_next='>',
1348 symbol_previous='<', symbol_next='>',
1346 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1349 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1347 curpage_attr={'class': 'pager_curpage'},
1350 curpage_attr={'class': 'pager_curpage'},
1348 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1351 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1349
1352
1350 self.curpage_attr = curpage_attr
1353 self.curpage_attr = curpage_attr
1351 self.separator = separator
1354 self.separator = separator
1352 self.pager_kwargs = kwargs
1355 self.pager_kwargs = kwargs
1353 self.page_param = page_param
1356 self.page_param = page_param
1354 self.partial_param = partial_param
1357 self.partial_param = partial_param
1355 self.onclick = onclick
1358 self.onclick = onclick
1356 self.link_attr = link_attr
1359 self.link_attr = link_attr
1357 self.dotdot_attr = dotdot_attr
1360 self.dotdot_attr = dotdot_attr
1358
1361
1359 # Don't show navigator if there is no more than one page
1362 # Don't show navigator if there is no more than one page
1360 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1363 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1361 return ''
1364 return ''
1362
1365
1363 from string import Template
1366 from string import Template
1364 # Replace ~...~ in token format by range of pages
1367 # Replace ~...~ in token format by range of pages
1365 result = re.sub(r'~(\d+)~', self._range, format)
1368 result = re.sub(r'~(\d+)~', self._range, format)
1366
1369
1367 # Interpolate '%' variables
1370 # Interpolate '%' variables
1368 result = Template(result).safe_substitute({
1371 result = Template(result).safe_substitute({
1369 'first_page': self.first_page,
1372 'first_page': self.first_page,
1370 'last_page': self.last_page,
1373 'last_page': self.last_page,
1371 'page': self.page,
1374 'page': self.page,
1372 'page_count': self.page_count,
1375 'page_count': self.page_count,
1373 'items_per_page': self.items_per_page,
1376 'items_per_page': self.items_per_page,
1374 'first_item': self.first_item,
1377 'first_item': self.first_item,
1375 'last_item': self.last_item,
1378 'last_item': self.last_item,
1376 'item_count': self.item_count,
1379 'item_count': self.item_count,
1377 'link_first': self.page > self.first_page and \
1380 'link_first': self.page > self.first_page and \
1378 self._pagerlink(self.first_page, symbol_first) or '',
1381 self._pagerlink(self.first_page, symbol_first) or '',
1379 'link_last': self.page < self.last_page and \
1382 'link_last': self.page < self.last_page and \
1380 self._pagerlink(self.last_page, symbol_last) or '',
1383 self._pagerlink(self.last_page, symbol_last) or '',
1381 'link_previous': self.previous_page and \
1384 'link_previous': self.previous_page and \
1382 self._pagerlink(self.previous_page, symbol_previous) \
1385 self._pagerlink(self.previous_page, symbol_previous) \
1383 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1386 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1384 'link_next': self.next_page and \
1387 'link_next': self.next_page and \
1385 self._pagerlink(self.next_page, symbol_next) \
1388 self._pagerlink(self.next_page, symbol_next) \
1386 or HTML.span(symbol_next, class_="pg-next disabled")
1389 or HTML.span(symbol_next, class_="pg-next disabled")
1387 })
1390 })
1388
1391
1389 return literal(result)
1392 return literal(result)
1390
1393
1391
1394
1392 #==============================================================================
1395 #==============================================================================
1393 # REPO PAGER, PAGER FOR REPOSITORY
1396 # REPO PAGER, PAGER FOR REPOSITORY
1394 #==============================================================================
1397 #==============================================================================
1395 class RepoPage(Page):
1398 class RepoPage(Page):
1396
1399
1397 def __init__(self, collection, page=1, items_per_page=20,
1400 def __init__(self, collection, page=1, items_per_page=20,
1398 item_count=None, url=None, **kwargs):
1401 item_count=None, url=None, **kwargs):
1399
1402
1400 """Create a "RepoPage" instance. special pager for paging
1403 """Create a "RepoPage" instance. special pager for paging
1401 repository
1404 repository
1402 """
1405 """
1403 self._url_generator = url
1406 self._url_generator = url
1404
1407
1405 # Safe the kwargs class-wide so they can be used in the pager() method
1408 # Safe the kwargs class-wide so they can be used in the pager() method
1406 self.kwargs = kwargs
1409 self.kwargs = kwargs
1407
1410
1408 # Save a reference to the collection
1411 # Save a reference to the collection
1409 self.original_collection = collection
1412 self.original_collection = collection
1410
1413
1411 self.collection = collection
1414 self.collection = collection
1412
1415
1413 # The self.page is the number of the current page.
1416 # The self.page is the number of the current page.
1414 # The first page has the number 1!
1417 # The first page has the number 1!
1415 try:
1418 try:
1416 self.page = int(page) # make it int() if we get it as a string
1419 self.page = int(page) # make it int() if we get it as a string
1417 except (ValueError, TypeError):
1420 except (ValueError, TypeError):
1418 self.page = 1
1421 self.page = 1
1419
1422
1420 self.items_per_page = items_per_page
1423 self.items_per_page = items_per_page
1421
1424
1422 # Unless the user tells us how many items the collections has
1425 # Unless the user tells us how many items the collections has
1423 # we calculate that ourselves.
1426 # we calculate that ourselves.
1424 if item_count is not None:
1427 if item_count is not None:
1425 self.item_count = item_count
1428 self.item_count = item_count
1426 else:
1429 else:
1427 self.item_count = len(self.collection)
1430 self.item_count = len(self.collection)
1428
1431
1429 # Compute the number of the first and last available page
1432 # Compute the number of the first and last available page
1430 if self.item_count > 0:
1433 if self.item_count > 0:
1431 self.first_page = 1
1434 self.first_page = 1
1432 self.page_count = int(math.ceil(float(self.item_count) /
1435 self.page_count = int(math.ceil(float(self.item_count) /
1433 self.items_per_page))
1436 self.items_per_page))
1434 self.last_page = self.first_page + self.page_count - 1
1437 self.last_page = self.first_page + self.page_count - 1
1435
1438
1436 # Make sure that the requested page number is the range of
1439 # Make sure that the requested page number is the range of
1437 # valid pages
1440 # valid pages
1438 if self.page > self.last_page:
1441 if self.page > self.last_page:
1439 self.page = self.last_page
1442 self.page = self.last_page
1440 elif self.page < self.first_page:
1443 elif self.page < self.first_page:
1441 self.page = self.first_page
1444 self.page = self.first_page
1442
1445
1443 # Note: the number of items on this page can be less than
1446 # Note: the number of items on this page can be less than
1444 # items_per_page if the last page is not full
1447 # items_per_page if the last page is not full
1445 self.first_item = max(0, (self.item_count) - (self.page *
1448 self.first_item = max(0, (self.item_count) - (self.page *
1446 items_per_page))
1449 items_per_page))
1447 self.last_item = ((self.item_count - 1) - items_per_page *
1450 self.last_item = ((self.item_count - 1) - items_per_page *
1448 (self.page - 1))
1451 (self.page - 1))
1449
1452
1450 self.items = list(self.collection[self.first_item:self.last_item + 1])
1453 self.items = list(self.collection[self.first_item:self.last_item + 1])
1451
1454
1452 # Links to previous and next page
1455 # Links to previous and next page
1453 if self.page > self.first_page:
1456 if self.page > self.first_page:
1454 self.previous_page = self.page - 1
1457 self.previous_page = self.page - 1
1455 else:
1458 else:
1456 self.previous_page = None
1459 self.previous_page = None
1457
1460
1458 if self.page < self.last_page:
1461 if self.page < self.last_page:
1459 self.next_page = self.page + 1
1462 self.next_page = self.page + 1
1460 else:
1463 else:
1461 self.next_page = None
1464 self.next_page = None
1462
1465
1463 # No items available
1466 # No items available
1464 else:
1467 else:
1465 self.first_page = None
1468 self.first_page = None
1466 self.page_count = 0
1469 self.page_count = 0
1467 self.last_page = None
1470 self.last_page = None
1468 self.first_item = None
1471 self.first_item = None
1469 self.last_item = None
1472 self.last_item = None
1470 self.previous_page = None
1473 self.previous_page = None
1471 self.next_page = None
1474 self.next_page = None
1472 self.items = []
1475 self.items = []
1473
1476
1474 # This is a subclass of the 'list' type. Initialise the list now.
1477 # This is a subclass of the 'list' type. Initialise the list now.
1475 list.__init__(self, reversed(self.items))
1478 list.__init__(self, reversed(self.items))
1476
1479
1477
1480
1478 def breadcrumb_repo_link(repo):
1481 def breadcrumb_repo_link(repo):
1479 """
1482 """
1480 Makes a breadcrumbs path link to repo
1483 Makes a breadcrumbs path link to repo
1481
1484
1482 ex::
1485 ex::
1483 group >> subgroup >> repo
1486 group >> subgroup >> repo
1484
1487
1485 :param repo: a Repository instance
1488 :param repo: a Repository instance
1486 """
1489 """
1487
1490
1488 path = [
1491 path = [
1489 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1492 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1490 for group in repo.groups_with_parents
1493 for group in repo.groups_with_parents
1491 ] + [
1494 ] + [
1492 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1495 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1493 ]
1496 ]
1494
1497
1495 return literal(' &raquo; '.join(path))
1498 return literal(' &raquo; '.join(path))
1496
1499
1497
1500
1498 def breadcrumb_repo_group_link(repo_group):
1501 def breadcrumb_repo_group_link(repo_group):
1499 """
1502 """
1500 Makes a breadcrumbs path link to repo
1503 Makes a breadcrumbs path link to repo
1501
1504
1502 ex::
1505 ex::
1503 group >> subgroup
1506 group >> subgroup
1504
1507
1505 :param repo_group: a Repository Group instance
1508 :param repo_group: a Repository Group instance
1506 """
1509 """
1507
1510
1508 path = [
1511 path = [
1509 link_to(group.name,
1512 link_to(group.name,
1510 route_path('repo_group_home', repo_group_name=group.group_name))
1513 route_path('repo_group_home', repo_group_name=group.group_name))
1511 for group in repo_group.parents
1514 for group in repo_group.parents
1512 ] + [
1515 ] + [
1513 link_to(repo_group.name,
1516 link_to(repo_group.name,
1514 route_path('repo_group_home', repo_group_name=repo_group.group_name))
1517 route_path('repo_group_home', repo_group_name=repo_group.group_name))
1515 ]
1518 ]
1516
1519
1517 return literal(' &raquo; '.join(path))
1520 return literal(' &raquo; '.join(path))
1518
1521
1519
1522
1520 def format_byte_size_binary(file_size):
1523 def format_byte_size_binary(file_size):
1521 """
1524 """
1522 Formats file/folder sizes to standard.
1525 Formats file/folder sizes to standard.
1523 """
1526 """
1524 if file_size is None:
1527 if file_size is None:
1525 file_size = 0
1528 file_size = 0
1526
1529
1527 formatted_size = format_byte_size(file_size, binary=True)
1530 formatted_size = format_byte_size(file_size, binary=True)
1528 return formatted_size
1531 return formatted_size
1529
1532
1530
1533
1531 def urlify_text(text_, safe=True):
1534 def urlify_text(text_, safe=True):
1532 """
1535 """
1533 Extrac urls from text and make html links out of them
1536 Extrac urls from text and make html links out of them
1534
1537
1535 :param text_:
1538 :param text_:
1536 """
1539 """
1537
1540
1538 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1541 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1539 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1542 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1540
1543
1541 def url_func(match_obj):
1544 def url_func(match_obj):
1542 url_full = match_obj.groups()[0]
1545 url_full = match_obj.groups()[0]
1543 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1546 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1544 _newtext = url_pat.sub(url_func, text_)
1547 _newtext = url_pat.sub(url_func, text_)
1545 if safe:
1548 if safe:
1546 return literal(_newtext)
1549 return literal(_newtext)
1547 return _newtext
1550 return _newtext
1548
1551
1549
1552
1550 def urlify_commits(text_, repository):
1553 def urlify_commits(text_, repository):
1551 """
1554 """
1552 Extract commit ids from text and make link from them
1555 Extract commit ids from text and make link from them
1553
1556
1554 :param text_:
1557 :param text_:
1555 :param repository: repo name to build the URL with
1558 :param repository: repo name to build the URL with
1556 """
1559 """
1557
1560
1558 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1561 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1559
1562
1560 def url_func(match_obj):
1563 def url_func(match_obj):
1561 commit_id = match_obj.groups()[1]
1564 commit_id = match_obj.groups()[1]
1562 pref = match_obj.groups()[0]
1565 pref = match_obj.groups()[0]
1563 suf = match_obj.groups()[2]
1566 suf = match_obj.groups()[2]
1564
1567
1565 tmpl = (
1568 tmpl = (
1566 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1569 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1567 '%(commit_id)s</a>%(suf)s'
1570 '%(commit_id)s</a>%(suf)s'
1568 )
1571 )
1569 return tmpl % {
1572 return tmpl % {
1570 'pref': pref,
1573 'pref': pref,
1571 'cls': 'revision-link',
1574 'cls': 'revision-link',
1572 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1575 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1573 'commit_id': commit_id,
1576 'commit_id': commit_id,
1574 'suf': suf
1577 'suf': suf
1575 }
1578 }
1576
1579
1577 newtext = URL_PAT.sub(url_func, text_)
1580 newtext = URL_PAT.sub(url_func, text_)
1578
1581
1579 return newtext
1582 return newtext
1580
1583
1581
1584
1582 def _process_url_func(match_obj, repo_name, uid, entry,
1585 def _process_url_func(match_obj, repo_name, uid, entry,
1583 return_raw_data=False, link_format='html'):
1586 return_raw_data=False, link_format='html'):
1584 pref = ''
1587 pref = ''
1585 if match_obj.group().startswith(' '):
1588 if match_obj.group().startswith(' '):
1586 pref = ' '
1589 pref = ' '
1587
1590
1588 issue_id = ''.join(match_obj.groups())
1591 issue_id = ''.join(match_obj.groups())
1589
1592
1590 if link_format == 'html':
1593 if link_format == 'html':
1591 tmpl = (
1594 tmpl = (
1592 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1595 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1593 '%(issue-prefix)s%(id-repr)s'
1596 '%(issue-prefix)s%(id-repr)s'
1594 '</a>')
1597 '</a>')
1595 elif link_format == 'rst':
1598 elif link_format == 'rst':
1596 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1599 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1597 elif link_format == 'markdown':
1600 elif link_format == 'markdown':
1598 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1601 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1599 else:
1602 else:
1600 raise ValueError('Bad link_format:{}'.format(link_format))
1603 raise ValueError('Bad link_format:{}'.format(link_format))
1601
1604
1602 (repo_name_cleaned,
1605 (repo_name_cleaned,
1603 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1606 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1604
1607
1605 # variables replacement
1608 # variables replacement
1606 named_vars = {
1609 named_vars = {
1607 'id': issue_id,
1610 'id': issue_id,
1608 'repo': repo_name,
1611 'repo': repo_name,
1609 'repo_name': repo_name_cleaned,
1612 'repo_name': repo_name_cleaned,
1610 'group_name': parent_group_name
1613 'group_name': parent_group_name
1611 }
1614 }
1612 # named regex variables
1615 # named regex variables
1613 named_vars.update(match_obj.groupdict())
1616 named_vars.update(match_obj.groupdict())
1614 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1617 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1615
1618
1616 def quote_cleaner(input_str):
1619 def quote_cleaner(input_str):
1617 """Remove quotes as it's HTML"""
1620 """Remove quotes as it's HTML"""
1618 return input_str.replace('"', '')
1621 return input_str.replace('"', '')
1619
1622
1620 data = {
1623 data = {
1621 'pref': pref,
1624 'pref': pref,
1622 'cls': quote_cleaner('issue-tracker-link'),
1625 'cls': quote_cleaner('issue-tracker-link'),
1623 'url': quote_cleaner(_url),
1626 'url': quote_cleaner(_url),
1624 'id-repr': issue_id,
1627 'id-repr': issue_id,
1625 'issue-prefix': entry['pref'],
1628 'issue-prefix': entry['pref'],
1626 'serv': entry['url'],
1629 'serv': entry['url'],
1627 }
1630 }
1628 if return_raw_data:
1631 if return_raw_data:
1629 return {
1632 return {
1630 'id': issue_id,
1633 'id': issue_id,
1631 'url': _url
1634 'url': _url
1632 }
1635 }
1633 return tmpl % data
1636 return tmpl % data
1634
1637
1635
1638
1636 def get_active_pattern_entries(repo_name):
1639 def get_active_pattern_entries(repo_name):
1637 repo = None
1640 repo = None
1638 if repo_name:
1641 if repo_name:
1639 # Retrieving repo_name to avoid invalid repo_name to explode on
1642 # Retrieving repo_name to avoid invalid repo_name to explode on
1640 # IssueTrackerSettingsModel but still passing invalid name further down
1643 # IssueTrackerSettingsModel but still passing invalid name further down
1641 repo = Repository.get_by_repo_name(repo_name, cache=True)
1644 repo = Repository.get_by_repo_name(repo_name, cache=True)
1642
1645
1643 settings_model = IssueTrackerSettingsModel(repo=repo)
1646 settings_model = IssueTrackerSettingsModel(repo=repo)
1644 active_entries = settings_model.get_settings(cache=True)
1647 active_entries = settings_model.get_settings(cache=True)
1645 return active_entries
1648 return active_entries
1646
1649
1647
1650
1648 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1651 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1649
1652
1650 allowed_formats = ['html', 'rst', 'markdown']
1653 allowed_formats = ['html', 'rst', 'markdown']
1651 if link_format not in allowed_formats:
1654 if link_format not in allowed_formats:
1652 raise ValueError('Link format can be only one of:{} got {}'.format(
1655 raise ValueError('Link format can be only one of:{} got {}'.format(
1653 allowed_formats, link_format))
1656 allowed_formats, link_format))
1654
1657
1655 active_entries = active_entries or get_active_pattern_entries(repo_name)
1658 active_entries = active_entries or get_active_pattern_entries(repo_name)
1656 issues_data = []
1659 issues_data = []
1657 newtext = text_string
1660 newtext = text_string
1658
1661
1659 for uid, entry in active_entries.items():
1662 for uid, entry in active_entries.items():
1660 log.debug('found issue tracker entry with uid %s', uid)
1663 log.debug('found issue tracker entry with uid %s', uid)
1661
1664
1662 if not (entry['pat'] and entry['url']):
1665 if not (entry['pat'] and entry['url']):
1663 log.debug('skipping due to missing data')
1666 log.debug('skipping due to missing data')
1664 continue
1667 continue
1665
1668
1666 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1669 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1667 uid, entry['pat'], entry['url'], entry['pref'])
1670 uid, entry['pat'], entry['url'], entry['pref'])
1668
1671
1669 try:
1672 try:
1670 pattern = re.compile(r'%s' % entry['pat'])
1673 pattern = re.compile(r'%s' % entry['pat'])
1671 except re.error:
1674 except re.error:
1672 log.exception(
1675 log.exception(
1673 'issue tracker pattern: `%s` failed to compile',
1676 'issue tracker pattern: `%s` failed to compile',
1674 entry['pat'])
1677 entry['pat'])
1675 continue
1678 continue
1676
1679
1677 data_func = partial(
1680 data_func = partial(
1678 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1681 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1679 return_raw_data=True)
1682 return_raw_data=True)
1680
1683
1681 for match_obj in pattern.finditer(text_string):
1684 for match_obj in pattern.finditer(text_string):
1682 issues_data.append(data_func(match_obj))
1685 issues_data.append(data_func(match_obj))
1683
1686
1684 url_func = partial(
1687 url_func = partial(
1685 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1688 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1686 link_format=link_format)
1689 link_format=link_format)
1687
1690
1688 newtext = pattern.sub(url_func, newtext)
1691 newtext = pattern.sub(url_func, newtext)
1689 log.debug('processed prefix:uid `%s`', uid)
1692 log.debug('processed prefix:uid `%s`', uid)
1690
1693
1691 return newtext, issues_data
1694 return newtext, issues_data
1692
1695
1693
1696
1694 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1697 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1695 """
1698 """
1696 Parses given text message and makes proper links.
1699 Parses given text message and makes proper links.
1697 issues are linked to given issue-server, and rest is a commit link
1700 issues are linked to given issue-server, and rest is a commit link
1698
1701
1699 :param commit_text:
1702 :param commit_text:
1700 :param repository:
1703 :param repository:
1701 """
1704 """
1702 def escaper(string):
1705 def escaper(string):
1703 return string.replace('<', '&lt;').replace('>', '&gt;')
1706 return string.replace('<', '&lt;').replace('>', '&gt;')
1704
1707
1705 newtext = escaper(commit_text)
1708 newtext = escaper(commit_text)
1706
1709
1707 # extract http/https links and make them real urls
1710 # extract http/https links and make them real urls
1708 newtext = urlify_text(newtext, safe=False)
1711 newtext = urlify_text(newtext, safe=False)
1709
1712
1710 # urlify commits - extract commit ids and make link out of them, if we have
1713 # urlify commits - extract commit ids and make link out of them, if we have
1711 # the scope of repository present.
1714 # the scope of repository present.
1712 if repository:
1715 if repository:
1713 newtext = urlify_commits(newtext, repository)
1716 newtext = urlify_commits(newtext, repository)
1714
1717
1715 # process issue tracker patterns
1718 # process issue tracker patterns
1716 newtext, issues = process_patterns(newtext, repository or '',
1719 newtext, issues = process_patterns(newtext, repository or '',
1717 active_entries=active_pattern_entries)
1720 active_entries=active_pattern_entries)
1718
1721
1719 return literal(newtext)
1722 return literal(newtext)
1720
1723
1721
1724
1722 def render_binary(repo_name, file_obj):
1725 def render_binary(repo_name, file_obj):
1723 """
1726 """
1724 Choose how to render a binary file
1727 Choose how to render a binary file
1725 """
1728 """
1726
1729
1727 filename = file_obj.name
1730 filename = file_obj.name
1728
1731
1729 # images
1732 # images
1730 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1733 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1731 if fnmatch.fnmatch(filename, pat=ext):
1734 if fnmatch.fnmatch(filename, pat=ext):
1732 alt = escape(filename)
1735 alt = escape(filename)
1733 src = route_path(
1736 src = route_path(
1734 'repo_file_raw', repo_name=repo_name,
1737 'repo_file_raw', repo_name=repo_name,
1735 commit_id=file_obj.commit.raw_id,
1738 commit_id=file_obj.commit.raw_id,
1736 f_path=file_obj.path)
1739 f_path=file_obj.path)
1737 return literal(
1740 return literal(
1738 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1741 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1739
1742
1740
1743
1741 def renderer_from_filename(filename, exclude=None):
1744 def renderer_from_filename(filename, exclude=None):
1742 """
1745 """
1743 choose a renderer based on filename, this works only for text based files
1746 choose a renderer based on filename, this works only for text based files
1744 """
1747 """
1745
1748
1746 # ipython
1749 # ipython
1747 for ext in ['*.ipynb']:
1750 for ext in ['*.ipynb']:
1748 if fnmatch.fnmatch(filename, pat=ext):
1751 if fnmatch.fnmatch(filename, pat=ext):
1749 return 'jupyter'
1752 return 'jupyter'
1750
1753
1751 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1754 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1752 if is_markup:
1755 if is_markup:
1753 return is_markup
1756 return is_markup
1754 return None
1757 return None
1755
1758
1756
1759
1757 def render(source, renderer='rst', mentions=False, relative_urls=None,
1760 def render(source, renderer='rst', mentions=False, relative_urls=None,
1758 repo_name=None):
1761 repo_name=None):
1759
1762
1760 def maybe_convert_relative_links(html_source):
1763 def maybe_convert_relative_links(html_source):
1761 if relative_urls:
1764 if relative_urls:
1762 return relative_links(html_source, relative_urls)
1765 return relative_links(html_source, relative_urls)
1763 return html_source
1766 return html_source
1764
1767
1765 if renderer == 'plain':
1768 if renderer == 'plain':
1766 return literal(
1769 return literal(
1767 MarkupRenderer.plain(source, leading_newline=False))
1770 MarkupRenderer.plain(source, leading_newline=False))
1768
1771
1769 elif renderer == 'rst':
1772 elif renderer == 'rst':
1770 if repo_name:
1773 if repo_name:
1771 # process patterns on comments if we pass in repo name
1774 # process patterns on comments if we pass in repo name
1772 source, issues = process_patterns(
1775 source, issues = process_patterns(
1773 source, repo_name, link_format='rst')
1776 source, repo_name, link_format='rst')
1774
1777
1775 return literal(
1778 return literal(
1776 '<div class="rst-block">%s</div>' %
1779 '<div class="rst-block">%s</div>' %
1777 maybe_convert_relative_links(
1780 maybe_convert_relative_links(
1778 MarkupRenderer.rst(source, mentions=mentions)))
1781 MarkupRenderer.rst(source, mentions=mentions)))
1779
1782
1780 elif renderer == 'markdown':
1783 elif renderer == 'markdown':
1781 if repo_name:
1784 if repo_name:
1782 # process patterns on comments if we pass in repo name
1785 # process patterns on comments if we pass in repo name
1783 source, issues = process_patterns(
1786 source, issues = process_patterns(
1784 source, repo_name, link_format='markdown')
1787 source, repo_name, link_format='markdown')
1785
1788
1786 return literal(
1789 return literal(
1787 '<div class="markdown-block">%s</div>' %
1790 '<div class="markdown-block">%s</div>' %
1788 maybe_convert_relative_links(
1791 maybe_convert_relative_links(
1789 MarkupRenderer.markdown(source, flavored=True,
1792 MarkupRenderer.markdown(source, flavored=True,
1790 mentions=mentions)))
1793 mentions=mentions)))
1791
1794
1792 elif renderer == 'jupyter':
1795 elif renderer == 'jupyter':
1793 return literal(
1796 return literal(
1794 '<div class="ipynb">%s</div>' %
1797 '<div class="ipynb">%s</div>' %
1795 maybe_convert_relative_links(
1798 maybe_convert_relative_links(
1796 MarkupRenderer.jupyter(source)))
1799 MarkupRenderer.jupyter(source)))
1797
1800
1798 # None means just show the file-source
1801 # None means just show the file-source
1799 return None
1802 return None
1800
1803
1801
1804
1802 def commit_status(repo, commit_id):
1805 def commit_status(repo, commit_id):
1803 return ChangesetStatusModel().get_status(repo, commit_id)
1806 return ChangesetStatusModel().get_status(repo, commit_id)
1804
1807
1805
1808
1806 def commit_status_lbl(commit_status):
1809 def commit_status_lbl(commit_status):
1807 return dict(ChangesetStatus.STATUSES).get(commit_status)
1810 return dict(ChangesetStatus.STATUSES).get(commit_status)
1808
1811
1809
1812
1810 def commit_time(repo_name, commit_id):
1813 def commit_time(repo_name, commit_id):
1811 repo = Repository.get_by_repo_name(repo_name)
1814 repo = Repository.get_by_repo_name(repo_name)
1812 commit = repo.get_commit(commit_id=commit_id)
1815 commit = repo.get_commit(commit_id=commit_id)
1813 return commit.date
1816 return commit.date
1814
1817
1815
1818
1816 def get_permission_name(key):
1819 def get_permission_name(key):
1817 return dict(Permission.PERMS).get(key)
1820 return dict(Permission.PERMS).get(key)
1818
1821
1819
1822
1820 def journal_filter_help(request):
1823 def journal_filter_help(request):
1821 _ = request.translate
1824 _ = request.translate
1822 from rhodecode.lib.audit_logger import ACTIONS
1825 from rhodecode.lib.audit_logger import ACTIONS
1823 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1826 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1824
1827
1825 return _(
1828 return _(
1826 'Example filter terms:\n' +
1829 'Example filter terms:\n' +
1827 ' repository:vcs\n' +
1830 ' repository:vcs\n' +
1828 ' username:marcin\n' +
1831 ' username:marcin\n' +
1829 ' username:(NOT marcin)\n' +
1832 ' username:(NOT marcin)\n' +
1830 ' action:*push*\n' +
1833 ' action:*push*\n' +
1831 ' ip:127.0.0.1\n' +
1834 ' ip:127.0.0.1\n' +
1832 ' date:20120101\n' +
1835 ' date:20120101\n' +
1833 ' date:[20120101100000 TO 20120102]\n' +
1836 ' date:[20120101100000 TO 20120102]\n' +
1834 '\n' +
1837 '\n' +
1835 'Actions: {actions}\n' +
1838 'Actions: {actions}\n' +
1836 '\n' +
1839 '\n' +
1837 'Generate wildcards using \'*\' character:\n' +
1840 'Generate wildcards using \'*\' character:\n' +
1838 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1841 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1839 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1842 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1840 '\n' +
1843 '\n' +
1841 'Optional AND / OR operators in queries\n' +
1844 'Optional AND / OR operators in queries\n' +
1842 ' "repository:vcs OR repository:test"\n' +
1845 ' "repository:vcs OR repository:test"\n' +
1843 ' "username:test AND repository:test*"\n'
1846 ' "username:test AND repository:test*"\n'
1844 ).format(actions=actions)
1847 ).format(actions=actions)
1845
1848
1846
1849
1847 def not_mapped_error(repo_name):
1850 def not_mapped_error(repo_name):
1848 from rhodecode.translation import _
1851 from rhodecode.translation import _
1849 flash(_('%s repository is not mapped to db perhaps'
1852 flash(_('%s repository is not mapped to db perhaps'
1850 ' it was created or renamed from the filesystem'
1853 ' it was created or renamed from the filesystem'
1851 ' please run the application again'
1854 ' please run the application again'
1852 ' in order to rescan repositories') % repo_name, category='error')
1855 ' in order to rescan repositories') % repo_name, category='error')
1853
1856
1854
1857
1855 def ip_range(ip_addr):
1858 def ip_range(ip_addr):
1856 from rhodecode.model.db import UserIpMap
1859 from rhodecode.model.db import UserIpMap
1857 s, e = UserIpMap._get_ip_range(ip_addr)
1860 s, e = UserIpMap._get_ip_range(ip_addr)
1858 return '%s - %s' % (s, e)
1861 return '%s - %s' % (s, e)
1859
1862
1860
1863
1861 def form(url, method='post', needs_csrf_token=True, **attrs):
1864 def form(url, method='post', needs_csrf_token=True, **attrs):
1862 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1865 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1863 if method.lower() != 'get' and needs_csrf_token:
1866 if method.lower() != 'get' and needs_csrf_token:
1864 raise Exception(
1867 raise Exception(
1865 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1868 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1866 'CSRF token. If the endpoint does not require such token you can ' +
1869 'CSRF token. If the endpoint does not require such token you can ' +
1867 'explicitly set the parameter needs_csrf_token to false.')
1870 'explicitly set the parameter needs_csrf_token to false.')
1868
1871
1869 return wh_form(url, method=method, **attrs)
1872 return wh_form(url, method=method, **attrs)
1870
1873
1871
1874
1872 def secure_form(form_url, method="POST", multipart=False, **attrs):
1875 def secure_form(form_url, method="POST", multipart=False, **attrs):
1873 """Start a form tag that points the action to an url. This
1876 """Start a form tag that points the action to an url. This
1874 form tag will also include the hidden field containing
1877 form tag will also include the hidden field containing
1875 the auth token.
1878 the auth token.
1876
1879
1877 The url options should be given either as a string, or as a
1880 The url options should be given either as a string, or as a
1878 ``url()`` function. The method for the form defaults to POST.
1881 ``url()`` function. The method for the form defaults to POST.
1879
1882
1880 Options:
1883 Options:
1881
1884
1882 ``multipart``
1885 ``multipart``
1883 If set to True, the enctype is set to "multipart/form-data".
1886 If set to True, the enctype is set to "multipart/form-data".
1884 ``method``
1887 ``method``
1885 The method to use when submitting the form, usually either
1888 The method to use when submitting the form, usually either
1886 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1889 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1887 hidden input with name _method is added to simulate the verb
1890 hidden input with name _method is added to simulate the verb
1888 over POST.
1891 over POST.
1889
1892
1890 """
1893 """
1891 from webhelpers.pylonslib.secure_form import insecure_form
1894 from webhelpers.pylonslib.secure_form import insecure_form
1892
1895
1893 if 'request' in attrs:
1896 if 'request' in attrs:
1894 session = attrs['request'].session
1897 session = attrs['request'].session
1895 del attrs['request']
1898 del attrs['request']
1896 else:
1899 else:
1897 raise ValueError(
1900 raise ValueError(
1898 'Calling this form requires request= to be passed as argument')
1901 'Calling this form requires request= to be passed as argument')
1899
1902
1900 form = insecure_form(form_url, method, multipart, **attrs)
1903 form = insecure_form(form_url, method, multipart, **attrs)
1901 token = literal(
1904 token = literal(
1902 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1905 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1903 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1906 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1904
1907
1905 return literal("%s\n%s" % (form, token))
1908 return literal("%s\n%s" % (form, token))
1906
1909
1907
1910
1908 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1911 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1909 select_html = select(name, selected, options, **attrs)
1912 select_html = select(name, selected, options, **attrs)
1910 select2 = """
1913 select2 = """
1911 <script>
1914 <script>
1912 $(document).ready(function() {
1915 $(document).ready(function() {
1913 $('#%s').select2({
1916 $('#%s').select2({
1914 containerCssClass: 'drop-menu',
1917 containerCssClass: 'drop-menu',
1915 dropdownCssClass: 'drop-menu-dropdown',
1918 dropdownCssClass: 'drop-menu-dropdown',
1916 dropdownAutoWidth: true%s
1919 dropdownAutoWidth: true%s
1917 });
1920 });
1918 });
1921 });
1919 </script>
1922 </script>
1920 """
1923 """
1921 filter_option = """,
1924 filter_option = """,
1922 minimumResultsForSearch: -1
1925 minimumResultsForSearch: -1
1923 """
1926 """
1924 input_id = attrs.get('id') or name
1927 input_id = attrs.get('id') or name
1925 filter_enabled = "" if enable_filter else filter_option
1928 filter_enabled = "" if enable_filter else filter_option
1926 select_script = literal(select2 % (input_id, filter_enabled))
1929 select_script = literal(select2 % (input_id, filter_enabled))
1927
1930
1928 return literal(select_html+select_script)
1931 return literal(select_html+select_script)
1929
1932
1930
1933
1931 def get_visual_attr(tmpl_context_var, attr_name):
1934 def get_visual_attr(tmpl_context_var, attr_name):
1932 """
1935 """
1933 A safe way to get a variable from visual variable of template context
1936 A safe way to get a variable from visual variable of template context
1934
1937
1935 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1938 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1936 :param attr_name: name of the attribute we fetch from the c.visual
1939 :param attr_name: name of the attribute we fetch from the c.visual
1937 """
1940 """
1938 visual = getattr(tmpl_context_var, 'visual', None)
1941 visual = getattr(tmpl_context_var, 'visual', None)
1939 if not visual:
1942 if not visual:
1940 return
1943 return
1941 else:
1944 else:
1942 return getattr(visual, attr_name, None)
1945 return getattr(visual, attr_name, None)
1943
1946
1944
1947
1945 def get_last_path_part(file_node):
1948 def get_last_path_part(file_node):
1946 if not file_node.path:
1949 if not file_node.path:
1947 return u''
1950 return u''
1948
1951
1949 path = safe_unicode(file_node.path.split('/')[-1])
1952 path = safe_unicode(file_node.path.split('/')[-1])
1950 return u'../' + path
1953 return u'../' + path
1951
1954
1952
1955
1953 def route_url(*args, **kwargs):
1956 def route_url(*args, **kwargs):
1954 """
1957 """
1955 Wrapper around pyramids `route_url` (fully qualified url) function.
1958 Wrapper around pyramids `route_url` (fully qualified url) function.
1956 """
1959 """
1957 req = get_current_request()
1960 req = get_current_request()
1958 return req.route_url(*args, **kwargs)
1961 return req.route_url(*args, **kwargs)
1959
1962
1960
1963
1961 def route_path(*args, **kwargs):
1964 def route_path(*args, **kwargs):
1962 """
1965 """
1963 Wrapper around pyramids `route_path` function.
1966 Wrapper around pyramids `route_path` function.
1964 """
1967 """
1965 req = get_current_request()
1968 req = get_current_request()
1966 return req.route_path(*args, **kwargs)
1969 return req.route_path(*args, **kwargs)
1967
1970
1968
1971
1969 def route_path_or_none(*args, **kwargs):
1972 def route_path_or_none(*args, **kwargs):
1970 try:
1973 try:
1971 return route_path(*args, **kwargs)
1974 return route_path(*args, **kwargs)
1972 except KeyError:
1975 except KeyError:
1973 return None
1976 return None
1974
1977
1975
1978
1976 def current_route_path(request, **kw):
1979 def current_route_path(request, **kw):
1977 new_args = request.GET.mixed()
1980 new_args = request.GET.mixed()
1978 new_args.update(kw)
1981 new_args.update(kw)
1979 return request.current_route_path(_query=new_args)
1982 return request.current_route_path(_query=new_args)
1980
1983
1981
1984
1982 def api_call_example(method, args):
1985 def api_call_example(method, args):
1983 """
1986 """
1984 Generates an API call example via CURL
1987 Generates an API call example via CURL
1985 """
1988 """
1986 args_json = json.dumps(OrderedDict([
1989 args_json = json.dumps(OrderedDict([
1987 ('id', 1),
1990 ('id', 1),
1988 ('auth_token', 'SECRET'),
1991 ('auth_token', 'SECRET'),
1989 ('method', method),
1992 ('method', method),
1990 ('args', args)
1993 ('args', args)
1991 ]))
1994 ]))
1992 return literal(
1995 return literal(
1993 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
1996 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
1994 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1997 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1995 "and needs to be of `api calls` role."
1998 "and needs to be of `api calls` role."
1996 .format(
1999 .format(
1997 api_url=route_url('apiv2'),
2000 api_url=route_url('apiv2'),
1998 token_url=route_url('my_account_auth_tokens'),
2001 token_url=route_url('my_account_auth_tokens'),
1999 data=args_json))
2002 data=args_json))
2000
2003
2001
2004
2002 def notification_description(notification, request):
2005 def notification_description(notification, request):
2003 """
2006 """
2004 Generate notification human readable description based on notification type
2007 Generate notification human readable description based on notification type
2005 """
2008 """
2006 from rhodecode.model.notification import NotificationModel
2009 from rhodecode.model.notification import NotificationModel
2007 return NotificationModel().make_description(
2010 return NotificationModel().make_description(
2008 notification, translate=request.translate)
2011 notification, translate=request.translate)
2009
2012
2010
2013
2011 def go_import_header(request, db_repo=None):
2014 def go_import_header(request, db_repo=None):
2012 """
2015 """
2013 Creates a header for go-import functionality in Go Lang
2016 Creates a header for go-import functionality in Go Lang
2014 """
2017 """
2015
2018
2016 if not db_repo:
2019 if not db_repo:
2017 return
2020 return
2018 if 'go-get' not in request.GET:
2021 if 'go-get' not in request.GET:
2019 return
2022 return
2020
2023
2021 clone_url = db_repo.clone_url()
2024 clone_url = db_repo.clone_url()
2022 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2025 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2023 # we have a repo and go-get flag,
2026 # we have a repo and go-get flag,
2024 return literal('<meta name="go-import" content="{} {} {}">'.format(
2027 return literal('<meta name="go-import" content="{} {} {}">'.format(
2025 prefix, db_repo.repo_type, clone_url))
2028 prefix, db_repo.repo_type, clone_url))
2026
2029
2027
2030
2028 def reviewer_as_json(*args, **kwargs):
2031 def reviewer_as_json(*args, **kwargs):
2029 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2032 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2030 return _reviewer_as_json(*args, **kwargs)
2033 return _reviewer_as_json(*args, **kwargs)
2031
2034
2032
2035
2033 def get_repo_view_type(request):
2036 def get_repo_view_type(request):
2034 route_name = request.matched_route.name
2037 route_name = request.matched_route.name
2035 route_to_view_type = {
2038 route_to_view_type = {
2036 'repo_changelog': 'changelog',
2039 'repo_changelog': 'changelog',
2037 'repo_files': 'files',
2040 'repo_files': 'files',
2038 'repo_summary': 'summary',
2041 'repo_summary': 'summary',
2039 'repo_commit': 'commit'
2042 'repo_commit': 'commit'
2040 }
2043 }
2041
2044
2042 return route_to_view_type.get(route_name)
2045 return route_to_view_type.get(route_name)
@@ -1,310 +1,310 b''
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 /**
19 /**
20 * Search file list
20 * Search file list
21 */
21 */
22 // global reference to file-node filter
22 // global reference to file-node filter
23 var _NODEFILTER = {};
23 var _NODEFILTER = {};
24
24
25 var fileBrowserListeners = function(node_list_url, url_base){
25 var fileBrowserListeners = function(node_list_url, url_base){
26 var n_filter = $('#node_filter').get(0);
26 var n_filter = $('#node_filter').get(0);
27
27
28 _NODEFILTER.filterTimeout = null;
28 _NODEFILTER.filterTimeout = null;
29 var nodes = null;
29 var nodes = null;
30
30
31 _NODEFILTER.fetchNodes = function(callback) {
31 _NODEFILTER.fetchNodes = function(callback) {
32 $.ajax({url: node_list_url, headers: {'X-PARTIAL-XHR': true}})
32 $.ajax({url: node_list_url, headers: {'X-PARTIAL-XHR': true}})
33 .done(function(data){
33 .done(function(data){
34 nodes = data.nodes;
34 nodes = data.nodes;
35 if (callback) {
35 if (callback) {
36 callback();
36 callback();
37 }
37 }
38 })
38 })
39 .fail(function(data){
39 .fail(function(data){
40 console.log('failed to load');
40 console.log('failed to load');
41 });
41 });
42 };
42 };
43
43
44 _NODEFILTER.fetchNodesCallback = function() {
44 _NODEFILTER.fetchNodesCallback = function() {
45 $('#node_filter_box_loading').hide();
45 $('#node_filter_box_loading').hide();
46 $('#node_filter_box').removeClass('hidden').show();
46 $('#node_filter_box').removeClass('hidden').show();
47 n_filter.focus();
47 n_filter.focus();
48 if ($('#node_filter').hasClass('init')){
48 if ($('#node_filter').hasClass('init')){
49 n_filter.value = '';
49 n_filter.value = '';
50 $('#node_filter').removeClass('init');
50 $('#node_filter').removeClass('init');
51 }
51 }
52 };
52 };
53
53
54 _NODEFILTER.initFilter = function(){
54 _NODEFILTER.initFilter = function(){
55 $('#node_filter_box_loading').removeClass('hidden').show();
55 $('#node_filter_box_loading').removeClass('hidden').show();
56 $('#search_activate_id').hide();
56 $('#search_activate_id').hide();
57 $('#search_deactivate_id').removeClass('hidden').show();
57 $('#search_deactivate_id').removeClass('hidden').show();
58 $('#add_node_id').hide();
58 $('#add_node_id').hide();
59 _NODEFILTER.fetchNodes(_NODEFILTER.fetchNodesCallback);
59 _NODEFILTER.fetchNodes(_NODEFILTER.fetchNodesCallback);
60 };
60 };
61
61
62 _NODEFILTER.resetFilter = function(){
62 _NODEFILTER.resetFilter = function(){
63 $('#node_filter_box_loading').hide();
63 $('#node_filter_box_loading').hide();
64 $('#node_filter_box').hide();
64 $('#node_filter_box').hide();
65 $('#search_activate_id').show();
65 $('#search_activate_id').show();
66 $('#search_deactivate_id').hide();
66 $('#search_deactivate_id').hide();
67 $('#add_node_id').show();
67 $('#add_node_id').show();
68 $('#tbody').show();
68 $('#tbody').show();
69 $('#tbody_filtered').hide();
69 $('#tbody_filtered').hide();
70 $('#node_filter').val('');
70 $('#node_filter').val('');
71 };
71 };
72
72
73 _NODEFILTER.fuzzy_match = function(filepath, query) {
73 _NODEFILTER.fuzzy_match = function(filepath, query) {
74 var highlight = [];
74 var highlight = [];
75 var order = 0;
75 var order = 0;
76 for (var i = 0; i < query.length; i++) {
76 for (var i = 0; i < query.length; i++) {
77 var match_position = filepath.indexOf(query[i]);
77 var match_position = filepath.indexOf(query[i]);
78 if (match_position !== -1) {
78 if (match_position !== -1) {
79 var prev_match_position = highlight[highlight.length-1];
79 var prev_match_position = highlight[highlight.length-1];
80 if (prev_match_position === undefined) {
80 if (prev_match_position === undefined) {
81 highlight.push(match_position);
81 highlight.push(match_position);
82 } else {
82 } else {
83 var current_match_position = prev_match_position + match_position + 1;
83 var current_match_position = prev_match_position + match_position + 1;
84 highlight.push(current_match_position);
84 highlight.push(current_match_position);
85 order = order + current_match_position - prev_match_position;
85 order = order + current_match_position - prev_match_position;
86 }
86 }
87 filepath = filepath.substring(match_position+1);
87 filepath = filepath.substring(match_position+1);
88 } else {
88 } else {
89 return false;
89 return false;
90 }
90 }
91 }
91 }
92 return {'order': order,
92 return {'order': order,
93 'highlight': highlight};
93 'highlight': highlight};
94 };
94 };
95
95
96 _NODEFILTER.sortPredicate = function(a, b) {
96 _NODEFILTER.sortPredicate = function(a, b) {
97 if (a.order < b.order) return -1;
97 if (a.order < b.order) return -1;
98 if (a.order > b.order) return 1;
98 if (a.order > b.order) return 1;
99 if (a.filepath < b.filepath) return -1;
99 if (a.filepath < b.filepath) return -1;
100 if (a.filepath > b.filepath) return 1;
100 if (a.filepath > b.filepath) return 1;
101 return 0;
101 return 0;
102 };
102 };
103
103
104 _NODEFILTER.updateFilter = function(elem, e) {
104 _NODEFILTER.updateFilter = function(elem, e) {
105 return function(){
105 return function(){
106 // Reset timeout
106 // Reset timeout
107 _NODEFILTER.filterTimeout = null;
107 _NODEFILTER.filterTimeout = null;
108 var query = elem.value.toLowerCase();
108 var query = elem.value.toLowerCase();
109 var match = [];
109 var match = [];
110 var matches_max = 20;
110 var matches_max = 20;
111 if (query !== ""){
111 if (query !== ""){
112 var results = [];
112 var results = [];
113 for(var k=0;k<nodes.length;k++){
113 for(var k=0;k<nodes.length;k++){
114 var result = _NODEFILTER.fuzzy_match(
114 var result = _NODEFILTER.fuzzy_match(
115 nodes[k].name.toLowerCase(), query);
115 nodes[k].name.toLowerCase(), query);
116 if (result) {
116 if (result) {
117 result.type = nodes[k].type;
117 result.type = nodes[k].type;
118 result.filepath = nodes[k].name;
118 result.filepath = nodes[k].name;
119 results.push(result);
119 results.push(result);
120 }
120 }
121 }
121 }
122 results = results.sort(_NODEFILTER.sortPredicate);
122 results = results.sort(_NODEFILTER.sortPredicate);
123 var limit = matches_max;
123 var limit = matches_max;
124 if (results.length < matches_max) {
124 if (results.length < matches_max) {
125 limit = results.length;
125 limit = results.length;
126 }
126 }
127 for (var i=0; i<limit; i++){
127 for (var i=0; i<limit; i++){
128 if(query && results.length > 0){
128 if(query && results.length > 0){
129 var n = results[i].filepath;
129 var n = results[i].filepath;
130 var t = results[i].type;
130 var t = results[i].type;
131 var n_hl = n.split("");
131 var n_hl = n.split("");
132 var pos = results[i].highlight;
132 var pos = results[i].highlight;
133 for (var j = 0; j < pos.length; j++) {
133 for (var j = 0; j < pos.length; j++) {
134 n_hl[pos[j]] = "<em>" + n_hl[pos[j]] + "</em>";
134 n_hl[pos[j]] = "<em>" + n_hl[pos[j]] + "</em>";
135 }
135 }
136 n_hl = n_hl.join("");
136 n_hl = n_hl.join("");
137 var new_url = url_base.replace('__FPATH__',n);
137 var new_url = url_base.replace('__FPATH__',n);
138
138
139 var typeObj = {
139 var typeObj = {
140 dir: 'icon-directory browser-dir',
140 dir: 'icon-directory browser-dir',
141 file: 'icon-file-text browser-file'
141 file: 'icon-file-text browser-file'
142 };
142 };
143
143
144 var typeIcon = '<i class="{0}"></i>'.format(typeObj[t]);
144 var typeIcon = '<i class="{0}"></i>'.format(typeObj[t]);
145 match.push('<tr class="browser-result"><td><a class="pjax-link" href="{0}">{1}{2}</a></td><td colspan="5"></td></tr>'.format(new_url,typeIcon, n_hl));
145 match.push('<tr class="browser-result"><td><a class="match-link" href="{0}">{1}{2}</a></td><td colspan="5"></td></tr>'.format(new_url,typeIcon, n_hl));
146 }
146 }
147 }
147 }
148 if(results.length > limit){
148 if(results.length > limit){
149 var truncated_count = results.length - matches_max;
149 var truncated_count = results.length - matches_max;
150 if (truncated_count === 1) {
150 if (truncated_count === 1) {
151 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated result')));
151 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated result')));
152 } else {
152 } else {
153 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated results')));
153 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated results')));
154 }
154 }
155 }
155 }
156 }
156 }
157 if (query !== ""){
157 if (query !== ""){
158 $('#tbody').hide();
158 $('#tbody').hide();
159 $('#tbody_filtered').show();
159 $('#tbody_filtered').show();
160
160
161 if (match.length === 0){
161 if (match.length === 0){
162 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_gettext('No matching files')));
162 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_gettext('No matching files')));
163 }
163 }
164 $('#tbody_filtered').html(match.join(""));
164 $('#tbody_filtered').html(match.join(""));
165 }
165 }
166 else{
166 else{
167 $('#tbody').show();
167 $('#tbody').show();
168 $('#tbody_filtered').hide();
168 $('#tbody_filtered').hide();
169 }
169 }
170
170
171 };
171 };
172 };
172 };
173
173
174 var scrollDown = function(element){
174 var scrollDown = function(element){
175 var elementBottom = element.offset().top + $(element).outerHeight();
175 var elementBottom = element.offset().top + $(element).outerHeight();
176 var windowBottom = window.innerHeight + $(window).scrollTop();
176 var windowBottom = window.innerHeight + $(window).scrollTop();
177 if (elementBottom > windowBottom) {
177 if (elementBottom > windowBottom) {
178 var offset = elementBottom - window.innerHeight;
178 var offset = elementBottom - window.innerHeight;
179 $('html,body').scrollTop(offset);
179 $('html,body').scrollTop(offset);
180 return false;
180 return false;
181 }
181 }
182 return true;
182 return true;
183 };
183 };
184
184
185 var scrollUp = function(element){
185 var scrollUp = function(element){
186 if (element.offset().top < $(window).scrollTop()) {
186 if (element.offset().top < $(window).scrollTop()) {
187 $('html,body').scrollTop(element.offset().top);
187 $('html,body').scrollTop(element.offset().top);
188 return false;
188 return false;
189 }
189 }
190 return true;
190 return true;
191 };
191 };
192
192
193 $('#filter_activate').click(function() {
193 $('#filter_activate').click(function() {
194 _NODEFILTER.initFilter();
194 _NODEFILTER.initFilter();
195 });
195 });
196
196
197 $('#filter_deactivate').click(function() {
197 $('#filter_deactivate').click(function() {
198 _NODEFILTER.resetFilter();
198 _NODEFILTER.resetFilter();
199 });
199 });
200
200
201 $(n_filter).click(function() {
201 $(n_filter).click(function() {
202 if ($('#node_filter').hasClass('init')){
202 if ($('#node_filter').hasClass('init')){
203 n_filter.value = '';
203 n_filter.value = '';
204 $('#node_filter').removeClass('init');
204 $('#node_filter').removeClass('init');
205 }
205 }
206 });
206 });
207
207
208 $(n_filter).keydown(function(e) {
208 $(n_filter).keydown(function(e) {
209 if (e.keyCode === 40){ // Down
209 if (e.keyCode === 40){ // Down
210 if ($('.browser-highlight').length === 0){
210 if ($('.browser-highlight').length === 0){
211 $('.browser-result').first().addClass('browser-highlight');
211 $('.browser-result').first().addClass('browser-highlight');
212 } else {
212 } else {
213 var next = $('.browser-highlight').next();
213 var next = $('.browser-highlight').next();
214 if (next.length !== 0) {
214 if (next.length !== 0) {
215 $('.browser-highlight').removeClass('browser-highlight');
215 $('.browser-highlight').removeClass('browser-highlight');
216 next.addClass('browser-highlight');
216 next.addClass('browser-highlight');
217 }
217 }
218 }
218 }
219 scrollDown($('.browser-highlight'));
219 scrollDown($('.browser-highlight'));
220 }
220 }
221 if (e.keyCode === 38){ // Up
221 if (e.keyCode === 38){ // Up
222 e.preventDefault();
222 e.preventDefault();
223 if ($('.browser-highlight').length !== 0){
223 if ($('.browser-highlight').length !== 0){
224 var next = $('.browser-highlight').prev();
224 var next = $('.browser-highlight').prev();
225 if (next.length !== 0) {
225 if (next.length !== 0) {
226 $('.browser-highlight').removeClass('browser-highlight');
226 $('.browser-highlight').removeClass('browser-highlight');
227 next.addClass('browser-highlight');
227 next.addClass('browser-highlight');
228 }
228 }
229 }
229 }
230 scrollUp($('.browser-highlight'));
230 scrollUp($('.browser-highlight'));
231 }
231 }
232 if (e.keyCode === 13){ // Enter
232 if (e.keyCode === 13){ // Enter
233 if ($('.browser-highlight').length !== 0){
233 if ($('.browser-highlight').length !== 0){
234 var url = $('.browser-highlight').find('.pjax-link').attr('href');
234 var url = $('.browser-highlight').find('.match-link').attr('href');
235 $.pjax({url: url, container: '#pjax-container', timeout: pjaxTimeout});
235 window.location = url;
236 }
236 }
237 }
237 }
238 if (e.keyCode === 27){ // Esc
238 if (e.keyCode === 27){ // Esc
239 _NODEFILTER.resetFilter();
239 _NODEFILTER.resetFilter();
240 $('html,body').scrollTop(0);
240 $('html,body').scrollTop(0);
241 }
241 }
242 });
242 });
243 var capture_keys = [40, 38, 39, 37, 13, 27];
243 var capture_keys = [40, 38, 39, 37, 13, 27];
244 $(n_filter).keyup(function(e) {
244 $(n_filter).keyup(function(e) {
245 if ($.inArray(e.keyCode, capture_keys) === -1){
245 if ($.inArray(e.keyCode, capture_keys) === -1){
246 clearTimeout(_NODEFILTER.filterTimeout);
246 clearTimeout(_NODEFILTER.filterTimeout);
247 _NODEFILTER.filterTimeout = setTimeout(_NODEFILTER.updateFilter(n_filter, e),200);
247 _NODEFILTER.filterTimeout = setTimeout(_NODEFILTER.updateFilter(n_filter, e),200);
248 }
248 }
249 });
249 });
250 };
250 };
251
251
252 var getIdentNode = function(n){
252 var getIdentNode = function(n){
253 // iterate through nodes until matched interesting node
253 // iterate through nodes until matched interesting node
254 if (typeof n === 'undefined'){
254 if (typeof n === 'undefined'){
255 return -1;
255 return -1;
256 }
256 }
257 if(typeof n.id !== "undefined" && n.id.match('L[0-9]+')){
257 if(typeof n.id !== "undefined" && n.id.match('L[0-9]+')){
258 return n;
258 return n;
259 }
259 }
260 else{
260 else{
261 return getIdentNode(n.parentNode);
261 return getIdentNode(n.parentNode);
262 }
262 }
263 };
263 };
264
264
265 var getSelectionLink = function(e) {
265 var getSelectionLink = function(e) {
266 // get selection from start/to nodes
266 // get selection from start/to nodes
267 if (typeof window.getSelection !== "undefined") {
267 if (typeof window.getSelection !== "undefined") {
268 s = window.getSelection();
268 s = window.getSelection();
269
269
270 from = getIdentNode(s.anchorNode);
270 from = getIdentNode(s.anchorNode);
271 till = getIdentNode(s.focusNode);
271 till = getIdentNode(s.focusNode);
272
272
273 f_int = parseInt(from.id.replace('L',''));
273 f_int = parseInt(from.id.replace('L',''));
274 t_int = parseInt(till.id.replace('L',''));
274 t_int = parseInt(till.id.replace('L',''));
275
275
276 if (f_int > t_int){
276 if (f_int > t_int){
277 // highlight from bottom
277 // highlight from bottom
278 offset = -35;
278 offset = -35;
279 ranges = [t_int,f_int];
279 ranges = [t_int,f_int];
280 }
280 }
281 else{
281 else{
282 // highligth from top
282 // highligth from top
283 offset = 35;
283 offset = 35;
284 ranges = [f_int,t_int];
284 ranges = [f_int,t_int];
285 }
285 }
286 // if we select more than 2 lines
286 // if we select more than 2 lines
287 if (ranges[0] !== ranges[1]){
287 if (ranges[0] !== ranges[1]){
288 if($('#linktt').length === 0){
288 if($('#linktt').length === 0){
289 hl_div = document.createElement('div');
289 hl_div = document.createElement('div');
290 hl_div.id = 'linktt';
290 hl_div.id = 'linktt';
291 }
291 }
292 hl_div.innerHTML = '';
292 hl_div.innerHTML = '';
293
293
294 anchor = '#L'+ranges[0]+'-'+ranges[1];
294 anchor = '#L'+ranges[0]+'-'+ranges[1];
295 var link = document.createElement('a');
295 var link = document.createElement('a');
296 link.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
296 link.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
297 link.innerHTML = _gettext('Selection link');
297 link.innerHTML = _gettext('Selection link');
298 hl_div.appendChild(link);
298 hl_div.appendChild(link);
299 $('#codeblock').append(hl_div);
299 $('#codeblock').append(hl_div);
300
300
301 var xy = $(till).offset();
301 var xy = $(till).offset();
302 $('#linktt').addClass('hl-tip-box tip-box');
302 $('#linktt').addClass('hl-tip-box tip-box');
303 $('#linktt').offset({top: xy.top + offset, left: xy.left});
303 $('#linktt').offset({top: xy.top + offset, left: xy.left});
304 $('#linktt').css('visibility','visible');
304 $('#linktt').css('visibility','visible');
305 }
305 }
306 else{
306 else{
307 $('#linktt').css('visibility','hidden');
307 $('#linktt').css('visibility','hidden');
308 }
308 }
309 }
309 }
310 };
310 };
@@ -1,44 +1,44 b''
1 <%def name="refs(commit)">
1 <%def name="refs(commit)">
2 ## Build a cache of refs for selector
2 ## Build a cache of refs for selector
3 <script>
3 <script>
4 fileTreeRefs = {
4 fileTreeRefs = {
5
5
6 }
6 }
7 </script>
7 </script>
8
8
9 %if commit.merge:
9 %if commit.merge:
10 <span class="mergetag tag">
10 <span class="mergetag tag">
11 <i class="icon-merge">${_('merge')}</i>
11 <i class="icon-merge">${_('merge')}</i>
12 </span>
12 </span>
13 %endif
13 %endif
14
14
15 %if h.is_hg(c.rhodecode_repo):
15 %if h.is_hg(c.rhodecode_repo):
16 %for book in commit.bookmarks:
16 %for book in commit.bookmarks:
17 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
17 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
18 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id,_query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
18 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id,_query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
19 </span>
19 </span>
20 <script>
20 <script>
21 fileTreeRefs["${book}"] = {raw_id: "${commit.raw_id}", type:"book"};
21 fileTreeRefs["${book}"] = {raw_id: "${commit.raw_id}", type:"book", text: "${book}"};
22 </script>
22 </script>
23 %endfor
23 %endfor
24 %endif
24 %endif
25
25
26 %for tag in commit.tags:
26 %for tag in commit.tags:
27 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
27 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
28 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id,_query=dict(at=tag))}"><i class="icon-tag"></i>${tag}</a>
28 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id,_query=dict(at=tag))}"><i class="icon-tag"></i>${tag}</a>
29 </span>
29 </span>
30 <script>
30 <script>
31 fileTreeRefs["${tag}"] = {raw_id: "${commit.raw_id}", type:"tag"};
31 fileTreeRefs["${tag}"] = {raw_id: "${commit.raw_id}", type:"tag", text: "${tag}"};
32 </script>
32 </script>
33 %endfor
33 %endfor
34
34
35 %if commit.branch:
35 %if commit.branch:
36 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
36 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % commit.branch)}">
37 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id,_query=dict(at=commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
37 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=commit.raw_id,_query=dict(at=commit.branch))}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
38 </span>
38 </span>
39 <script>
39 <script>
40 fileTreeRefs["${commit.branch}"] = {raw_id: "${commit.raw_id}", type:"branch"};
40 fileTreeRefs["${commit.branch}"] = {raw_id: "${commit.raw_id}", type:"branch", text: "${commit.branch}"};
41 </script>
41 </script>
42 %endif
42 %endif
43
43
44 </%def>
44 </%def>
@@ -1,386 +1,350 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title(*args)">
3 <%def name="title(*args)">
4 ${_('%s Files') % c.repo_name}
4 ${_('{} Files').format(c.repo_name)}
5 %if hasattr(c,'file'):
5 %if hasattr(c,'file'):
6 &middot; ${h.safe_unicode(c.file.path) or '\\'}
6 &middot; ${(h.safe_unicode(c.file.path) or '\\')}
7 %endif
7 %endif
8
8
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Files')}
15 ${_('Files')}
16 %if c.file:
16 %if c.file:
17 @ ${h.show_id(c.commit)}
17 @ ${h.show_id(c.commit)}
18 %endif
18 %endif
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_nav()">
21 <%def name="menu_bar_nav()">
22 ${self.menu_items(active='repositories')}
22 ${self.menu_items(active='repositories')}
23 </%def>
23 </%def>
24
24
25 <%def name="menu_bar_subnav()">
25 <%def name="menu_bar_subnav()">
26 ${self.repo_menu(active='files')}
26 ${self.repo_menu(active='files')}
27 </%def>
27 </%def>
28
28
29 <%def name="main()">
29 <%def name="main()">
30 <div id="pjax-container">
30 <div>
31 <div id="files_data">
31 <div id="files_data">
32 <%include file='files_pjax.mako'/>
32 <%include file='files_pjax.mako'/>
33 </div>
33 </div>
34 </div>
34 </div>
35 <script>
35 <script>
36 var pjaxTimeout = 5000;
37
36
38 var curState = {
37 var metadataRequest = null;
39 commit_id: "${c.commit.raw_id}"
38 var fileSourcePage = ${c.file_source_page};
40 };
39 var atRef = '${request.GET.get('at', '')}';
41
40
42 var getState = function(context) {
41 var getState = function(context) {
43 var url = $(location).attr('href');
42 var url = $(location).attr('href');
44 var _base_url = '${h.route_path("repo_files",repo_name=c.repo_name,commit_id='',f_path='')}';
43 var _base_url = '${h.route_path("repo_files",repo_name=c.repo_name,commit_id='',f_path='')}';
45 var _annotate_url = '${h.route_path("repo_files:annotated",repo_name=c.repo_name,commit_id='',f_path='')}';
44 var _annotate_url = '${h.route_path("repo_files:annotated",repo_name=c.repo_name,commit_id='',f_path='')}';
46 _base_url = _base_url.replace('//', '/');
45 _base_url = _base_url.replace('//', '/');
47 _annotate_url = _annotate_url.replace('//', '/');
46 _annotate_url = _annotate_url.replace('//', '/');
48
47
49 //extract f_path from url.
48 //extract f_path from url.
50 var parts = url.split(_base_url);
49 var parts = url.split(_base_url);
51 if (parts.length != 2) {
50 if (parts.length != 2) {
52 parts = url.split(_annotate_url);
51 parts = url.split(_annotate_url);
53 if (parts.length != 2) {
52 if (parts.length != 2) {
54 var rev = "tip";
53 var rev = "tip";
55 var f_path = "";
54 var f_path = "";
56 } else {
55 } else {
57 var parts2 = parts[1].split('/');
56 var parts2 = parts[1].split('/');
58 var rev = parts2.shift(); // pop the first element which is the revision
57 var rev = parts2.shift(); // pop the first element which is the revision
59 var f_path = parts2.join('/');
58 var f_path = parts2.join('/');
60 }
59 }
61
60
62 } else {
61 } else {
63 var parts2 = parts[1].split('/');
62 var parts2 = parts[1].split('/');
64 var rev = parts2.shift(); // pop the first element which is the revision
63 var rev = parts2.shift(); // pop the first element which is the revision
65 var f_path = parts2.join('/');
64 var f_path = parts2.join('/');
66 }
65 }
67
66
67 var url_params = {
68 repo_name: templateContext.repo_name,
69 commit_id: rev,
70 f_path:'__FPATH__'
71 };
72 if (atRef !== '') {
73 url_params['at'] = atRef
74 }
75
76 var _url_base = pyroutes.url('repo_files', url_params);
68 var _node_list_url = pyroutes.url('repo_files_nodelist',
77 var _node_list_url = pyroutes.url('repo_files_nodelist',
69 {repo_name: templateContext.repo_name,
78 {repo_name: templateContext.repo_name,
70 commit_id: rev, f_path: f_path});
79 commit_id: rev, f_path: f_path});
71 var _url_base = pyroutes.url('repo_files',
80
72 {repo_name: templateContext.repo_name,
73 commit_id: rev, f_path:'__FPATH__'});
74 return {
81 return {
75 url: url,
82 url: url,
76 f_path: f_path,
83 f_path: f_path,
77 rev: rev,
84 rev: rev,
78 commit_id: curState.commit_id,
85 commit_id: "${c.commit.raw_id}",
79 node_list_url: _node_list_url,
86 node_list_url: _node_list_url,
80 url_base: _url_base
87 url_base: _url_base
81 };
88 };
82 };
89 };
83
90
84 var metadataRequest = null;
85 var getFilesMetadata = function() {
91 var getFilesMetadata = function() {
86 if (metadataRequest && metadataRequest.readyState != 4) {
92 if (metadataRequest && metadataRequest.readyState != 4) {
87 metadataRequest.abort();
93 metadataRequest.abort();
88 }
94 }
89 if (fileSourcePage) {
95 if (fileSourcePage) {
90 return false;
96 return false;
91 }
97 }
92
98
93 if ($('#file-tree-wrapper').hasClass('full-load')) {
99 if ($('#file-tree-wrapper').hasClass('full-load')) {
94 // in case our HTML wrapper has full-load class we don't
100 // in case our HTML wrapper has full-load class we don't
95 // trigger the async load of metadata
101 // trigger the async load of metadata
96 return false;
102 return false;
97 }
103 }
98
104
99 var state = getState('metadata');
105 var state = getState('metadata');
100 var url_data = {
106 var url_data = {
101 'repo_name': templateContext.repo_name,
107 'repo_name': templateContext.repo_name,
102 'commit_id': state.commit_id,
108 'commit_id': state.commit_id,
103 'f_path': state.f_path
109 'f_path': state.f_path
104 };
110 };
105
111
106 var url = pyroutes.url('repo_nodetree_full', url_data);
112 var url = pyroutes.url('repo_nodetree_full', url_data);
107
113
108 metadataRequest = $.ajax({url: url});
114 metadataRequest = $.ajax({url: url});
109
115
110 metadataRequest.done(function(data) {
116 metadataRequest.done(function(data) {
111 $('#file-tree').html(data);
117 $('#file-tree').html(data);
112 timeagoActivate();
118 timeagoActivate();
113 });
119 });
114 metadataRequest.fail(function (data, textStatus, errorThrown) {
120 metadataRequest.fail(function (data, textStatus, errorThrown) {
115 console.log(data);
121 console.log(data);
116 if (data.status != 0) {
122 if (data.status != 0) {
117 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
123 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
118 }
124 }
119 });
125 });
120 };
126 };
121
127
122 var callbacks = function() {
128 var callbacks = function() {
123 var state = getState('callbacks');
124 timeagoActivate();
129 timeagoActivate();
125
130
126 if ($('#trimmed_message_box').height() < 50) {
131 if ($('#trimmed_message_box').height() < 50) {
127 $('#message_expand').hide();
132 $('#message_expand').hide();
128 }
133 }
129
134
130 $('#message_expand').on('click', function(e) {
135 $('#message_expand').on('click', function(e) {
131 $('#trimmed_message_box').css('max-height', 'none');
136 $('#trimmed_message_box').css('max-height', 'none');
132 $(this).hide();
137 $(this).hide();
133 });
138 });
134
139
140 var state = getState('callbacks');
141
135 // VIEW FOR FILE SOURCE
142 // VIEW FOR FILE SOURCE
136 if (fileSourcePage) {
143 if (fileSourcePage) {
137 // variants for with source code, not tree view
144 // variants for with source code, not tree view
138
145
139 // select code link event
146 // select code link event
140 $("#hlcode").mouseup(getSelectionLink);
147 $("#hlcode").mouseup(getSelectionLink);
141
148
142 // file history select2 used for history, and switch to
149 // file history select2 used for history, and switch to
143 var initialCommitData = {
150 var initialCommitData = {
144 id: null,
151 id: null,
145 text: '${_("Pick Commit")}',
152 text: '${_("Pick Commit")}',
146 type: 'sha',
153 type: 'sha',
147 raw_id: null,
154 raw_id: null,
148 files_url: null
155 files_url: null
149 };
156 };
157
150 select2FileHistorySwitcher('#diff1', initialCommitData, state);
158 select2FileHistorySwitcher('#diff1', initialCommitData, state);
151
159
152 // show at, diff to actions handlers
160 // show at, diff to actions handlers
153 $('#diff1').on('change', function(e) {
161 $('#diff1').on('change', function(e) {
154 $('#diff_to_commit').removeClass('disabled').removeAttr("disabled");
162 $('#diff_to_commit').removeClass('disabled').removeAttr("disabled");
155 $('#diff_to_commit').val(_gettext('Diff to Commit ') + e.val.truncateAfter(8, '...'));
163 $('#diff_to_commit').val(_gettext('Diff to Commit ') + e.val.truncateAfter(8, '...'));
156
164
157 $('#show_at_commit').removeClass('disabled').removeAttr("disabled");
165 $('#show_at_commit').removeClass('disabled').removeAttr("disabled");
158 $('#show_at_commit').val(_gettext('Show at Commit ') + e.val.truncateAfter(8, '...'));
166 $('#show_at_commit').val(_gettext('Show at Commit ') + e.val.truncateAfter(8, '...'));
159 });
167 });
160
168
161 $('#diff_to_commit').on('click', function(e) {
169 $('#diff_to_commit').on('click', function(e) {
162 var diff1 = $('#diff1').val();
170 var diff1 = $('#diff1').val();
163 var diff2 = $('#diff2').val();
171 var diff2 = $('#diff2').val();
164
172
165 var url_data = {
173 var url_data = {
166 repo_name: templateContext.repo_name,
174 repo_name: templateContext.repo_name,
167 source_ref: diff1,
175 source_ref: diff1,
168 source_ref_type: 'rev',
176 source_ref_type: 'rev',
169 target_ref: diff2,
177 target_ref: diff2,
170 target_ref_type: 'rev',
178 target_ref_type: 'rev',
171 merge: 1,
179 merge: 1,
172 f_path: state.f_path
180 f_path: state.f_path
173 };
181 };
174 window.location = pyroutes.url('repo_compare', url_data);
182 window.location = pyroutes.url('repo_compare', url_data);
175 });
183 });
176
184
177 $('#show_at_commit').on('click', function(e) {
185 $('#show_at_commit').on('click', function(e) {
178 var diff1 = $('#diff1').val();
186 var diff1 = $('#diff1').val();
179
187
180 var annotate = $('#annotate').val();
188 var annotate = $('#annotate').val();
181 if (annotate === "True") {
189 if (annotate === "True") {
182 var url = pyroutes.url('repo_files:annotated',
190 var url = pyroutes.url('repo_files:annotated',
183 {'repo_name': templateContext.repo_name,
191 {'repo_name': templateContext.repo_name,
184 'commit_id': diff1, 'f_path': state.f_path});
192 'commit_id': diff1, 'f_path': state.f_path});
185 } else {
193 } else {
186 var url = pyroutes.url('repo_files',
194 var url = pyroutes.url('repo_files',
187 {'repo_name': templateContext.repo_name,
195 {'repo_name': templateContext.repo_name,
188 'commit_id': diff1, 'f_path': state.f_path});
196 'commit_id': diff1, 'f_path': state.f_path});
189 }
197 }
190 window.location = url;
198 window.location = url;
191
199
192 });
200 });
193
201
194 // show more authors
202 // show more authors
195 $('#show_authors').on('click', function(e) {
203 $('#show_authors').on('click', function(e) {
196 e.preventDefault();
204 e.preventDefault();
197 var url = pyroutes.url('repo_file_authors',
205 var url = pyroutes.url('repo_file_authors',
198 {'repo_name': templateContext.repo_name,
206 {'repo_name': templateContext.repo_name,
199 'commit_id': state.rev, 'f_path': state.f_path});
207 'commit_id': state.rev, 'f_path': state.f_path});
200
208
201 $.pjax({
209 $.pjax({
202 url: url,
210 url: url,
203 data: 'annotate=${"1" if c.annotate else "0"}',
211 data: 'annotate=${("1" if c.annotate else "0")}',
204 container: '#file_authors',
212 container: '#file_authors',
205 push: false,
213 push: false,
206 timeout: pjaxTimeout
214 timeout: pjaxTimeout
207 }).complete(function(){
215 }).complete(function(){
208 $('#show_authors').hide();
216 $('#show_authors').hide();
209 $('#file_authors_title').html(_gettext('All Authors'))
217 $('#file_authors_title').html(_gettext('All Authors'))
210 })
218 })
211 });
219 });
212
220
213 // load file short history
221 // load file short history
214 $('#file_history_overview').on('click', function(e) {
222 $('#file_history_overview').on('click', function(e) {
215 e.preventDefault();
223 e.preventDefault();
216 path = state.f_path;
224 path = state.f_path;
217 if (path.indexOf("#") >= 0) {
225 if (path.indexOf("#") >= 0) {
218 path = path.slice(0, path.indexOf("#"));
226 path = path.slice(0, path.indexOf("#"));
219 }
227 }
220 var url = pyroutes.url('repo_changelog_file',
228 var url = pyroutes.url('repo_changelog_file',
221 {'repo_name': templateContext.repo_name,
229 {'repo_name': templateContext.repo_name,
222 'commit_id': state.rev, 'f_path': path, 'limit': 6});
230 'commit_id': state.rev, 'f_path': path, 'limit': 6});
223 $('#file_history_container').show();
231 $('#file_history_container').show();
224 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...')));
232 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...')));
225
233
226 $.pjax({
234 $.pjax({
227 url: url,
235 url: url,
228 container: '#file_history_container',
236 container: '#file_history_container',
229 push: false,
237 push: false,
230 timeout: pjaxTimeout
238 timeout: pjaxTimeout
231 })
239 })
232 });
240 });
233
241
234 }
242 }
235 // VIEW FOR FILE TREE BROWSER
243 // VIEW FOR FILE TREE BROWSER
236 else {
244 else {
237 getFilesMetadata();
245 getFilesMetadata();
238
246
239 // fuzzy file filter
247 // fuzzy file filter
240 fileBrowserListeners(state.node_list_url, state.url_base);
248 fileBrowserListeners(state.node_list_url, state.url_base);
241
249
242 // switch to widget
250 // switch to widget
243 console.log(state)
244 var initialCommitData = {
251 var initialCommitData = {
245 at_ref: '${request.GET.get('at')}',
252 at_ref: atRef,
246 id: null,
253 id: null,
247 text: '${c.commit.raw_id}',
254 text: '${c.commit.raw_id}',
248 type: 'sha',
255 type: 'sha',
249 raw_id: '${c.commit.raw_id}',
256 raw_id: '${c.commit.raw_id}',
250 idx: ${c.commit.idx},
257 idx: ${c.commit.idx},
251 files_url: null,
258 files_url: null,
252 };
259 };
253
260
261 // check if we have ref info.
262 var selectedRef = fileTreeRefs[atRef];
263 if (selectedRef !== undefined) {
264 $.extend(initialCommitData, selectedRef)
265 }
266
254 var loadUrl = pyroutes.url('repo_refs_data', {'repo_name': templateContext.repo_name});
267 var loadUrl = pyroutes.url('repo_refs_data', {'repo_name': templateContext.repo_name});
255
268
256 var select2RefFileSwitcher = function (targetElement, loadUrl, initialData) {
269 var select2RefFileSwitcher = function (targetElement, loadUrl, initialData) {
257 var formatResult = function (result, container, query) {
270 var formatResult = function (result, container, query) {
258 return formatSelect2SelectionRefs(result);
271 return formatSelect2SelectionRefs(result);
259 };
272 };
260
273
261 var formatSelection = function (data, container) {
274 var formatSelection = function (data, container) {
262 var commit_ref = data;
275 var commit_ref = data;
263 console.log(data)
264
276
265 var tmpl = '';
277 var tmpl = '';
266 if (commit_ref.type === 'sha') {
278 if (commit_ref.type === 'sha') {
267 tmpl = commit_ref.raw_id.substr(0,8);
279 tmpl = commit_ref.raw_id.substr(0,8);
268 } else if (commit_ref.type === 'branch') {
280 } else if (commit_ref.type === 'branch') {
269 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
281 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
270 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
282 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
271 } else if (commit_ref.type === 'tag') {
283 } else if (commit_ref.type === 'tag') {
272 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
284 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
273 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
285 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
274 } else if (commit_ref.type === 'book') {
286 } else if (commit_ref.type === 'book') {
275 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
287 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
276 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
288 tmpl = tmpl.concat(escapeHtml(commit_ref.text));
277 }
289 }
278
290
279 tmpl = tmpl.concat('<span class="select-index-number">{0}</span>'.format(commit_ref.idx));
291 tmpl = tmpl.concat('<span class="select-index-number">{0}</span>'.format(commit_ref.idx));
280 return tmpl
292 return tmpl
281 };
293 };
282
294
283 $(targetElement).select2({
295 $(targetElement).select2({
284 cachedDataSource: {},
296 cachedDataSource: {},
285 dropdownAutoWidth: true,
297 dropdownAutoWidth: true,
286 width: "resolve",
298 width: "resolve",
287 containerCssClass: "drop-menu",
299 containerCssClass: "drop-menu",
288 dropdownCssClass: "drop-menu-dropdown",
300 dropdownCssClass: "drop-menu-dropdown",
289 query: function(query) {
301 query: function(query) {
290 var self = this;
302 var self = this;
291 var cacheKey = '__ALL_FILE_REFS__';
303 var cacheKey = '__ALL_FILE_REFS__';
292 var cachedData = self.cachedDataSource[cacheKey];
304 var cachedData = self.cachedDataSource[cacheKey];
293 if (cachedData) {
305 if (cachedData) {
294 var data = select2RefFilterResults(query.term, cachedData);
306 var data = select2RefFilterResults(query.term, cachedData);
295 query.callback({results: data.results});
307 query.callback({results: data.results});
296 } else {
308 } else {
297 $.ajax({
309 $.ajax({
298 url: loadUrl,
310 url: loadUrl,
299 data: {},
311 data: {},
300 dataType: 'json',
312 dataType: 'json',
301 type: 'GET',
313 type: 'GET',
302 success: function(data) {
314 success: function(data) {
303 self.cachedDataSource[cacheKey] = data;
315 self.cachedDataSource[cacheKey] = data;
304 query.callback({results: data.results});
316 query.callback({results: data.results});
305 }
317 }
306 });
318 });
307 }
319 }
308 },
320 },
309 initSelection: function(element, callback) {
321 initSelection: function(element, callback) {
310 callback(initialData);
322 callback(initialData);
311 },
323 },
312 formatResult: formatResult,
324 formatResult: formatResult,
313 formatSelection: formatSelection
325 formatSelection: formatSelection
314 });
326 });
315
327
316 };
328 };
317
329
318 select2RefFileSwitcher('#refs_filter', loadUrl, initialCommitData);
330 select2RefFileSwitcher('#refs_filter', loadUrl, initialCommitData);
319
331
320 $('#refs_filter').on('change', function(e) {
332 $('#refs_filter').on('change', function(e) {
321 var data = $('#refs_filter').select2('data');
333 var data = $('#refs_filter').select2('data');
322 curState.commit_id = data.raw_id;
334 window.location = data.files_url
323 $.pjax({url: data.files_url, container: '#pjax-container', timeout: pjaxTimeout});
324 });
325
326 $("#prev_commit_link").on('click', function(e) {
327 var data = $(this).data();
328 curState.commit_id = data.commitId;
329 });
330
331 $("#next_commit_link").on('click', function(e) {
332 var data = $(this).data();
333 curState.commit_id = data.commitId;
334 });
335 });
335
336
336 }
337 }
337 };
338 };
338
339
339 $(document).pjax(".pjax-link", "#pjax-container", {
340 "fragment": "#pjax-content",
341 "maxCacheLength": 1000,
342 "timeout": pjaxTimeout
343 });
344
345 // define global back/forward states
346 var isPjaxPopState = false;
347 $(document).on('pjax:popstate', function() {
348 isPjaxPopState = true;
349 });
350
351 $(document).on('pjax:end', function(xhr, options) {
352 if (isPjaxPopState) {
353 isPjaxPopState = false;
354 callbacks();
355 _NODEFILTER.resetFilter();
356 }
357
358 // run callback for tracking if defined for google analytics etc.
359 // this is used to trigger tracking on pjax
360 if (typeof window.rhodecode_statechange_callback !== 'undefined') {
361 var state = getState('statechange');
362 rhodecode_statechange_callback(state.url, null)
363 }
364 });
365
366 $(document).on('pjax:success', function(event, xhr, options) {
367 if (event.target.id == "file_history_container") {
368 $('#file_history_overview').hide();
369 $('#file_history_overview_full').show();
370 timeagoActivate();
371 } else {
372 callbacks();
373 }
374 });
375
376 $(document).ready(function() {
340 $(document).ready(function() {
377 callbacks();
341 callbacks();
378 var search_GET = "${request.GET.get('search','')}";
342 var search_GET = "${request.GET.get('search','')}";
379 if (search_GET === "1") {
343 if (search_GET === "1") {
380 _NODEFILTER.initFilter();
344 _NODEFILTER.initFilter();
381 }
345 }
382 });
346 });
383
347
384 </script>
348 </script>
385
349
386 </%def>
350 </%def> No newline at end of file
@@ -1,236 +1,236 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('%s Files Add') % c.repo_name}
4 ${_('%s Files Add') % c.repo_name}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="menu_bar_nav()">
10 <%def name="menu_bar_nav()">
11 ${self.menu_items(active='repositories')}
11 ${self.menu_items(active='repositories')}
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Add new file')} @ ${h.show_id(c.commit)} ${_('Branch')}: ${c.commit.branch}
15 ${_('Add new file')} @ ${h.show_id(c.commit)} ${_('Branch')}: ${c.commit.branch}
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_subnav()">
18 <%def name="menu_bar_subnav()">
19 ${self.repo_menu(active='files')}
19 ${self.repo_menu(active='files')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23 <div class="box">
23 <div class="box">
24
24
25 <div class="edit-file-title">
25 <div class="edit-file-title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28 ${h.secure_form(h.route_path('repo_files_create_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', enctype="multipart/form-data", class_="form-horizontal", request=request)}
28 ${h.secure_form(h.route_path('repo_files_create_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', enctype="multipart/form-data", class_="form-horizontal", request=request)}
29 <div class="edit-file-fieldset">
29 <div class="edit-file-fieldset">
30 <div class="fieldset">
30 <div class="fieldset">
31 <div id="destination-label" class="left-label">
31 <div id="destination-label" class="left-label">
32 ${_('Path')}:
32 ${_('Path')}:
33 </div>
33 </div>
34 <div class="right-content">
34 <div class="right-content">
35 <div id="specify-custom-path-container">
35 <div id="specify-custom-path-container">
36 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path)}</span>
36 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path, request.GET.get('at'))}</span>
37 <a class="custom-path-link" id="specify-custom-path" href="#">${_('Specify Custom Path')}</a>
37 <a class="custom-path-link" id="specify-custom-path" href="#">${_('Specify Custom Path')}</a>
38 </div>
38 </div>
39 <div id="remove-custom-path-container" style="display: none;">
39 <div id="remove-custom-path-container" style="display: none;">
40 ${c.repo_name}/
40 ${c.repo_name}/
41 <input type="input-small" value="${c.f_path}" size="46" name="location" id="location">
41 <input type="input-small" value="${c.f_path}" size="46" name="location" id="location">
42 <a class="custom-path-link" id="remove-custom-path" href="#">${_('Remove Custom Path')}</a>
42 <a class="custom-path-link" id="remove-custom-path" href="#">${_('Remove Custom Path')}</a>
43 </div>
43 </div>
44 </div>
44 </div>
45 </div>
45 </div>
46 <div id="filename_container" class="fieldset">
46 <div id="filename_container" class="fieldset">
47 <div class="filename-label left-label">
47 <div class="filename-label left-label">
48 ${_('Filename')}:
48 ${_('Filename')}:
49 </div>
49 </div>
50 <div class="right-content">
50 <div class="right-content">
51 <input class="input-small" type="text" value="" size="46" name="filename" id="filename">
51 <input class="input-small" type="text" value="" size="46" name="filename" id="filename">
52 <p>${_('or')} <a id="upload_file_enable" href="#">${_('Upload File')}</a></p>
52 <p>${_('or')} <a id="upload_file_enable" href="#">${_('Upload File')}</a></p>
53 </div>
53 </div>
54 </div>
54 </div>
55 <div id="upload_file_container" class="fieldset" style="display: none;">
55 <div id="upload_file_container" class="fieldset" style="display: none;">
56 <div class="filename-label left-label">
56 <div class="filename-label left-label">
57 ${_('Filename')}:
57 ${_('Filename')}:
58 </div>
58 </div>
59 <div class="right-content">
59 <div class="right-content">
60 <input class="input-small" type="text" value="" size="46" name="filename_upload" id="filename_upload" placeholder="${_('No file selected')}">
60 <input class="input-small" type="text" value="" size="46" name="filename_upload" id="filename_upload" placeholder="${_('No file selected')}">
61 </div>
61 </div>
62 <div class="filename-label left-label file-upload-label">
62 <div class="filename-label left-label file-upload-label">
63 ${_('Upload file')}:
63 ${_('Upload file')}:
64 </div>
64 </div>
65 <div class="right-content file-upload-input">
65 <div class="right-content file-upload-input">
66 <label for="upload_file" class="btn btn-default">Browse</label>
66 <label for="upload_file" class="btn btn-default">Browse</label>
67
67
68 <input type="file" name="upload_file" id="upload_file">
68 <input type="file" name="upload_file" id="upload_file">
69 <p>${_('or')} <a id="file_enable" href="#">${_('Create New File')}</a></p>
69 <p>${_('or')} <a id="file_enable" href="#">${_('Create New File')}</a></p>
70 </div>
70 </div>
71 </div>
71 </div>
72 </div>
72 </div>
73 <div class="table">
73 <div class="table">
74 <div id="files_data">
74 <div id="files_data">
75 <div id="codeblock" class="codeblock">
75 <div id="codeblock" class="codeblock">
76 <div class="code-header form" id="set_mode_header">
76 <div class="code-header form" id="set_mode_header">
77 <div class="fields">
77 <div class="fields">
78 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
78 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
79 <label for="line_wrap">${_('line wraps')}</label>
79 <label for="line_wrap">${_('line wraps')}</label>
80 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
80 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
81
81
82 <div id="render_preview" class="btn btn-small preview hidden" >${_('Preview')}</div>
82 <div id="render_preview" class="btn btn-small preview hidden" >${_('Preview')}</div>
83 </div>
83 </div>
84 </div>
84 </div>
85 <div id="editor_container">
85 <div id="editor_container">
86 <pre id="editor_pre"></pre>
86 <pre id="editor_pre"></pre>
87 <textarea id="editor" name="content" ></textarea>
87 <textarea id="editor" name="content" ></textarea>
88 <div id="editor_preview"></div>
88 <div id="editor_preview"></div>
89 </div>
89 </div>
90 </div>
90 </div>
91 </div>
91 </div>
92 </div>
92 </div>
93
93
94 <div class="edit-file-fieldset">
94 <div class="edit-file-fieldset">
95 <div class="fieldset">
95 <div class="fieldset">
96 <div id="commit-message-label" class="commit-message-label left-label">
96 <div id="commit-message-label" class="commit-message-label left-label">
97 ${_('Commit Message')}:
97 ${_('Commit Message')}:
98 </div>
98 </div>
99 <div class="right-content">
99 <div class="right-content">
100 <div class="message">
100 <div class="message">
101 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
101 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
102 </div>
102 </div>
103 </div>
103 </div>
104 </div>
104 </div>
105 <div class="pull-right">
105 <div class="pull-right">
106 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
106 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
107 ${h.submit('commit_btn',_('Commit changes'),class_="btn btn-small btn-success")}
107 ${h.submit('commit_btn',_('Commit changes'),class_="btn btn-small btn-success")}
108 </div>
108 </div>
109 </div>
109 </div>
110 ${h.end_form()}
110 ${h.end_form()}
111 </div>
111 </div>
112 <script type="text/javascript">
112 <script type="text/javascript">
113
113
114 $('#commit_btn').on('click', function() {
114 $('#commit_btn').on('click', function() {
115 var button = $(this);
115 var button = $(this);
116 if (button.hasClass('clicked')) {
116 if (button.hasClass('clicked')) {
117 button.attr('disabled', true);
117 button.attr('disabled', true);
118 } else {
118 } else {
119 button.addClass('clicked');
119 button.addClass('clicked');
120 }
120 }
121 });
121 });
122
122
123 $('#specify-custom-path').on('click', function(e){
123 $('#specify-custom-path').on('click', function(e){
124 e.preventDefault();
124 e.preventDefault();
125 $('#specify-custom-path-container').hide();
125 $('#specify-custom-path-container').hide();
126 $('#remove-custom-path-container').show();
126 $('#remove-custom-path-container').show();
127 $('#destination-label').css('margin-top', '13px');
127 $('#destination-label').css('margin-top', '13px');
128 });
128 });
129
129
130 $('#remove-custom-path').on('click', function(e){
130 $('#remove-custom-path').on('click', function(e){
131 e.preventDefault();
131 e.preventDefault();
132 $('#specify-custom-path-container').show();
132 $('#specify-custom-path-container').show();
133 $('#remove-custom-path-container').hide();
133 $('#remove-custom-path-container').hide();
134 $('#location').val('${c.f_path}');
134 $('#location').val('${c.f_path}');
135 $('#destination-label').css('margin-top', '0');
135 $('#destination-label').css('margin-top', '0');
136 });
136 });
137
137
138 var hide_upload = function(){
138 var hide_upload = function(){
139 $('#files_data').show();
139 $('#files_data').show();
140 $('#upload_file_container').hide();
140 $('#upload_file_container').hide();
141 $('#filename_container').show();
141 $('#filename_container').show();
142 };
142 };
143
143
144 $('#file_enable').on('click', function(e){
144 $('#file_enable').on('click', function(e){
145 e.preventDefault();
145 e.preventDefault();
146 hide_upload();
146 hide_upload();
147 });
147 });
148
148
149 $('#upload_file_enable').on('click', function(e){
149 $('#upload_file_enable').on('click', function(e){
150 e.preventDefault();
150 e.preventDefault();
151 $('#files_data').hide();
151 $('#files_data').hide();
152 $('#upload_file_container').show();
152 $('#upload_file_container').show();
153 $('#filename_container').hide();
153 $('#filename_container').hide();
154 if (detectIE() && detectIE() <= 9) {
154 if (detectIE() && detectIE() <= 9) {
155 $('#upload_file_container .file-upload-input label').hide();
155 $('#upload_file_container .file-upload-input label').hide();
156 $('#upload_file_container .file-upload-input span').hide();
156 $('#upload_file_container .file-upload-input span').hide();
157 $('#upload_file_container .file-upload-input input').show();
157 $('#upload_file_container .file-upload-input input').show();
158 }
158 }
159 });
159 });
160
160
161 $('#upload_file').on('change', function() {
161 $('#upload_file').on('change', function() {
162 if (this.files && this.files[0]) {
162 if (this.files && this.files[0]) {
163 $('#filename_upload').val(this.files[0].name);
163 $('#filename_upload').val(this.files[0].name);
164 }
164 }
165 });
165 });
166
166
167 hide_upload();
167 hide_upload();
168
168
169 var renderer = "";
169 var renderer = "";
170 var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}";
170 var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}";
171 var myCodeMirror = initCodeMirror('editor', reset_url, false);
171 var myCodeMirror = initCodeMirror('editor', reset_url, false);
172
172
173 var modes_select = $('#set_mode');
173 var modes_select = $('#set_mode');
174 fillCodeMirrorOptions(modes_select);
174 fillCodeMirrorOptions(modes_select);
175
175
176 var filename_selector = '#filename';
176 var filename_selector = '#filename';
177 var callback = function(filename, mimetype, mode){
177 var callback = function(filename, mimetype, mode){
178 CodeMirrorPreviewEnable(mode);
178 CodeMirrorPreviewEnable(mode);
179 };
179 };
180 // on change of select field set mode
180 // on change of select field set mode
181 setCodeMirrorModeFromSelect(
181 setCodeMirrorModeFromSelect(
182 modes_select, filename_selector, myCodeMirror, callback);
182 modes_select, filename_selector, myCodeMirror, callback);
183
183
184 // on entering the new filename set mode, from given extension
184 // on entering the new filename set mode, from given extension
185 setCodeMirrorModeFromInput(
185 setCodeMirrorModeFromInput(
186 modes_select, filename_selector, myCodeMirror, callback);
186 modes_select, filename_selector, myCodeMirror, callback);
187
187
188 // if the file is renderable set line wraps automatically
188 // if the file is renderable set line wraps automatically
189 if (renderer !== ""){
189 if (renderer !== ""){
190 var line_wrap = 'on';
190 var line_wrap = 'on';
191 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
191 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
192 setCodeMirrorLineWrap(myCodeMirror, true);
192 setCodeMirrorLineWrap(myCodeMirror, true);
193 }
193 }
194
194
195 // on select line wraps change the editor
195 // on select line wraps change the editor
196 $('#line_wrap').on('change', function(e){
196 $('#line_wrap').on('change', function(e){
197 var selected = e.currentTarget;
197 var selected = e.currentTarget;
198 var line_wraps = {'on': true, 'off': false}[selected.value];
198 var line_wraps = {'on': true, 'off': false}[selected.value];
199 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
199 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
200 });
200 });
201
201
202 // render preview/edit button
202 // render preview/edit button
203 $('#render_preview').on('click', function(e){
203 $('#render_preview').on('click', function(e){
204 if($(this).hasClass('preview')){
204 if($(this).hasClass('preview')){
205 $(this).removeClass('preview');
205 $(this).removeClass('preview');
206 $(this).html("${_('Edit')}");
206 $(this).html("${_('Edit')}");
207 $('#editor_preview').show();
207 $('#editor_preview').show();
208 $(myCodeMirror.getWrapperElement()).hide();
208 $(myCodeMirror.getWrapperElement()).hide();
209
209
210 var possible_renderer = {
210 var possible_renderer = {
211 'rst':'rst',
211 'rst':'rst',
212 'markdown':'markdown',
212 'markdown':'markdown',
213 'gfm': 'markdown'}[myCodeMirror.getMode().name];
213 'gfm': 'markdown'}[myCodeMirror.getMode().name];
214 var _text = myCodeMirror.getValue();
214 var _text = myCodeMirror.getValue();
215 var _renderer = possible_renderer || DEFAULT_RENDERER;
215 var _renderer = possible_renderer || DEFAULT_RENDERER;
216 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
216 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
217 $('#editor_preview').html(_gettext('Loading ...'));
217 $('#editor_preview').html(_gettext('Loading ...'));
218 var url = pyroutes.url('repo_commit_comment_preview',
218 var url = pyroutes.url('repo_commit_comment_preview',
219 {'repo_name': '${c.repo_name}',
219 {'repo_name': '${c.repo_name}',
220 'commit_id': '${c.commit.raw_id}'});
220 'commit_id': '${c.commit.raw_id}'});
221
221
222 ajaxPOST(url, post_data, function(o){
222 ajaxPOST(url, post_data, function(o){
223 $('#editor_preview').html(o);
223 $('#editor_preview').html(o);
224 })
224 })
225 }
225 }
226 else{
226 else{
227 $(this).addClass('preview');
227 $(this).addClass('preview');
228 $(this).html("${_('Preview')}");
228 $(this).html("${_('Preview')}");
229 $('#editor_preview').hide();
229 $('#editor_preview').hide();
230 $(myCodeMirror.getWrapperElement()).show();
230 $(myCodeMirror.getWrapperElement()).show();
231 }
231 }
232 });
232 });
233 $('#filename').focus();
233 $('#filename').focus();
234
234
235 </script>
235 </script>
236 </%def>
236 </%def>
@@ -1,58 +1,58 b''
1
1
2 <div id="codeblock" class="browserblock">
2 <div id="codeblock" class="browserblock">
3 <div class="browser-header">
3 <div class="browser-header">
4 <div class="browser-nav">
4 <div class="browser-nav">
5
5
6 <div class="info_box">
6 <div class="info_box">
7
7
8 <div class="info_box_elem previous">
8 <div class="info_box_elem previous">
9 <a id="prev_commit_link" data-commit-id="${c.prev_commit.raw_id}" class="pjax-link ${'disabled' if c.url_prev == '#' else ''}" href="${c.url_prev}" title="${_('Previous commit')}"><i class="icon-left"></i></a>
9 <a id="prev_commit_link" data-commit-id="${c.prev_commit.raw_id}" class=" ${'disabled' if c.url_prev == '#' else ''}" href="${c.url_prev}" title="${_('Previous commit')}"><i class="icon-left"></i></a>
10 </div>
10 </div>
11
11
12 ${h.hidden('refs_filter')}
12 ${h.hidden('refs_filter')}
13
13
14 <div class="info_box_elem next">
14 <div class="info_box_elem next">
15 <a id="next_commit_link" data-commit-id="${c.next_commit.raw_id}" class="pjax-link ${'disabled' if c.url_next == '#' else ''}" href="${c.url_next}" title="${_('Next commit')}"><i class="icon-right"></i></a>
15 <a id="next_commit_link" data-commit-id="${c.next_commit.raw_id}" class=" ${'disabled' if c.url_next == '#' else ''}" href="${c.url_next}" title="${_('Next commit')}"><i class="icon-right"></i></a>
16 </div>
16 </div>
17 </div>
17 </div>
18
18
19 <div id="search_activate_id" class="search_activate">
19 <div id="search_activate_id" class="search_activate">
20 <a class="btn btn-default" id="filter_activate" href="javascript:void(0)">${_('Search File List')}</a>
20 <a class="btn btn-default" id="filter_activate" href="javascript:void(0)">${_('Search File List')}</a>
21 </div>
21 </div>
22 <div id="search_deactivate_id" class="search_activate hidden">
22 <div id="search_deactivate_id" class="search_activate hidden">
23 <a class="btn btn-default" id="filter_deactivate" href="javascript:void(0)">${_('Close File List')}</a>
23 <a class="btn btn-default" id="filter_deactivate" href="javascript:void(0)">${_('Close File List')}</a>
24 </div>
24 </div>
25 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
25 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
26 <div title="${_('Add New File')}" class="btn btn-primary new-file">
26 <div title="${_('Add New File')}" class="btn btn-primary new-file">
27 <a href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _anchor='edit')}">
27 <a href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _anchor='edit')}">
28 ${_('Add File')}</a>
28 ${_('Add File')}</a>
29 </div>
29 </div>
30 % endif
30 % endif
31 % if c.enable_downloads:
31 % if c.enable_downloads:
32 <% at_path = '{}.zip'.format(request.GET.get('at') or c.commit.raw_id[:6]) %>
32 <% at_path = '{}.zip'.format(request.GET.get('at') or c.commit.raw_id[:6]) %>
33 <div title="${_('Download tree at {}').format(at_path)}" class="btn btn-default new-file">
33 <div title="${_('Download tree at {}').format(at_path)}" class="btn btn-default new-file">
34 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id))}">
34 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id))}">
35 ${_('Download tree at {}').format(at_path)}
35 ${_('Download tree at {}').format(at_path)}
36 </a>
36 </a>
37 </div>
37 </div>
38 % endif
38 % endif
39 </div>
39 </div>
40
40
41 <div class="browser-search">
41 <div class="browser-search">
42 <div class="node-filter">
42 <div class="node-filter">
43 <div class="node_filter_box hidden" id="node_filter_box_loading" >${_('Loading file list...')}</div>
43 <div class="node_filter_box hidden" id="node_filter_box_loading" >${_('Loading file list...')}</div>
44 <div class="node_filter_box hidden" id="node_filter_box" >
44 <div class="node_filter_box hidden" id="node_filter_box" >
45 <div class="node-filter-path">${h.get_last_path_part(c.file)}/</div>
45 <div class="node-filter-path">${h.get_last_path_part(c.file)}/</div>
46 <div class="node-filter-input">
46 <div class="node-filter-input">
47 <input class="init" type="text" name="filter" size="25" id="node_filter" autocomplete="off">
47 <input class="init" type="text" name="filter" size="25" id="node_filter" autocomplete="off">
48 </div>
48 </div>
49 </div>
49 </div>
50 </div>
50 </div>
51 </div>
51 </div>
52 </div>
52 </div>
53 ## file tree is computed from caches, and filled in
53 ## file tree is computed from caches, and filled in
54 <div id="file-tree">
54 <div id="file-tree">
55 ${c.file_tree |n}
55 ${c.file_tree |n}
56 </div>
56 </div>
57
57
58 </div>
58 </div>
@@ -1,82 +1,89 b''
1 <div id="file-tree-wrapper" class="browser-body ${'full-load' if c.full_load else ''}">
1 <%
2 if request.GET.get('at'):
3 query={'at': request.GET.get('at')}
4 else:
5 query=None
6 %>
7 <div id="file-tree-wrapper" class="browser-body ${('full-load' if c.full_load else '')}">
2 <table class="code-browser rctable repo_summary">
8 <table class="code-browser rctable repo_summary">
3 <thead>
9 <thead>
4 <tr>
10 <tr>
5 <th>${_('Name')}</th>
11 <th>${_('Name')}</th>
6 <th>${_('Size')}</th>
12 <th>${_('Size')}</th>
7 <th>${_('Modified')}</th>
13 <th>${_('Modified')}</th>
8 <th>${_('Last Commit')}</th>
14 <th>${_('Last Commit')}</th>
9 <th>${_('Author')}</th>
15 <th>${_('Author')}</th>
10 </tr>
16 </tr>
11 </thead>
17 </thead>
12
18
13 <tbody id="tbody">
19 <tbody id="tbody">
14 %if c.file.parent:
20 %if c.file.parent:
15 <tr class="parity0">
21 <tr class="parity0">
16 <td class="td-componentname">
22 <td class="td-componentname">
17 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.file.parent.path)}" class="pjax-link">
23 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.file.parent.path, _query=query)}">
18 <i class="icon-directory"></i>..
24 <i class="icon-directory"></i>..
19 </a>
25 </a>
20 </td>
26 </td>
21 <td></td>
27 <td></td>
22 <td></td>
28 <td></td>
23 <td></td>
29 <td></td>
24 <td></td>
30 <td></td>
25 </tr>
31 </tr>
26 %endif
32 %endif
27 %for cnt,node in enumerate(c.file):
33 %for cnt,node in enumerate(c.file):
28 <tr class="parity${cnt%2}">
34 <tr class="parity${cnt%2}">
29 <td class="td-componentname">
35 <td class="td-componentname">
30 % if node.is_submodule():
36 % if node.is_submodule():
31 <span class="submodule-dir">
37 <span class="submodule-dir">
32 % if node.url.startswith('http://') or node.url.startswith('https://'):
38 % if node.url.startswith('http://') or node.url.startswith('https://'):
33 <a href="${node.url}">
39 <a href="${node.url}">
34 <i class="icon-directory browser-dir"></i>${node.name}
40 <i class="icon-directory browser-dir"></i>${node.name}
35 </a>
41 </a>
36 % else:
42 % else:
37 <i class="icon-directory browser-dir"></i>${node.name}
43 <i class="icon-directory browser-dir"></i>${node.name}
38 % endif
44 % endif
39 </span>
45 </span>
40 % else:
46 % else:
41 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=h.safe_unicode(node.path))}" class="pjax-link">
47
42 <i class="${'icon-file-text browser-file' if node.is_file() else 'icon-directory browser-dir'}"></i>${node.name}
48 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=h.safe_unicode(node.path), _query=query)}">
49 <i class="${('icon-file-text browser-file' if node.is_file() else 'icon-directory browser-dir')}"></i>${node.name}
43 </a>
50 </a>
44 % endif
51 % endif
45 </td>
52 </td>
46 %if node.is_file():
53 %if node.is_file():
47 <td class="td-size" data-attr-name="size">
54 <td class="td-size" data-attr-name="size">
48 % if c.full_load:
55 % if c.full_load:
49 <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span>
56 <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span>
50 % else:
57 % else:
51 ${_('Loading ...')}
58 ${_('Loading ...')}
52 % endif
59 % endif
53 </td>
60 </td>
54 <td class="td-time" data-attr-name="modified_at">
61 <td class="td-time" data-attr-name="modified_at">
55 % if c.full_load:
62 % if c.full_load:
56 <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span>
63 <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span>
57 % endif
64 % endif
58 </td>
65 </td>
59 <td class="td-hash" data-attr-name="commit_id">
66 <td class="td-hash" data-attr-name="commit_id">
60 % if c.full_load:
67 % if c.full_load:
61 <div class="tooltip" title="${h.tooltip(node.last_commit.message)}">
68 <div class="tooltip" title="${h.tooltip(node.last_commit.message)}">
62 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.idx}:${node.last_commit.short_id}</pre>
69 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.idx}:${node.last_commit.short_id}</pre>
63 </div>
70 </div>
64 % endif
71 % endif
65 </td>
72 </td>
66 <td class="td-user" data-attr-name="author">
73 <td class="td-user" data-attr-name="author">
67 % if c.full_load:
74 % if c.full_load:
68 <span data-author="${node.last_commit.author}" title="${h.tooltip(node.last_commit.author)}">${h.gravatar_with_user(request, node.last_commit.author)|n}</span>
75 <span data-author="${node.last_commit.author}" title="${h.tooltip(node.last_commit.author)}">${h.gravatar_with_user(request, node.last_commit.author)|n}</span>
69 % endif
76 % endif
70 </td>
77 </td>
71 %else:
78 %else:
72 <td></td>
79 <td></td>
73 <td></td>
80 <td></td>
74 <td></td>
81 <td></td>
75 <td></td>
82 <td></td>
76 %endif
83 %endif
77 </tr>
84 </tr>
78 %endfor
85 %endfor
79 </tbody>
86 </tbody>
80 <tbody id="tbody_filtered"></tbody>
87 <tbody id="tbody_filtered"></tbody>
81 </table>
88 </table>
82 </div>
89 </div>
@@ -1,72 +1,72 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('%s Files Delete') % c.repo_name}
4 ${_('%s Files Delete') % c.repo_name}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="menu_bar_nav()">
10 <%def name="menu_bar_nav()">
11 ${self.menu_items(active='repositories')}
11 ${self.menu_items(active='repositories')}
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Delete file')} @ ${h.show_id(c.commit)}
15 ${_('Delete file')} @ ${h.show_id(c.commit)}
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_subnav()">
18 <%def name="menu_bar_subnav()">
19 ${self.repo_menu(active='files')}
19 ${self.repo_menu(active='files')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23 <div class="box">
23 <div class="box">
24 <div class="edit-file-title">
24 <div class="edit-file-title">
25 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
26 </div>
26 </div>
27 ${h.secure_form(h.route_path('repo_files_delete_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', class_="form-horizontal", request=request)}
27 ${h.secure_form(h.route_path('repo_files_delete_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', class_="form-horizontal", request=request)}
28 <div class="edit-file-fieldset">
28 <div class="edit-file-fieldset">
29 <div class="fieldset">
29 <div class="fieldset">
30 <div id="destination-label" class="left-label">
30 <div id="destination-label" class="left-label">
31 ${_('Path')}:
31 ${_('Path')}:
32 </div>
32 </div>
33 <div class="right-content">
33 <div class="right-content">
34 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path)}</span>
34 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path, request.GET.get('at'))}</span>
35 </div>
35 </div>
36 </div>
36 </div>
37 </div>
37 </div>
38
38
39 <div id="codeblock" class="codeblock delete-file-preview">
39 <div id="codeblock" class="codeblock delete-file-preview">
40 <div class="code-body">
40 <div class="code-body">
41 %if c.file.is_binary:
41 %if c.file.is_binary:
42 ${_('Binary file (%s)') % c.file.mimetype}
42 ${_('Binary file (%s)') % c.file.mimetype}
43 %else:
43 %else:
44 %if c.file.size < c.visual.cut_off_limit_file:
44 %if c.file.size < c.visual.cut_off_limit_file:
45 ${h.pygmentize(c.file,linenos=True,anchorlinenos=False,cssclass="code-highlight")}
45 ${h.pygmentize(c.file,linenos=True,anchorlinenos=False,cssclass="code-highlight")}
46 %else:
46 %else:
47 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
47 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
48 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
48 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
49 %endif
49 %endif
50 %endif
50 %endif
51 </div>
51 </div>
52 </div>
52 </div>
53
53
54 <div class="edit-file-fieldset">
54 <div class="edit-file-fieldset">
55 <div class="fieldset">
55 <div class="fieldset">
56 <div id="commit-message-label" class="commit-message-label left-label">
56 <div id="commit-message-label" class="commit-message-label left-label">
57 ${_('Commit Message')}:
57 ${_('Commit Message')}:
58 </div>
58 </div>
59 <div class="right-content">
59 <div class="right-content">
60 <div class="message">
60 <div class="message">
61 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
61 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
62 </div>
62 </div>
63 </div>
63 </div>
64 </div>
64 </div>
65 <div class="pull-right">
65 <div class="pull-right">
66 ${h.reset('reset',_('Cancel'),class_="btn btn-small btn-danger")}
66 ${h.reset('reset',_('Cancel'),class_="btn btn-small btn-danger")}
67 ${h.submit('commit',_('Delete File'),class_="btn btn-small btn-danger-action")}
67 ${h.submit('commit',_('Delete File'),class_="btn btn-small btn-danger-action")}
68 </div>
68 </div>
69 </div>
69 </div>
70 ${h.end_form()}
70 ${h.end_form()}
71 </div>
71 </div>
72 </%def>
72 </%def>
@@ -1,194 +1,194 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('%s File Edit') % c.repo_name}
4 ${_('%s File Edit') % c.repo_name}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="menu_bar_nav()">
10 <%def name="menu_bar_nav()">
11 ${self.menu_items(active='repositories')}
11 ${self.menu_items(active='repositories')}
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Edit file')} @ ${h.show_id(c.commit)}
15 ${_('Edit file')} @ ${h.show_id(c.commit)}
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_subnav()">
18 <%def name="menu_bar_subnav()">
19 ${self.repo_menu(active='files')}
19 ${self.repo_menu(active='files')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23 <% renderer = h.renderer_from_filename(c.f_path)%>
23 <% renderer = h.renderer_from_filename(c.f_path)%>
24 <div class="box">
24 <div class="box">
25 <div class="edit-file-title">
25 <div class="edit-file-title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28 <div class="edit-file-fieldset">
28 <div class="edit-file-fieldset">
29 <div class="fieldset">
29 <div class="fieldset">
30 <div id="destination-label" class="left-label">
30 <div id="destination-label" class="left-label">
31 ${_('Path')}:
31 ${_('Path')}:
32 </div>
32 </div>
33 <div class="right-content">
33 <div class="right-content">
34 <div id="specify-custom-path-container">
34 <div id="specify-custom-path-container">
35 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path)}</span>
35 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path, request.GET.get('at'))}</span>
36 </div>
36 </div>
37 </div>
37 </div>
38 </div>
38 </div>
39 </div>
39 </div>
40
40
41 <div class="table">
41 <div class="table">
42 ${h.secure_form(h.route_path('repo_files_update_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', request=request)}
42 ${h.secure_form(h.route_path('repo_files_update_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', request=request)}
43 <div id="codeblock" class="codeblock" >
43 <div id="codeblock" class="codeblock" >
44 <div class="code-header">
44 <div class="code-header">
45 <div class="stats">
45 <div class="stats">
46 <i class="icon-file"></i>
46 <i class="icon-file"></i>
47 <span class="item">${h.link_to("r%s:%s" % (c.file.commit.idx,h.short_id(c.file.commit.raw_id)),h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.file.commit.raw_id))}</span>
47 <span class="item">${h.link_to("r%s:%s" % (c.file.commit.idx,h.short_id(c.file.commit.raw_id)),h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.file.commit.raw_id))}</span>
48 <span class="item">${h.format_byte_size_binary(c.file.size)}</span>
48 <span class="item">${h.format_byte_size_binary(c.file.size)}</span>
49 <span class="item last">${c.file.mimetype}</span>
49 <span class="item last">${c.file.mimetype}</span>
50 <div class="buttons">
50 <div class="buttons">
51 <a class="btn btn-mini" href="${h.route_path('repo_changelog_file',repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}">
51 <a class="btn btn-mini" href="${h.route_path('repo_changelog_file',repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}">
52 <i class="icon-time"></i> ${_('history')}
52 <i class="icon-time"></i> ${_('history')}
53 </a>
53 </a>
54
54
55 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
55 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
56 % if not c.file.is_binary:
56 % if not c.file.is_binary:
57 %if True:
57 %if True:
58 ${h.link_to(_('source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
58 ${h.link_to(_('source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
59 %else:
59 %else:
60 ${h.link_to(_('annotation'),h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
60 ${h.link_to(_('annotation'),h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
61 %endif
61 %endif
62
62
63 <a class="btn btn-mini" href="${h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
63 <a class="btn btn-mini" href="${h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
64 ${_('raw')}
64 ${_('raw')}
65 </a>
65 </a>
66 <a class="btn btn-mini" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
66 <a class="btn btn-mini" href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
67 <i class="icon-archive"></i> ${_('download')}
67 <i class="icon-archive"></i> ${_('download')}
68 </a>
68 </a>
69 % endif
69 % endif
70 % endif
70 % endif
71 </div>
71 </div>
72 </div>
72 </div>
73 <div class="form">
73 <div class="form">
74 <label for="set_mode">${_('Editing file')}:</label>
74 <label for="set_mode">${_('Editing file')}:</label>
75 ${'%s /' % c.file.dir_path if c.file.dir_path else c.file.dir_path}
75 ${'%s /' % c.file.dir_path if c.file.dir_path else c.file.dir_path}
76 <input id="filename" type="text" name="filename" value="${c.file.name}">
76 <input id="filename" type="text" name="filename" value="${c.file.name}">
77
77
78 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
78 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
79 <label for="line_wrap">${_('line wraps')}</label>
79 <label for="line_wrap">${_('line wraps')}</label>
80 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
80 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
81
81
82 <div id="render_preview" class="btn btn-small preview hidden">${_('Preview')}</div>
82 <div id="render_preview" class="btn btn-small preview hidden">${_('Preview')}</div>
83 </div>
83 </div>
84 </div>
84 </div>
85 <div id="editor_container">
85 <div id="editor_container">
86 <pre id="editor_pre"></pre>
86 <pre id="editor_pre"></pre>
87 <textarea id="editor" name="content" >${h.escape(c.file.content)|n}</textarea>
87 <textarea id="editor" name="content" >${h.escape(c.file.content)|n}</textarea>
88 <div id="editor_preview" ></div>
88 <div id="editor_preview" ></div>
89 </div>
89 </div>
90 </div>
90 </div>
91 </div>
91 </div>
92
92
93 <div class="edit-file-fieldset">
93 <div class="edit-file-fieldset">
94 <div class="fieldset">
94 <div class="fieldset">
95 <div id="commit-message-label" class="commit-message-label left-label">
95 <div id="commit-message-label" class="commit-message-label left-label">
96 ${_('Commit Message')}:
96 ${_('Commit Message')}:
97 </div>
97 </div>
98 <div class="right-content">
98 <div class="right-content">
99 <div class="message">
99 <div class="message">
100 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
100 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
101 </div>
101 </div>
102 </div>
102 </div>
103 </div>
103 </div>
104 <div class="pull-right">
104 <div class="pull-right">
105 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
105 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
106 ${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-success")}
106 ${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-success")}
107 </div>
107 </div>
108 </div>
108 </div>
109 ${h.end_form()}
109 ${h.end_form()}
110 </div>
110 </div>
111
111
112 <script type="text/javascript">
112 <script type="text/javascript">
113 $(document).ready(function(){
113 $(document).ready(function(){
114 var renderer = "${renderer}";
114 var renderer = "${renderer}";
115 var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.file.path)}";
115 var reset_url = "${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.file.path)}";
116 var myCodeMirror = initCodeMirror('editor', reset_url);
116 var myCodeMirror = initCodeMirror('editor', reset_url);
117
117
118 var modes_select = $('#set_mode');
118 var modes_select = $('#set_mode');
119 fillCodeMirrorOptions(modes_select);
119 fillCodeMirrorOptions(modes_select);
120
120
121 // try to detect the mode based on the file we edit
121 // try to detect the mode based on the file we edit
122 var mimetype = "${c.file.mimetype}";
122 var mimetype = "${c.file.mimetype}";
123 var detected_mode = detectCodeMirrorMode(
123 var detected_mode = detectCodeMirrorMode(
124 "${c.file.name}", mimetype);
124 "${c.file.name}", mimetype);
125
125
126 if(detected_mode){
126 if(detected_mode){
127 setCodeMirrorMode(myCodeMirror, detected_mode);
127 setCodeMirrorMode(myCodeMirror, detected_mode);
128 $(modes_select).select2("val", mimetype);
128 $(modes_select).select2("val", mimetype);
129 $(modes_select).change();
129 $(modes_select).change();
130 setCodeMirrorMode(myCodeMirror, detected_mode);
130 setCodeMirrorMode(myCodeMirror, detected_mode);
131 }
131 }
132
132
133 var filename_selector = '#filename';
133 var filename_selector = '#filename';
134 var callback = function(filename, mimetype, mode){
134 var callback = function(filename, mimetype, mode){
135 CodeMirrorPreviewEnable(mode);
135 CodeMirrorPreviewEnable(mode);
136 };
136 };
137 // on change of select field set mode
137 // on change of select field set mode
138 setCodeMirrorModeFromSelect(
138 setCodeMirrorModeFromSelect(
139 modes_select, filename_selector, myCodeMirror, callback);
139 modes_select, filename_selector, myCodeMirror, callback);
140
140
141 // on entering the new filename set mode, from given extension
141 // on entering the new filename set mode, from given extension
142 setCodeMirrorModeFromInput(
142 setCodeMirrorModeFromInput(
143 modes_select, filename_selector, myCodeMirror, callback);
143 modes_select, filename_selector, myCodeMirror, callback);
144
144
145 // if the file is renderable set line wraps automatically
145 // if the file is renderable set line wraps automatically
146 if (renderer !== ""){
146 if (renderer !== ""){
147 var line_wrap = 'on';
147 var line_wrap = 'on';
148 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
148 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
149 setCodeMirrorLineWrap(myCodeMirror, true);
149 setCodeMirrorLineWrap(myCodeMirror, true);
150 }
150 }
151 // on select line wraps change the editor
151 // on select line wraps change the editor
152 $('#line_wrap').on('change', function(e){
152 $('#line_wrap').on('change', function(e){
153 var selected = e.currentTarget;
153 var selected = e.currentTarget;
154 var line_wraps = {'on': true, 'off': false}[selected.value];
154 var line_wraps = {'on': true, 'off': false}[selected.value];
155 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
155 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
156 });
156 });
157
157
158 // render preview/edit button
158 // render preview/edit button
159 if (mimetype === 'text/x-rst' || mimetype === 'text/plain') {
159 if (mimetype === 'text/x-rst' || mimetype === 'text/plain') {
160 $('#render_preview').removeClass('hidden');
160 $('#render_preview').removeClass('hidden');
161 }
161 }
162 $('#render_preview').on('click', function(e){
162 $('#render_preview').on('click', function(e){
163 if($(this).hasClass('preview')){
163 if($(this).hasClass('preview')){
164 $(this).removeClass('preview');
164 $(this).removeClass('preview');
165 $(this).html("${_('Edit')}");
165 $(this).html("${_('Edit')}");
166 $('#editor_preview').show();
166 $('#editor_preview').show();
167 $(myCodeMirror.getWrapperElement()).hide();
167 $(myCodeMirror.getWrapperElement()).hide();
168
168
169 var possible_renderer = {
169 var possible_renderer = {
170 'rst':'rst',
170 'rst':'rst',
171 'markdown':'markdown',
171 'markdown':'markdown',
172 'gfm': 'markdown'}[myCodeMirror.getMode().name];
172 'gfm': 'markdown'}[myCodeMirror.getMode().name];
173 var _text = myCodeMirror.getValue();
173 var _text = myCodeMirror.getValue();
174 var _renderer = possible_renderer || DEFAULT_RENDERER;
174 var _renderer = possible_renderer || DEFAULT_RENDERER;
175 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
175 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
176 $('#editor_preview').html(_gettext('Loading ...'));
176 $('#editor_preview').html(_gettext('Loading ...'));
177 var url = pyroutes.url('repo_commit_comment_preview',
177 var url = pyroutes.url('repo_commit_comment_preview',
178 {'repo_name': '${c.repo_name}',
178 {'repo_name': '${c.repo_name}',
179 'commit_id': '${c.commit.raw_id}'});
179 'commit_id': '${c.commit.raw_id}'});
180 ajaxPOST(url, post_data, function(o){
180 ajaxPOST(url, post_data, function(o){
181 $('#editor_preview').html(o);
181 $('#editor_preview').html(o);
182 })
182 })
183 }
183 }
184 else{
184 else{
185 $(this).addClass('preview');
185 $(this).addClass('preview');
186 $(this).html("${_('Preview')}");
186 $(this).html("${_('Preview')}");
187 $('#editor_preview').hide();
187 $('#editor_preview').hide();
188 $(myCodeMirror.getWrapperElement()).show();
188 $(myCodeMirror.getWrapperElement()).show();
189 }
189 }
190 });
190 });
191
191
192 })
192 })
193 </script>
193 </script>
194 </%def>
194 </%def>
@@ -1,46 +1,42 b''
1 <%def name="title(*args)">
1 <%def name="title(*args)">
2 ${_('{} Files').format(c.repo_name)}
2 ${_('{} Files').format(c.repo_name)}
3 %if hasattr(c,'file'):
3 %if hasattr(c,'file'):
4 &middot; ${(h.safe_unicode(c.file.path) or '\\')}
4 &middot; ${(h.safe_unicode(c.file.path) or '\\')}
5 %endif
5 %endif
6
6
7 %if c.rhodecode_name:
7 %if c.rhodecode_name:
8 &middot; ${h.branding(c.rhodecode_name)}
8 &middot; ${h.branding(c.rhodecode_name)}
9 %endif
9 %endif
10 </%def>
10 </%def>
11
11
12 <div id="pjax-content" data-title="${self.title()}">
12 <div>
13 <script>
14 // set the pageSource variable
15 var fileSourcePage = ${c.file_source_page};
16 </script>
17
13
18 <div class="summary-detail">
14 <div class="summary-detail">
19 <div class="summary-detail-header">
15 <div class="summary-detail-header">
20 <div class="breadcrumbs files_location">
16 <div class="breadcrumbs files_location">
21 <h4>
17 <h4>
22 ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.file.path)}
18 ${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.file.path, request.GET.get('at'))}
23 %if c.annotate:
19 %if c.annotate:
24 - ${_('annotation')}
20 - ${_('annotation')}
25 %endif
21 %endif
26 </h4>
22 </h4>
27 </div>
23 </div>
28 </div><!--end summary-detail-header-->
24 </div><!--end summary-detail-header-->
29
25
30 % if c.file.is_submodule():
26 % if c.file.is_submodule():
31 <span class="submodule-dir">Submodule ${h.escape(c.file.name)}</span>
27 <span class="submodule-dir">Submodule ${h.escape(c.file.name)}</span>
32 % elif c.file.is_dir():
28 % elif c.file.is_dir():
33 <%include file='files_tree_header.mako'/>
29 <%include file='files_tree_header.mako'/>
34 % else:
30 % else:
35 <%include file='files_source_header.mako'/>
31 <%include file='files_source_header.mako'/>
36 % endif
32 % endif
37
33
38 </div> <!--end summary-detail-->
34 </div> <!--end summary-detail-->
39
35
40 % if c.file.is_dir():
36 % if c.file.is_dir():
41 <%include file='files_browser.mako'/>
37 <%include file='files_browser.mako'/>
42 % else:
38 % else:
43 <%include file='files_source.mako'/>
39 <%include file='files_source.mako'/>
44 % endif
40 % endif
45
41
46 </div>
42 </div>
@@ -1,210 +1,210 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import copy
21 import copy
22 import mock
22 import mock
23 import pytest
23 import pytest
24
24
25 from rhodecode.lib import helpers
25 from rhodecode.lib import helpers
26 from rhodecode.lib.utils2 import AttributeDict
26 from rhodecode.lib.utils2 import AttributeDict
27 from rhodecode.model.settings import IssueTrackerSettingsModel
27 from rhodecode.model.settings import IssueTrackerSettingsModel
28 from rhodecode.tests import no_newline_id_generator
28 from rhodecode.tests import no_newline_id_generator
29
29
30
30
31 @pytest.mark.parametrize('url, expected_url', [
31 @pytest.mark.parametrize('url, expected_url', [
32 ('http://rc.rc/test', '<a href="http://rc.rc/test">http://rc.rc/test</a>'),
32 ('http://rc.rc/test', '<a href="http://rc.rc/test">http://rc.rc/test</a>'),
33 ('http://rc.rc/@foo', '<a href="http://rc.rc/@foo">http://rc.rc/@foo</a>'),
33 ('http://rc.rc/@foo', '<a href="http://rc.rc/@foo">http://rc.rc/@foo</a>'),
34 ('http://rc.rc/!foo', '<a href="http://rc.rc/!foo">http://rc.rc/!foo</a>'),
34 ('http://rc.rc/!foo', '<a href="http://rc.rc/!foo">http://rc.rc/!foo</a>'),
35 ('http://rc.rc/&foo', '<a href="http://rc.rc/&foo">http://rc.rc/&foo</a>'),
35 ('http://rc.rc/&foo', '<a href="http://rc.rc/&foo">http://rc.rc/&foo</a>'),
36 ('http://rc.rc/#foo', '<a href="http://rc.rc/#foo">http://rc.rc/#foo</a>'),
36 ('http://rc.rc/#foo', '<a href="http://rc.rc/#foo">http://rc.rc/#foo</a>'),
37 ])
37 ])
38 def test_urlify_text(url, expected_url):
38 def test_urlify_text(url, expected_url):
39 assert helpers.urlify_text(url) == expected_url
39 assert helpers.urlify_text(url) == expected_url
40
40
41
41
42 @pytest.mark.parametrize('repo_name, commit_id, path, expected_result', [
42 @pytest.mark.parametrize('repo_name, commit_id, path, expected_result', [
43 ('rX<X', 'cX<X', 'pX<X/aX<X/bX<X',
43 ('rX<X', 'cX<X', 'pX<X/aX<X/bX<X',
44 '<a class="pjax-link" href="/rX%3CX/files/cX%3CX/">rX&lt;X</a>/'
44 '<a href="/rX%3CX/files/cX%3CX/">rX&lt;X</a>/'
45 '<a class="pjax-link" href="/rX%3CX/files/cX%3CX/pX%3CX">pX&lt;X</a>/'
45 '<a href="/rX%3CX/files/cX%3CX/pX%3CX">pX&lt;X</a>/'
46 '<a class="pjax-link" href="/rX%3CX/files/cX%3CX/pX%3CX/aX%3CX">aX&lt;X'
46 '<a href="/rX%3CX/files/cX%3CX/pX%3CX/aX%3CX">aX&lt;X'
47 '</a>/bX&lt;X'),
47 '</a>/bX&lt;X'),
48 # Path with only one segment
48 # Path with only one segment
49 ('rX<X', 'cX<X', 'pX<X',
49 ('rX<X', 'cX<X', 'pX<X',
50 '<a class="pjax-link" href="/rX%3CX/files/cX%3CX/">rX&lt;X</a>/pX&lt;X'),
50 '<a href="/rX%3CX/files/cX%3CX/">rX&lt;X</a>/pX&lt;X'),
51 # Empty path
51 # Empty path
52 ('rX<X', 'cX<X', '', 'rX&lt;X'),
52 ('rX<X', 'cX<X', '', 'rX&lt;X'),
53 ('rX"X', 'cX"X', 'pX"X/aX"X/bX"X',
53 ('rX"X', 'cX"X', 'pX"X/aX"X/bX"X',
54 '<a class="pjax-link" href="/rX%22X/files/cX%22X/">rX&#34;X</a>/'
54 '<a href="/rX%22X/files/cX%22X/">rX&#34;X</a>/'
55 '<a class="pjax-link" href="/rX%22X/files/cX%22X/pX%22X">pX&#34;X</a>/'
55 '<a href="/rX%22X/files/cX%22X/pX%22X">pX&#34;X</a>/'
56 '<a class="pjax-link" href="/rX%22X/files/cX%22X/pX%22X/aX%22X">aX&#34;X'
56 '<a href="/rX%22X/files/cX%22X/pX%22X/aX%22X">aX&#34;X'
57 '</a>/bX&#34;X'),
57 '</a>/bX&#34;X'),
58 ], ids=['simple', 'one_segment', 'empty_path', 'simple_quote'])
58 ], ids=['simple', 'one_segment', 'empty_path', 'simple_quote'])
59 def test_files_breadcrumbs_xss(
59 def test_files_breadcrumbs_xss(
60 repo_name, commit_id, path, app, expected_result):
60 repo_name, commit_id, path, app, expected_result):
61 result = helpers.files_breadcrumbs(repo_name, commit_id, path)
61 result = helpers.files_breadcrumbs(repo_name, commit_id, path)
62 # Expect it to encode all path fragments properly. This is important
62 # Expect it to encode all path fragments properly. This is important
63 # because it returns an instance of `literal`.
63 # because it returns an instance of `literal`.
64 assert result == expected_result
64 assert result == expected_result
65
65
66
66
67 def test_format_binary():
67 def test_format_binary():
68 assert helpers.format_byte_size_binary(298489462784) == '278.0 GiB'
68 assert helpers.format_byte_size_binary(298489462784) == '278.0 GiB'
69
69
70
70
71 @pytest.mark.parametrize('text_string, pattern, expected', [
71 @pytest.mark.parametrize('text_string, pattern, expected', [
72 ('No issue here', '(?:#)(?P<issue_id>\d+)', []),
72 ('No issue here', '(?:#)(?P<issue_id>\d+)', []),
73 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
73 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
74 [{'url': 'http://r.io/{repo}/i/42', 'id': '42'}]),
74 [{'url': 'http://r.io/{repo}/i/42', 'id': '42'}]),
75 ('Fix #42, #53', '(?:#)(?P<issue_id>\d+)', [
75 ('Fix #42, #53', '(?:#)(?P<issue_id>\d+)', [
76 {'url': 'http://r.io/{repo}/i/42', 'id': '42'},
76 {'url': 'http://r.io/{repo}/i/42', 'id': '42'},
77 {'url': 'http://r.io/{repo}/i/53', 'id': '53'}]),
77 {'url': 'http://r.io/{repo}/i/53', 'id': '53'}]),
78 ('Fix #42', '(?:#)?<issue_id>\d+)', []), # Broken regex
78 ('Fix #42', '(?:#)?<issue_id>\d+)', []), # Broken regex
79 ])
79 ])
80 def test_extract_issues(backend, text_string, pattern, expected):
80 def test_extract_issues(backend, text_string, pattern, expected):
81 repo = backend.create_repo()
81 repo = backend.create_repo()
82 config = {
82 config = {
83 '123': {
83 '123': {
84 'uid': '123',
84 'uid': '123',
85 'pat': pattern,
85 'pat': pattern,
86 'url': 'http://r.io/${repo}/i/${issue_id}',
86 'url': 'http://r.io/${repo}/i/${issue_id}',
87 'pref': '#',
87 'pref': '#',
88 }
88 }
89 }
89 }
90
90
91 def get_settings_mock(self, cache=True):
91 def get_settings_mock(self, cache=True):
92 return config
92 return config
93
93
94 with mock.patch.object(IssueTrackerSettingsModel,
94 with mock.patch.object(IssueTrackerSettingsModel,
95 'get_settings', get_settings_mock):
95 'get_settings', get_settings_mock):
96 text, issues = helpers.process_patterns(text_string, repo.repo_name)
96 text, issues = helpers.process_patterns(text_string, repo.repo_name)
97
97
98 expected = copy.deepcopy(expected)
98 expected = copy.deepcopy(expected)
99 for item in expected:
99 for item in expected:
100 item['url'] = item['url'].format(repo=repo.repo_name)
100 item['url'] = item['url'].format(repo=repo.repo_name)
101
101
102 assert issues == expected
102 assert issues == expected
103
103
104
104
105 @pytest.mark.parametrize('text_string, pattern, link_format, expected_text', [
105 @pytest.mark.parametrize('text_string, pattern, link_format, expected_text', [
106 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'html',
106 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'html',
107 'Fix <a class="issue-tracker-link" href="http://r.io/{repo}/i/42">#42</a>'),
107 'Fix <a class="issue-tracker-link" href="http://r.io/{repo}/i/42">#42</a>'),
108
108
109 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'markdown',
109 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'markdown',
110 'Fix [#42](http://r.io/{repo}/i/42)'),
110 'Fix [#42](http://r.io/{repo}/i/42)'),
111
111
112 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'rst',
112 ('Fix #42', '(?:#)(?P<issue_id>\d+)', 'rst',
113 'Fix `#42 <http://r.io/{repo}/i/42>`_'),
113 'Fix `#42 <http://r.io/{repo}/i/42>`_'),
114
114
115 ('Fix #42', '(?:#)?<issue_id>\d+)', 'html',
115 ('Fix #42', '(?:#)?<issue_id>\d+)', 'html',
116 'Fix #42'), # Broken regex
116 'Fix #42'), # Broken regex
117 ])
117 ])
118 def test_process_patterns_repo(backend, text_string, pattern, expected_text, link_format):
118 def test_process_patterns_repo(backend, text_string, pattern, expected_text, link_format):
119 repo = backend.create_repo()
119 repo = backend.create_repo()
120
120
121 def get_settings_mock(self, cache=True):
121 def get_settings_mock(self, cache=True):
122 return {
122 return {
123 '123': {
123 '123': {
124 'uid': '123',
124 'uid': '123',
125 'pat': pattern,
125 'pat': pattern,
126 'url': 'http://r.io/${repo}/i/${issue_id}',
126 'url': 'http://r.io/${repo}/i/${issue_id}',
127 'pref': '#',
127 'pref': '#',
128 }
128 }
129 }
129 }
130
130
131 with mock.patch.object(IssueTrackerSettingsModel,
131 with mock.patch.object(IssueTrackerSettingsModel,
132 'get_settings', get_settings_mock):
132 'get_settings', get_settings_mock):
133 processed_text, issues = helpers.process_patterns(
133 processed_text, issues = helpers.process_patterns(
134 text_string, repo.repo_name, link_format)
134 text_string, repo.repo_name, link_format)
135
135
136 assert processed_text == expected_text.format(repo=repo.repo_name)
136 assert processed_text == expected_text.format(repo=repo.repo_name)
137
137
138
138
139 @pytest.mark.parametrize('text_string, pattern, expected_text', [
139 @pytest.mark.parametrize('text_string, pattern, expected_text', [
140 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
140 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
141 'Fix <a class="issue-tracker-link" href="http://r.io/i/42">#42</a>'),
141 'Fix <a class="issue-tracker-link" href="http://r.io/i/42">#42</a>'),
142 ('Fix #42', '(?:#)?<issue_id>\d+)',
142 ('Fix #42', '(?:#)?<issue_id>\d+)',
143 'Fix #42'), # Broken regex
143 'Fix #42'), # Broken regex
144 ])
144 ])
145 def test_process_patterns_no_repo(text_string, pattern, expected_text):
145 def test_process_patterns_no_repo(text_string, pattern, expected_text):
146
146
147 def get_settings_mock(self, cache=True):
147 def get_settings_mock(self, cache=True):
148 return {
148 return {
149 '123': {
149 '123': {
150 'uid': '123',
150 'uid': '123',
151 'pat': pattern,
151 'pat': pattern,
152 'url': 'http://r.io/i/${issue_id}',
152 'url': 'http://r.io/i/${issue_id}',
153 'pref': '#',
153 'pref': '#',
154 }
154 }
155 }
155 }
156
156
157 with mock.patch.object(IssueTrackerSettingsModel,
157 with mock.patch.object(IssueTrackerSettingsModel,
158 'get_global_settings', get_settings_mock):
158 'get_global_settings', get_settings_mock):
159 processed_text, issues = helpers.process_patterns(
159 processed_text, issues = helpers.process_patterns(
160 text_string, '')
160 text_string, '')
161
161
162 assert processed_text == expected_text
162 assert processed_text == expected_text
163
163
164
164
165 def test_process_patterns_non_existent_repo_name(backend):
165 def test_process_patterns_non_existent_repo_name(backend):
166 text_string = 'Fix #42'
166 text_string = 'Fix #42'
167 pattern = '(?:#)(?P<issue_id>\d+)'
167 pattern = '(?:#)(?P<issue_id>\d+)'
168 expected_text = ('Fix <a class="issue-tracker-link" '
168 expected_text = ('Fix <a class="issue-tracker-link" '
169 'href="http://r.io/do-not-exist/i/42">#42</a>')
169 'href="http://r.io/do-not-exist/i/42">#42</a>')
170
170
171 def get_settings_mock(self, cache=True):
171 def get_settings_mock(self, cache=True):
172 return {
172 return {
173 '123': {
173 '123': {
174 'uid': '123',
174 'uid': '123',
175 'pat': pattern,
175 'pat': pattern,
176 'url': 'http://r.io/${repo}/i/${issue_id}',
176 'url': 'http://r.io/${repo}/i/${issue_id}',
177 'pref': '#',
177 'pref': '#',
178 }
178 }
179 }
179 }
180
180
181 with mock.patch.object(IssueTrackerSettingsModel,
181 with mock.patch.object(IssueTrackerSettingsModel,
182 'get_global_settings', get_settings_mock):
182 'get_global_settings', get_settings_mock):
183 processed_text, issues = helpers.process_patterns(
183 processed_text, issues = helpers.process_patterns(
184 text_string, 'do-not-exist')
184 text_string, 'do-not-exist')
185
185
186 assert processed_text == expected_text
186 assert processed_text == expected_text
187
187
188
188
189 def test_get_visual_attr(baseapp):
189 def test_get_visual_attr(baseapp):
190 from rhodecode.apps._base import TemplateArgs
190 from rhodecode.apps._base import TemplateArgs
191 c = TemplateArgs()
191 c = TemplateArgs()
192 assert None is helpers.get_visual_attr(c, 'fakse')
192 assert None is helpers.get_visual_attr(c, 'fakse')
193
193
194 # emulate the c.visual behaviour
194 # emulate the c.visual behaviour
195 c.visual = AttributeDict({})
195 c.visual = AttributeDict({})
196 assert None is helpers.get_visual_attr(c, 'some_var')
196 assert None is helpers.get_visual_attr(c, 'some_var')
197
197
198 c.visual.some_var = 'foobar'
198 c.visual.some_var = 'foobar'
199 assert 'foobar' == helpers.get_visual_attr(c, 'some_var')
199 assert 'foobar' == helpers.get_visual_attr(c, 'some_var')
200
200
201
201
202 @pytest.mark.parametrize('test_text, inclusive, expected_text', [
202 @pytest.mark.parametrize('test_text, inclusive, expected_text', [
203 ('just a string', False, 'just a string'),
203 ('just a string', False, 'just a string'),
204 ('just a string\n', False, 'just a string'),
204 ('just a string\n', False, 'just a string'),
205 ('just a string\n next line', False, 'just a string...'),
205 ('just a string\n next line', False, 'just a string...'),
206 ('just a string\n next line', True, 'just a string\n...'),
206 ('just a string\n next line', True, 'just a string\n...'),
207 ], ids=no_newline_id_generator)
207 ], ids=no_newline_id_generator)
208 def test_chop_at(test_text, inclusive, expected_text):
208 def test_chop_at(test_text, inclusive, expected_text):
209 assert helpers.chop_at_smart(
209 assert helpers.chop_at_smart(
210 test_text, '\n', inclusive, '...') == expected_text
210 test_text, '\n', inclusive, '...') == expected_text
General Comments 0
You need to be logged in to leave comments. Login now